Reputation: 9841
I have two lists of maps and I want to form a list that would contain values from both lists basing on some common values.
list1 = [
%{email: "email1", name: "name1"},
%{email: "email2", name: "name2"}
]
list2 = [
%{"email" => "email3", "name" => "new name3"},
%{"email" => "email2", "name" => "new name2"}
]
The lists may be different size, one of them may even be empty.
The desired output would be:
[
{%{email: "email1", name: "name1"}, nil},
{%{email: "email2", name: "name2"},
%{"email" => "email2", "name" => "new name2"}},
{nil, %{"email" => "email3", "name" => "new name3"}}
]
Here is an implementation I came up with:
# 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
The implementation feels sub-optimal to me and it's hard to read. Am I missing something, could it be done in a more concise and more readable way?
Upvotes: 1
Views: 321
Reputation: 9841
The accepted answer works perfectly for the data set I've posted in the question, but it turned out that my data set has some duplicated data on both sides.
For example:
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"}
]
In the first list there are two items with duplicated keys and both of them nil
, and in the second list there are two keys with the same values equals to email2
.
I need to keep all these values in the resulting data set, so mapping by key won't work.
What I ended up doing is using List.myers_difference
function and transforming its results into the list I want to see:
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
The code above gives the result I wanted:
[
{%{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"}}
]
Upvotes: 0
Reputation: 23091
How about this?
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
Usage:
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"}}
]
Upvotes: 3