使用内部贴图中的关键帧将贴图列表转换为单个贴图



我有两个地图列表,如下所示:

list_one = [
%{id: :a, value: 1},
%{id: :b, value: 2},
%{id: :c, value: 3}
]

list_two = [
%{id: :a, value: 1},
%{id: :b, value: 4},
%{id: :d, value: 5}
]

我知道以下内容:

  • id在每个列表中都是唯一的
  • `id总是原子,值总是整数
  • 相同的id可能出现在两个列表中
  • 每个列表可能包含一些未出现在另一个列表中的id
  • 两个列表中都没有包含或多或少键的映射

我希望将它们合并到一个单独的映射中,以内部映射的id中的值为键,如果存在两个值,或者一个";空值";(假设为0(,如果两个列表中的一个不包含ids之一(最后一件事是可选的(。上述示例的期望输出为:

%{
a: %{
value_one: 1,
value_two: 1
},
b: %{
value_one: 2,
value_two: 4
},
c: %{
value_one: 3,
value_two: 0
},
d: %{
value_one: 0,
value_two: 5
}
}

我知道我可以通过做几个Enum.reduce来做到这一点,但感觉我错过了一些更容易的

我会首先使用Map.new/2将列表转换为所需形状的映射,然后使用Map.merge/3:将它们合并在一起

def merge(list_one, list_two) do
a =
Map.new(list_one, fn %{id: id, value: value_one} ->
{id, %{value_one: value_one, value_two: 0}}
end)
b =
Map.new(list_two, fn %{id: id, value: value_two} ->
{id, %{value_one: 0, value_two: value_two}}
end)
Map.merge(a, b, fn _id, %{value_one: value_one}, %{value_two: value_two} ->
%{value_one: value_one, value_two: value_two}
end)
end

输出:

%{
a: %{value_one: 1, value_two: 1},
b: %{value_one: 2, value_two: 4},
c: %{value_one: 3, value_two: 0},
d: %{value_one: 0, value_two: 5}
}

首先,您需要将列表转换为映射,以便稍后进行即时O(log N)访问。

[map_one, map_two] =
Enum.map([list_one, list_two], fn list ->
for %{id: id, value: value} <- list, into: %{}, do: {id, value}
end)

然后,你需要提前获得所有密钥,否则,就无法确定我们需要在哪里添加零。

keys =
list_one
|> Kernel.++(list_two)
|> get_in([Access.all(), :id])
|> Enum.uniq()

现在,我们都准备好了建立结果。

for k <- keys, into: %{} do
{k,
%{number_one: Map.get(map_one, k, 0),
number_two: Map.get(map_two, k, 0)}}  
end

给我们想要的

%{
a: %{number_one: 1, number_two: 1},
b: %{number_one: 2, number_two: 4},
c: %{number_one: 3, number_two: 0},
d: %{number_one: 0, number_two: 5}
}

我最喜欢亚当的答案,但既然你说"在任一列表中没有映射具有更多或更少的键";提到Enum.reduce,我想我会给出一个兼顾两者的答案!以下解决方案仅适用于两个列表,但它是另一个选项。😄

list_one = [
%{id: :a, value: 1},
%{id: :b, value: 2},
%{id: :c, value: 3}
]
list_two = [
%{id: :a, value: 1},
%{id: :b, value: 4},
%{id: :d, value: 5}
]
labels = [:value_one, :value_two]
# Helper function for building a single map out of a list of maps
build_map_with_label = fn acc, map_from_list, label ->
%{id: id, value: value} = map_from_list
{_, acc} =
Map.get_and_update(acc, id, fn current_value ->
map_from_list =
if current_value,
do: current_value,
else: %{}
{current_value, Map.put(map_from_list, label, value)}
end)
acc
end
# Zip reduce two lists using our map builder helper to produce a single map
Enum.zip_reduce(list_one, list_two, %{}, fn left, right, acc ->
acc
|> build_map_with_label.(left, Enum.at(labels, 0))
|> build_map_with_label.(right, Enum.at(labels, 1))
end)
# This second pass fills in any missing keys with 0
|> Enum.reduce(%{}, fn {id, id_map}, acc ->
new_id_map =
Enum.reduce(labels, id_map, fn label, acc ->
Map.put_new(acc, label, 0)
end)
Map.put(acc, id, new_id_map)
end)
|> IO.inspect()

打印:

%{
a: %{value_one: 1, value_two: 1},
b: %{value_one: 2, value_two: 4},
c: %{value_one: 3, value_two: 0},
d: %{value_one: 0, value_two: 5}
}

这是Nezteb使用Enum.zip_reduce/4的答案的重构版本,这是一种非常酷的方法,但只需要在put_in/3Access.key/2的帮助下简化为基础。请注意,只有当两个列表具有相同数量的项目时,这才有效。

Enum.zip_reduce(list_one, list_two, %{}, fn %{id: id_one, value: value_one},
%{id: id_two, value: value_two},
acc ->
acc
|> put_in([Access.key(id_one, %{value_two: 0}), :value_one], value_one)
|> put_in([Access.key(id_two, %{value_one: 0}), :value_two], value_two)
end)

这里有一个版本使用了类似的方法,但没有zip_reduce,只需对两个列表进行一次遍历。即使列表的长度不同,这也能起作用:

a =
for %{id: id, value: value} <- list_one, reduce: %{} do
acc -> put_in(acc, [Access.key(id, %{value_two: 0}), :value_one], value)
end
for %{id: id, value: value} <- list_two, reduce: a do
acc -> put_in(acc, [Access.key(id, %{value_one: 0}), :value_two], value)
end

最新更新