假设我有几个列表,里面有相同的键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}
]
谢谢!
答案 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