【问题标题】:Compare two lists of maps and return a new transformed list (Elixir)比较两个地图列表并返回一个新的转换列表(Elixir)
【发布时间】:2019-06-30 14:21:44
【问题描述】:

我有两个地图列表 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 中获取最终结果中选择值的方法(如您所见,我将“选择”的值保留为 [])。如何涵盖所有三种情况并获得所需结构中的列表?

【问题讨论】:

  • 对我来说,看起来您有两个列表,并且您将每个列表中的一些数据随机组合成一个结果列表。逻辑是什么?在"is_common" => yes(if it exists in both lists) OR only list a OR only list b 这一行中,it 指的是什么?这就引出了提问的黄金法则之一:永远不要使用代词。

标签: list dictionary functional-programming elixir


【解决方案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()} 在列表元素中的唯一性。现在只需根据您的需要迭代和更新元素。

【讨论】:

    【解决方案2】:

    我将采用不同的方法,尝试使用尾递归遍历两个列表。

    为了使用这种方法,我们需要保证ab 两个列表都将按照允许我们进行匹配的字段排序,在本例中为schoolclass

    这是必需的,因为在尾递归期间,我们将在运行中进行列表之间的匹配,并且必须保证如果我们留下不匹配的 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 和类fields 升序排列。

    让我们从困难的部分开始吧。现在我们需要考虑遍历两个有序列表。递归将在match_lists 函数上完成。

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

    1. [MATCH] 两个列表的Headschoolclass 字段相同,所以它们匹配。在这种情况下,我们构建新元素并将其添加到累加器中。在下一次调用时,我们只传递两个列表的尾部。
    2. [UNMATCHED B] Heada 的元素在前面 Headb 的元素,这是 school 字段(或 class 字段,如果 school 相同)具有更大的值.这意味着列表b 的当前Head 元素没有可用的匹配项,因为列表a 已经在它前面。因此将构建一个不匹配的b 元素并将其添加到累加器中。在下一次通话中,我们刚刚通过了 b 的尾部,但完整的 a list.
    3. [UNMATCHED A] 与第 2 点相同,但尊重列表 a。列表bHead 元素位于列表aHead 元素之前。这意味着a 中的Head 元素没有可用的匹配项,因为b 中的Head 已经领先。将构建一个不匹配的 a 元素并将其添加到累加器中。
    4. [UNMATCHED B] 列表a 为空。将使用bHead 生成一个不匹配的 B,并将其添加到累加器中。
    5. [UNMATCHED A] 列表b 为空。将使用aHead 生成一个不匹配的 A,并将其添加到累加器中。
    6. [END] 两个列表都是空的。递归结束,返回累加器。
    def match_lists(a, b, acc \\ [] )
    
    # Case: Element in both lists  
    def match_lists(
          [%{"school" => school, "class" => class, "student" => student} | rest_a],
          [%{"school" => school, "class" => class, "choice" => choice} | rest_b],
          acc
        ) do
      element = build(school, class, student, [choice], true)
      match_lists(rest_a, rest_b, [element | acc])
    end
    
    # Case: Element only in list B case. So it is a B case
    def match_lists(
          [%{"school" => school_a, "class" => class_a} | _] = a,
          [%{"school" => school_b, "class" => class_b, "choice" => choice} | rest_b],
          acc
        )
        when school_a > school_b or class_a > class_b do
      element = build(school_b, class_b, nil, [choice], "only_list_b")
      match_lists(a, rest_b, [element | acc])
    end
    
    # Case: No more elementes in A. So It is a B case
    def match_lists([], [%{"school" => school, "class" => class, "choice" => choice} | rest_b], acc) do
      element = build(school, class, nil, [choice], "only_list_b")
      match_lists([], rest_b, [element | acc])
    end
    
    # Case: Element only in list A
    def match_lists(
          [%{"school" => school_a, "class" => class_a, "student" => student} | rest_a],
          [%{"school" => school_b, "class" => class_b} | _] = b,
          acc
        )
        when school_b > school_a or class_b > class_a do
      element = build(school_a, class_a, student, [], "only_list_a")
      match_lists(rest_a, b, [element | acc])
    end
    
    # Case: No more elementes in B. So It is an uncommon A case
    def match_lists([%{"school" => school, "class" => class, "student" => student} | rest_a], [], acc) do
      element = build(school, class, student, [], "only_list_a")
      match_lists(rest_a, [], [element | acc])
    end
    
    def match_lists([], [], acc) do
      acc
    end
    
    defp build(school, class, student, choices, is_common) do
      %{
        "school" => school,
        "class" => class,
        "student" => student,
        "choices" => choices,
        "is_common" => is_common
      }
    end
    
    iex(1)> match_lists(ordered_a, ordered_b)
    [
      %{
        "choices" => [],
        "class" => 6,
        "is_common" => "only_list_a",
        "school" => "c",
        "student" => "jane doe2"
      },
      %{
        "choices" => [],
        "class" => 9,
        "is_common" => "only_list_a",
        "school" => "b",
        "student" => "jane1 doe"
      },
      %{
        "choices" => ["maths"],
        "class" => 6,
        "is_common" => "only_list_b",
        "school" => "b",
        "student" => nil
      },
      %{
        "choices" => ["science"],
        "class" => 9,
        "is_common" => "only_list_b",
        "school" => "a",
        "student" => nil
      },
      %{
        "choices" => ["arts"],
        "class" => 1,
        "is_common" => true, 
        "school" => "a",
        "student" => "jane doe"
      }
    ]
    

    希望对您有所帮助。

    【讨论】:

      猜你喜欢
      • 2014-02-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多