比较两个地图列表并返回一个新的转换列表(Elixir)

时间:2019-06-29 17:27:08

标签: list dictionary functional-programming elixir elixir-framework

我有两个列表a和b。

a = [
%{"school" => "a", "class" => 1, "student" => "jane doe"},
%{"school" => "b", "class" => 9, "student" => "jane1 doe"},
%{"school" => "c", "class" => 6, "student" => "jane doe2"}
]

b = [
%{"choice" => "arts", "class" => 1, "school" => "a"},
%{"choice" => "science", "class" => 9, "school" => "a"},
%{"choice" => "maths", "class" => 6, "school" => "b"}
]

我希望能够比较两个列表并生成具有以下结构的项目的列表

desired_result = [
%{
"school" => "a",
"class" => 1,
"student" => "jane doe" or nil (if student exists only in list b but not in a),
"choices" => ["arts"] or [] (if student exists only in list a but not in b),
"is_common" => yes(if the student exists in both lists) OR only list a OR only list b
}
]

我尝试使用Enum.into和Enum.member吗?功能,我已经能够实现我想要的解决方案的60%。

Enum.into(a, [], fn item ->
      if Enum.member?(b, %{
        "school" => item["school"],
        "class" => item["class"]
      }) do
        %{
       "school" => item["school"],
       "class" => item["class"],
       "student" => item["student"],
       "choices" => [],
       "is_common" => "yes"
     }
   else
     %{
       "school" => item["school"],
       "class" => item["class"],
       "student" => item["student"],
       "choices" => [],
       "is_common" => "only list a"
     }
   end
    end)

上述问题是它涵盖了两个列表中的常见情况以及仅在列表a中的情况;但是它不包括仅在列表b中的那些。而且,我也找不到从列表b获得最终结果中选择值的方法(如您所见,我将[choice]的值保留为[])。如何覆盖所有三种情况并在所需的结构中获得包含值的列表?

2 个答案:

答案 0 :(得分:1)

让我们先从您现有的产品中得出一个简单的结果。我假设school + class对就是定义的唯一性。

[a, b]
|> Enum.map(fn list ->
  Enum.group_by(list, & {&1["class"], &1["school"]})
end)
|> Enum.reduce(
  &Map.merge(&1, &2, fn _, [v1], [v2] -> [Map.merge(v1, v2)] end))
|> Enum.map(fn {_, [v]} -> v end)
#⇒ [
#   %{"choice" => "arts", "class" => 1, "school" => "a", "student" => "jane doe"},
#   %{"choice" => "maths", "class" => 6, "school" => "b"},
#   %{"class" => 6, "school" => "c", "student" => "jane doe2"},
#   %{"choice" => "science", "class" => 9, "school" => "a"},
#   %{"class" => 9, "school" => "b", "student" => "jane1 doe"}
# ]

可以随意运行上述逐个子句以查看所有涉及的转换。

上面的列表通过%{"school" => any(), "class" => any()}保证列表元素之间的唯一性。现在,只需根据需要迭代并更新元素。

答案 1 :(得分:0)

我将采用另一种方法,尝试使用尾部递归遍历两个列表。

为了使用这种方法,我们需要保证两个列表ab都将通过允许我们进行匹配的字段(在这种情况下为school和{ {1}}。

这是必需的,因为在尾部递归过程中,我们将即时进行列表之间的匹配,并且必须保证如果我们留下不匹配的class元素,则不可能找到{{ 1}}之后再比赛

a

因此,这两个列表将按b和类# With this both lists will be ordered ascendently by school and class fields. ordered_a = Enum.sort(a, &((&1["school"] < &2["school"]) || (&1["class"] <= &2["class"] ))) ordered_b = Enum.sort(b, &((&1["school"] < &2["school"]) || (&1["class"] <= &2["class"] ))) 升序排列。

让我们谈谈困难的部分。现在我们需要考虑遍历两个有序列表。递归将通过school函数完成。

我们可以使标题的这6种可能的模式匹配:

  1. [MATCH]两个列表的fields的{​​{1}}和match_lists字段是相同的,因此它们匹配。在这种情况下,我们将构建新元素并将其添加到累加器中。在下一次通话中,我们只传递了两个列表的结尾。
  2. school的[UNMATCHED B] class元素位于Head的{​​{1}}元素之前,这是Head字段(或a字段如果Head相同)则具有更大的值。这意味着列表b的当前school元素没有可用的匹配项,因为列表class已经在它的前面。因此,将构建不匹配的school元素并将其添加到累加器中。在下一次通话中,我们只传递了Head的尾部,但完整了b
  3. [UNMATCHED A]与第2点相同,但尊重列表a。列表b的{​​{1}}元素位于列表b的{​​{1}}元素之前。这意味着a list.中的a元素不可用,因为Head中的b已经在前面。将构建不匹配的Head元素并将其添加到累加器中。
  4. [未配对B]列表a为空。 Head的{​​{1}}会生成不匹配的B,并将其添加到累加器中。
  5. [UNMATCHED A]列表a为空。 Head的{​​{1}}会产生不匹配的A,并将其添加到累加器中。
  6. [END]两个列表都是空的。递归已结束,并且将返回累加器。
b
a

希望有帮助。