【问题标题】:merge same keys data into one map with unique keys elixir将相同的键数据合并到一个具有唯一键的映射中
【发布时间】:2019-05-30 11:51:02
【问题描述】:

我有这个 json

[
  %{
    "159.69.136.31" => [
      %{"2015" => ["4"]},
      %{"2016" => []},
      %{"2017" => ["9"]},
      %{"2018" => []},
      %{"2019" => ["05"]}
    ]
  },
  %{ 
    "94.130.139.38" => [
      %{"2015" => []},
      %{"2016" => []},
      %{"2017" => []},
      %{"2018" => ["5", "8"]},
      %{"2019" => []}
    ]
  },
  %{
    "94.130.217.56" => [
      %{"2015" => []},
      %{"2016" => []},
      %{"2017" => []},
      %{"2018" => []},
      %{"2019" => []}
    ]
  }
]

我想让它像

[
  %{"2015" => ["4"]},
  %{"2016" => []},
  %{"2017" => ["9"]},
  %{"2018" => ["5", "8"]},
  %{"2019" => ["05"]}
]

基本上,它合并了同一年的键和地图上可用的数据。我已经用不同的方法尝试过这个解决方案,但它不起作用Elixir: Merge list with same map keys to one map

更新:年份和 IP 不变 更新一点关于此的更多信息..

years = ["2015", "2016", "2017", "2018", "2019"]
servers = [@seaweedfs_new, @seaweedfs_old, @seaweedfs_oldest]

  Enum.map(servers, fn server ->
    type = seaweefs_type(server)
    attribute = seaweedfs_attribute(server)
    url = "http://" <> server <> ":8888" <> "/#{camera.exid}/snapshots/recordings/"
    year_values =
      Enum.map(years, fn year ->
        final_url = url <> year <> "/"
        %{
          "#{year}" => request_from_seaweedfs(final_url, type, attribute)
        }
      end)
    %{
      "#{server}" => year_values
    }
  end)

这就是我获取年份值并将它们添加到服务器对象的方式。如果有可能在获得年份值的同时将其分解?

"#{year}" =&gt; request_from_seaweedfs(final_url, type, attribute)这个请求基本返回如"2015" =&gt; ["1", "2", "3"],有没有可能在进入服务器之前合并几年?

【问题讨论】:

    标签: elixir


    【解决方案1】:

    您需要获取地图的平面列表,然后合并所有地图。

    input
    |> Enum.flat_map(&Map.values/1)
    |> Enum.flat_map(& &1)
    |> Enum.reduce(&Map.merge(&1, &2, fn _, v1, v2 ->
      v1 ++ v2
    end))
    #⇒ %{
    #    "2015" => ["4"],
    #    "2016" => [],
    #    "2017" => ["9"],
    #    "2018" => ["5", "8"],
    #    "2019" => ["05"]
    # }
    

    要获取地图列表(恕我直言,这是错误的设计决定),只需执行以下操作:

    for {k, v} <- map, do: %{k => v}
    #⇒ [
    #    %{"2015" => ["4"]},
    #    %{"2016" => []},
    #    %{"2017" => ["9"]},
    #    %{"2018" => ["5", "8"]},
    #    %{"2019" => ["05"]}
    # ]
    

    【讨论】:

    • 这对你来说没有任何意义,但我总是在 Elixir 中寻找你的建议,并严格按照你的建议行事。感谢您的回答 mudasobwa。
    • 很高兴能提供任何帮助。
    • @JunaidFarooq,你知道,这个答案会遍历列表 1、2、3 次,然后必须在此之上合并地图,然后进行从地图到列表的转换。甚至 Aleksei 也会告诉你这不是最有效的答案。
    • @7stud I second ⬆️(好吧,你的仍然遍历地图一次。)
    • @7stud 但这张地图有 years 作为键。这显然意味着可读性比性能更重要。
    【解决方案2】:

    要回答您的第一个问题,如何从 X 中生成 Y,这里有一个快速而简单的解决方案:

    data = [
      %{
        "159.69.136.31" => [
          %{"2015" => ["4"]},
          %{"2016" => []},
          %{"2017" => ["9"]},
          %{"2018" => []},
          %{"2019" => ["05"]}
        ]
      },
      %{
        "94.130.139.38" => [
          %{"2015" => []},
          %{"2016" => []},
          %{"2017" => []},
          %{"2018" => ["5", "8"]},
          %{"2019" => []}
        ]
      },
      %{
        "94.130.217.56" => [
          %{"2015" => []},
          %{"2016" => []},
          %{"2017" => []},
          %{"2018" => []},
          %{"2019" => []}
        ]
      }
    ]
    

    代码:

    data
    |> Enum.map(&Map.values/1)
    |> List.flatten
    |> Enum.map(fn year_map -> year_map |> Map.to_list |> hd end)
    |> Enum.reduce(%{}, fn {year, year_data}, acc ->
      Map.update(acc, year, year_data, &[&1 | year_data])
    end)
    |> Enum.map(fn {year, year_data} ->
      {year, List.flatten(year_data) |> Enum.reject(&is_nil/1)}
    end)
    |> IO.inspect
    

    返回:

    [
      {"2015", ["4"]},
      {"2016", []},
      {"2017", ["9"]},
      {"2018", ["5", "8"]},
      {"2019", ["05"]}
    ]
    

    在管道和函数中插入更多 IO.inspects 以查看正在进行的转换。

    【讨论】:

      【解决方案3】:

      你为什么这样做:

      "#{server}" => year_values
      

      当您不关心最终结果中的服务器名称时?这样做会使访问相关数据变得更加困难。

      您可以使用for loop(也称为列表推导)和:reduce 选项(elixir 1.8+)来创建结果映射:

      defmodule My do
      
        @years ["2015", "2016", "2017", "2018", "2019"]
        @servers ["new", "old", "oldest"] 
      
        def request_from_seaweedfs(url, _type, _attribute) do
          IO.puts url
          "#{:random.uniform(20)}"  #Returns a random string
        end
      
        def get_data do
          for server <- @servers, year <- @years, reduce: %{} do
            map -> #This is the map specified here --------^
              url = "http://#{server}:8888/camera.exid/snapshots/recordings/#{year}/"
              result = request_from_seaweedfs(url, "x", "y")
              IO.puts "#{year}: #{result}"
              Map.update(map, year, [result], &([result|&1]))
          end
        end
      
      end
      

      输出:

      ~/elixir_programs$ iex my.ex
      Erlang/OTP 20 [erts-9.3] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false]
      Interactive Elixir (1.8.2) - press Ctrl+C to exit (type h() ENTER for help)
      
      iex(1)> My.get_data
      http://new:8888/camera.exid/snapshots/recordings/2015/
      2015: 9
      http://new:8888/camera.exid/snapshots/recordings/2016/
      2016: 15
      http://new:8888/camera.exid/snapshots/recordings/2017/
      2017: 19
      http://new:8888/camera.exid/snapshots/recordings/2018/
      2018: 11
      http://new:8888/camera.exid/snapshots/recordings/2019/
      2019: 7
      http://old:8888/camera.exid/snapshots/recordings/2015/
      2015: 12
      http://old:8888/camera.exid/snapshots/recordings/2016/
      2016: 19
      http://old:8888/camera.exid/snapshots/recordings/2017/
      2017: 14
      http://old:8888/camera.exid/snapshots/recordings/2018/
      2018: 10
      http://old:8888/camera.exid/snapshots/recordings/2019/
      2019: 12
      http://oldest:8888/camera.exid/snapshots/recordings/2015/
      2015: 3
      http://oldest:8888/camera.exid/snapshots/recordings/2016/
      2016: 5
      http://oldest:8888/camera.exid/snapshots/recordings/2017/
      2017: 14
      http://oldest:8888/camera.exid/snapshots/recordings/2018/
      2018: 4
      http://oldest:8888/camera.exid/snapshots/recordings/2019/
      2019: 12
      
      %{
        "2015" => ["3", "12", "9"],
        "2016" => ["5", "19", "15"],
        "2017" => ["14", "14", "19"],
        "2018" => ["4", "10", "11"],
        "2019" => ["12", "12", "7"]
      }
      

      要获取您的列表,您可以:

      My.get_data |> Enum.map(fn {key,val} -> %{key => val} end)
      

      产生:

      [
        %{"2015" => ["3", "12", "9"]},
        %{"2016" => ["5", "19", "15"]},
        %{"2017" => ["14", "14", "19"]},
        %{"2018" => ["4", "10", "11"]},
        %{"2019" => ["12", "12", "7"]}
      ]
      

      而且,如果需要维护数据的顺序,那么您可以这样做:

      My.get_data |> Enum.map(fn {key,val} -> %{key => Enum.reverse(val)} end)
      

      【讨论】:

      • 最终结果会是什么样子?
      • @JunaidFarooq,输出中没有问题。你向下滚动了吗?你真的想要一个像你的问题一样的列表吗?列表只会让访问数据变得更加困难。
      • 是的,谢谢,我已经向下滚动了,但我需要一个列表,最后写成:)
      • @JunaidFarooq,请参阅我的答案底部的列表转换。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-01-29
      • 1970-01-01
      • 1970-01-01
      • 2020-04-17
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多