我将采用不同的方法,尝试使用尾递归遍历两个列表。
为了使用这种方法,我们需要保证a 和b 两个列表都将按照允许我们进行匹配的字段排序,在本例中为school 和class。
这是必需的,因为在尾递归期间,我们将在运行中进行列表之间的匹配,并且必须保证如果我们留下不匹配的 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 种可能的标题模式匹配:
- [MATCH] 两个列表的
Head 的school 和class 字段相同,所以它们匹配。在这种情况下,我们构建新元素并将其添加到累加器中。在下一次调用时,我们只传递两个列表的尾部。
- [UNMATCHED B]
Head 的 a 的元素在前面 Head 的 b 的元素,这是 school 字段(或 class 字段,如果 school 相同)具有更大的值.这意味着列表b 的当前Head 元素没有可用的匹配项,因为列表a 已经在它前面。因此将构建一个不匹配的b 元素并将其添加到累加器中。在下一次通话中,我们刚刚通过了 b 的尾部,但完整的 a list.
- [UNMATCHED A] 与第 2 点相同,但尊重列表
a。列表b 的Head 元素位于列表a 的Head 元素之前。这意味着a 中的Head 元素没有可用的匹配项,因为b 中的Head 已经领先。将构建一个不匹配的 a 元素并将其添加到累加器中。
- [UNMATCHED B] 列表
a 为空。将使用b 的Head 生成一个不匹配的 B,并将其添加到累加器中。
- [UNMATCHED A] 列表
b 为空。将使用a 的Head 生成一个不匹配的 A,并将其添加到累加器中。
- [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"
}
]
希望对您有所帮助。