如何通过键值 "intersperse" 两个列表?
How to "intersperse" two lists by a key value?
我有两个地图列表,我想形成一个列表,其中包含基于一些共同值的两个列表中的值。
list1 = [
%{email: "email1", name: "name1"},
%{email: "email2", name: "name2"}
]
list2 = [
%{"email" => "email3", "name" => "new name3"},
%{"email" => "email2", "name" => "new name2"}
]
列表的大小可能不同,其中一个甚至可能是空的。
所需的输出将是:
[
{%{email: "email1", name: "name1"}, nil},
{%{email: "email2", name: "name2"},
%{"email" => "email2", "name" => "new name2"}},
{nil, %{"email" => "email3", "name" => "new name3"}}
]
这是我想出的实现:
# to produce the output above, call it with
# list_intersperse(list1, list2, :email, "email")
def list_intersperse(list1, list2, key1, key2) do
map2 = Enum.map(list2, fn elem -> {elem[key2], elem} end) |> Map.new()
result =
Enum.map(list1, fn elem ->
common_value = Map.get(elem, key1)
{elem, map2[common_value]}
end)
keys1 = Enum.map(list1, fn elem -> Map.get(elem, key1) end) |> MapSet.new()
result ++
(list2
|> Enum.reject(&MapSet.member?(keys1, &1[key2]))
|> Enum.map(&{nil, &1}))
end
我觉得这个实现不是最理想的,而且很难读懂。我是不是遗漏了什么,可以用更简洁、更易读的方式来完成吗?
这个怎么样?
def pair(list1, list2) do
map1 = Map.new(list1, fn item -> {item.email, item} end)
map2 = Map.new(list2, fn item -> {item["email"], item} end)
all_emails = MapSet.new(Map.keys(map1) ++ Map.keys(map2))
for email <- all_emails, do: {map1[email], map2[email]}
end
用法:
iex(1)> Example.pair(list1, list2)
[
{%{email: "email1", name: "name1"}, nil},
{%{email: "email2", name: "name2"},
%{"email" => "email2", "name" => "new name2"}},
{nil, %{"email" => "email3", "name" => "new name3"}}
]
接受的答案非常适合我在问题中发布的数据集,但事实证明我的数据集在两边都有一些重复的数据。
例如:
list1 = [
%{email: nil, name: "nil1"},
%{email: nil, name: "nil2"},
%{email: "email1", name: "name1"},
%{email: "email2", name: "name2"}
]
list2 = [
%{"email" => "email3", "name" => "new name3"},
%{"email" => "email2", "name" => "new name2"},
%{"email" => "email2", "name" => "new name2"}
]
在第一个列表中有两个具有重复键的项目并且它们都是 nil
,在第二个列表中有两个具有相同值的键等于 email2
.
我需要将所有这些值保留在生成的数据集中,因此按键映射不起作用。
我最终做的是使用 List.myers_difference
函数并将其结果转换为我想要查看的列表:
def list_intersperse(list1, list2, key1, key2) do
list1 = Enum.sort_by(list1, &Map.get(&1, key1))
list2 = Enum.sort_by(list2, &Map.get(&1, key2))
List.myers_difference(list1, list2, fn elem1, elem2 ->
if Map.get(elem1, key1) == Map.get(elem2, key2), do: {elem1, elem2}
end)
|> Enum.map(&myers_to_intersperse/1)
|> List.flatten()
end
defp myers_to_intersperse({:del, list}), do: Enum.map(list, &{&1, nil})
defp myers_to_intersperse({:ins, list}), do: Enum.map(list, &{nil, &1})
defp myers_to_intersperse({:diff, tuple}), do: tuple
上面的代码给出了我想要的结果:
[
{%{email: nil, name: "nil1"}, nil},
{%{email: nil, name: "nil2"}, nil},
{%{email: "email1", name: "name1"}, nil},
{%{email: "email2", name: "name2"}, %{"email" => "email2", "name" => "new name2"}},
{nil, %{"email" => "email2", "name" => "new name2"}},
{nil, %{"email" => "email3", "name" => "new name3"}}
]
我有两个地图列表,我想形成一个列表,其中包含基于一些共同值的两个列表中的值。
list1 = [
%{email: "email1", name: "name1"},
%{email: "email2", name: "name2"}
]
list2 = [
%{"email" => "email3", "name" => "new name3"},
%{"email" => "email2", "name" => "new name2"}
]
列表的大小可能不同,其中一个甚至可能是空的。
所需的输出将是:
[
{%{email: "email1", name: "name1"}, nil},
{%{email: "email2", name: "name2"},
%{"email" => "email2", "name" => "new name2"}},
{nil, %{"email" => "email3", "name" => "new name3"}}
]
这是我想出的实现:
# to produce the output above, call it with
# list_intersperse(list1, list2, :email, "email")
def list_intersperse(list1, list2, key1, key2) do
map2 = Enum.map(list2, fn elem -> {elem[key2], elem} end) |> Map.new()
result =
Enum.map(list1, fn elem ->
common_value = Map.get(elem, key1)
{elem, map2[common_value]}
end)
keys1 = Enum.map(list1, fn elem -> Map.get(elem, key1) end) |> MapSet.new()
result ++
(list2
|> Enum.reject(&MapSet.member?(keys1, &1[key2]))
|> Enum.map(&{nil, &1}))
end
我觉得这个实现不是最理想的,而且很难读懂。我是不是遗漏了什么,可以用更简洁、更易读的方式来完成吗?
这个怎么样?
def pair(list1, list2) do
map1 = Map.new(list1, fn item -> {item.email, item} end)
map2 = Map.new(list2, fn item -> {item["email"], item} end)
all_emails = MapSet.new(Map.keys(map1) ++ Map.keys(map2))
for email <- all_emails, do: {map1[email], map2[email]}
end
用法:
iex(1)> Example.pair(list1, list2)
[
{%{email: "email1", name: "name1"}, nil},
{%{email: "email2", name: "name2"},
%{"email" => "email2", "name" => "new name2"}},
{nil, %{"email" => "email3", "name" => "new name3"}}
]
接受的答案非常适合我在问题中发布的数据集,但事实证明我的数据集在两边都有一些重复的数据。
例如:
list1 = [
%{email: nil, name: "nil1"},
%{email: nil, name: "nil2"},
%{email: "email1", name: "name1"},
%{email: "email2", name: "name2"}
]
list2 = [
%{"email" => "email3", "name" => "new name3"},
%{"email" => "email2", "name" => "new name2"},
%{"email" => "email2", "name" => "new name2"}
]
在第一个列表中有两个具有重复键的项目并且它们都是 nil
,在第二个列表中有两个具有相同值的键等于 email2
.
我需要将所有这些值保留在生成的数据集中,因此按键映射不起作用。
我最终做的是使用 List.myers_difference
函数并将其结果转换为我想要查看的列表:
def list_intersperse(list1, list2, key1, key2) do
list1 = Enum.sort_by(list1, &Map.get(&1, key1))
list2 = Enum.sort_by(list2, &Map.get(&1, key2))
List.myers_difference(list1, list2, fn elem1, elem2 ->
if Map.get(elem1, key1) == Map.get(elem2, key2), do: {elem1, elem2}
end)
|> Enum.map(&myers_to_intersperse/1)
|> List.flatten()
end
defp myers_to_intersperse({:del, list}), do: Enum.map(list, &{&1, nil})
defp myers_to_intersperse({:ins, list}), do: Enum.map(list, &{nil, &1})
defp myers_to_intersperse({:diff, tuple}), do: tuple
上面的代码给出了我想要的结果:
[
{%{email: nil, name: "nil1"}, nil},
{%{email: nil, name: "nil2"}, nil},
{%{email: "email1", name: "name1"}, nil},
{%{email: "email2", name: "name2"}, %{"email" => "email2", "name" => "new name2"}},
{nil, %{"email" => "email2", "name" => "new name2"}},
{nil, %{"email" => "email3", "name" => "new name3"}}
]