如何通过键值 "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"}}
]