合并地图清单

时间:2019-05-21 16:45:52

标签: elixir

假设我有几个列表,里面有相同的键id

[
 %{id: 1, total: 10},
 %{id: 2, total: 20},
 %{id: 3, total: 30}
]

[
 %{id: 1, name: "what", age: 23},
 %{id: 2, name: "pro", age: 56},
 %{id: 3, name: "rider", age: 25}
]

如何获得以下列表?

[
 %{id: 1, total: 10, name: "what", age: 23},
 %{id: 2, total: 20, name: "pro", age: 56},
 %{id: 3, total: 30, name: "rider", age: 25}
]

谢谢!

3 个答案:

答案 0 :(得分:2)

我会为其中一个列表建立索引,以便与另一个列表中的地图更快地匹配:

maps1 = [
 %{id: 1, total: 10},
 %{id: 2, total: 20},
 %{id: 3, total: 30}
]

indexed_maps1 = for map <- maps1, into: %{} do
  {map[:id], map}
end 

IO.inspect indexed_maps1

maps2 = [
 %{id: 1, name: "what", age: 23},
 %{id: 2, name: "pro", age: 56},
 %{id: 3, name: "rider", age: 25}
]

Enum.map(maps2, &(Map.merge(&1, indexed_maps1[&1[:id]] )))

输出:

%{1 => %{id: 1, total: 10}, 2 => %{id: 2, total: 20}, 3 => %{id: 3, total: 30}}

[
  %{age: 23, id: 1, name: "what", total: 10},
  %{age: 56, id: 2, name: "pro", total: 20},
  %{age: 25, id: 3, name: "rider", total: 30}
]

如果列表按顺序排列,则可以执行以下操作:

  def merge(list1, list2), do: merge(list1, list2, [])

  defp merge([map1|tail1], [map2|tail2], result) do
    merge(tail1, tail2, [Map.merge(map1, map2)|result])
  end
  defp merge([], [], result), do: Enum.reverse(result)

答案 1 :(得分:1)

这是一种方法:

iex(1)> totals = [
...(1)>  %{id: 1, total: 10},
...(1)>  %{id: 2, total: 20},
...(1)>  %{id: 3, total: 30}
...(1)> ]
[%{id: 1, total: 10}, %{id: 2, total: 20}, %{id: 3, total: 30}]
iex(2)> entries = [
...(2)>  %{id: 1, name: "what", age: 23},
...(2)>  %{id: 2, name: "pro", age: 56},
...(2)>  %{id: 3, name: "rider", age: 25}
...(2)> ]
[
  %{age: 23, id: 1, name: "what"},
  %{age: 56, id: 2, name: "pro"},
  %{age: 25, id: 3, name: "rider"}
]
iex(3)> for entry <- entries do
...(3)>   Map.merge(entry, Enum.find(totals, & &1.id == entry.id))
...(3)> end
[
  %{age: 23, id: 1, name: "what", total: 10},
  %{age: 56, id: 2, name: "pro", total: 20},
  %{age: 25, id: 3, name: "rider", total: 30}
]

请注意,它假定您在两个列表中始终都有匹配的条目。

答案 2 :(得分:0)

出于完整性考虑,我将发布更多使用裸递归的 erlangish 解决方案。

 
def merge_am(elems, totals, acc \\ []) do
  elems
  |> Enum.sort_by(& &1.id)
  |> do_merge_am(Enum.sort_by(totals, & &1.id), acc)
  |> Enum.reverse()
end

defp do_merge_am([], [], acc), do: acc
defp do_merge_am([], list, acc), do: Enum.reverse(list) ++ acc
defp do_merge_am(list, [], acc), do: Enum.reverse(list) ++ acc

defp do_merge_am([%{id: id} = he | te], [%{id: id} = ht | tt], acc),
  do: do_merge_am(te, tt, [Map.merge(he, ht) | acc])

defp do_merge_am([%{id: ide} = he | te], [%{id: idt} = ht | tt], acc)
  when ide < idt, do: do_merge_am(te, [ht | tt], [he | acc])

defp do_merge_am([%{id: _ide} = he | te], [%{id: _idt} = ht | tt], acc),
  do: do_merge_am([he | te], tt, [ht | acc])

想法是遍历这两个列表,并在找到的任何地方都找到最接近的id项。如果两个列表都具有,则将调用Map.merge/2(第3个子句),否则,将以id最接近的位置放在结果的前面。该方法要求将列表预先排序。

这似乎有些冗长,但它赢得了基准测试之战(略微反对@ 7stud的解决方案,并大大击败了Jose的解决方案:)

defmodule Merger.Bench do
  use Benchfella

  @entries for i <- 1..1000, do: %{id: i, total: 10 * i}
  @totals for i <- 1..1000,
    do: %{id: i, name: "Name_#{i}", age: Enum.random(18..100)}

  bench("7s", do: Merger.merge_7s(@entries, @totals))
  bench("jv", do: Merger.merge_jv(@entries, @totals))
  bench("am", do: Merger.merge_am(@entries, @totals))
end

结果:

Settings:
  duration:      1.0 s

## Merger.Bench
[06:27:16] 1/3: 7s
[06:27:20] 2/3: am
[06:27:22] 3/3: jv

Finished in 7.39 seconds

## Merger.Bench
ben iterations   average time 
am        5000   355.31 µs/op
7s        5000   511.49 µs/op
jv         100   18755.67 µs/op