I will go with a different approach, trying to go through both lists using tail recursion.
In order to use this approach we need guarantee that both lists a and b will be ordered by the fields that allow us to make the match, in this case school and class.
This is needed because during the tail recursion we will be making the match between lists on the fly and it is mandatory to guarantee that if we are leaving an unmatched a element it is not possible to find an b match later
# 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"] )))
With this both list will be ascending ordered by school and class fields.
Let's go with the hard part. Now we need to think about going through two ordered lists. The recursion will be done over the match_lists function.
We can have these 6 possibles pattern match of the headers:
- [MATCH] The
school and class fields of the Head of the two lists are the same, so they make a match. In this case we build the new element and add it to the accumulator. On next call we just pass the tail of both lists.
- [UNMATCHED B]
Head element of a is ahead Head element of b, this is school field (or class field if school is the same) has a bigger value. That means there is no match available for current Head element of list b since the list a is already ahead it. So an unmatched b element will be built and added to the accumulator. On next call we just passed the tail of b but the full a list.
- [UNMATCHED A] Same that point 2 but respect to list
a. The Head element of list b is ahead the Head element of list a. That means there is no match available for Head element in a since Head in b is already ahead. An unmatched a element will be build and added to the accumulator.
- [UNMATCHED B] The list
a is empty. An unmatched B will generated with the Head of band added to the accumulator.
- [UNMATCHED A] The list
b is empty. An unmatched A will generated with the Head of a and added to the accumulator.
- [END] Both list are empty. The recursion has ended and the accumulator will be returned.
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"
}
]
Hope it helps.