zhanjingbaobao
Merging two lists of maps
I currently have two list data.
data1 = [%{id: "111", name: "name1"},
%{id: "222", name: "name2"},
%{id: "333", name: "name3"},
%{id: "444", name: "name4"}
]
data2 = [
%{
mapData: %{
"111": "data1.com",
"222": "data2.com",
"333": "data3.com",
}
},
%{
mapData: %{
"111": "data11.com",
"222": "data22.com",
},
mapData: %{
"111": "data111.com"
}
}
]
Now I want to obtain data in this format.
[
%{id: "111", name: "name1",address:"data1.com"},
%{id: "111", name: "name1",address:"data11.com"},
%{id: "111", name: "name1",address:"data111.com"},
%{id: "222", name: "name2",address:"data2.com"},
%{id: "222", name: "name22",address:"data22.com"},
%{id: "333", name: "name3",address:"data3com"},
%{id: "444", name: "name4",address:""}
]
I don’t know how to implement it now.I am a beginner and I hope everyone can help me. Thank you.
Most Liked
Eiji
Yes, as this was not mentioned originally.
No worries.
No, it’s not. As above you have added a special cases not mentioned before. Here is the current error:
** (FunctionClauseError) no function clause matching in Example.merge/3
The following arguments were given to Example.merge/3:
# 1
[]
# 2
[{"666", "testName666"}]
# 3
false
We can see that we have done with data, but still have extra names list elements. This means that we only need to modify a strict condition that finishes our recursive process i.e.
# in case no extra names we can strictly pattern match here
defp merge([], [], _last_matched), do: []
# in case we do not care about more items in `names` list
defp merge([], _names, _last_matched), do: []
Also I have simplified my example a bit:
defmodule Example do
def sample(data, names) when is_list(data) and is_list(names) do
# a list of names
# reduce it with an empty list
names
|> Enum.reduce([], fn %{mediaTailoId: ids_map, name: name}, acc ->
# take a map value under mapData key
ids_map
# change atom id to string
|> Enum.map(fn {id, _value} -> {Atom.to_string(id), name} end)
# concat the result with acc
|> then(&(acc ++ &1))
end)
# the original names are sorted partially i.e. only in specified mediaTailoId
# to fix that we simply need to sort the flat list result by id
|> Enum.sort_by(&elem(&1, 0))
|> then(&merge(data, &1))
end
# function head is required when using a default argument(s)
# in functions with multiple clasule
defp merge(data, names, last_matched \\ false)
# when done simply finish a recursive call returning an empty list as result tail
defp merge([], _names, _last_matched), do: []
# if id matches add a new map to result and continue with same data and the rest of names
defp merge([%{channelId: id} = map | _data_tail] = data, [{id, name} | names], _last_matched) do
[Map.merge(map, %{name: name}) | merge(data, names, true)]
end
# if id does not match and previous clause matched for this id
# we simply continue with rest of data and same names
defp merge([_data_head | data], names, true), do: merge(data, names, false)
# otherwise if id still does not match and previous clause do not matched for this id even once
# add a new map with an empty name and continue with rest of data and same names
# the combination of last_matched boolean argument and this clause
# is required in case we do not have any names for specific data like it is for "444" id
defp merge([map | data], names, false) do
[Map.merge(map, %{name: ""}) | merge(data, names, false)]
end
end
data1 = [
%{
channelId: "1",
# fix: describtion -> description
description: "dev-test_001",
tags: %{pipelineId: "1001", pipelineTemplate: "live-drm"}
},
%{
channelId: "2",
# fix: describtion -> description
description: "dev-test_002",
tags: %{pipelineId: "1002", pipelineTemplate: "live-drm"}
},
%{
channelId: "3",
# fix: describtion -> description
description: "dev-test_003",
tags: %{pipelineId: "1003", pipelineTemplate: "live-drm"}
},
%{
channelId: "4",
# fix: describtion -> description
description: "dev-test_004",
tags: %{pipelineId: "1004", pipelineTemplate: "live-drm"}
}
]
data2 = [
%{
mediaTailoId: %{"1": "channel1_data", "2": "channel2_dadta", "3": "channel3_dadta"},
mediaTailorRegion: "us-east-1",
name: "testName1"
},
%{
mediaTailoId: %{"1": "channel1_data", "2": "channel2_dadta"},
mediaTailorRegion: "us-east-2",
name: "testName2"
},
%{mediaTailoId: %{"1": "channel1_data"}, mediaTailorRegion: "us-east-3", name: "testName3"},
%{mediaTailoId: %{"666": "channel1_data"}, mediaTailorRegion: "us-east-3", name: "testName666"}
]
expect_data = [
%{
channelId: "1",
description: "dev-test_001",
tags: %{pipelineId: "1001", pipelineTemplate: "live-drm"},
name: "testName1"
},
%{
channelId: "1",
description: "dev-test_001",
tags: %{pipelineId: "1001", pipelineTemplate: "live-drm"},
name: "testName2"
},
%{
channelId: "1",
description: "dev-test_001",
tags: %{pipelineId: "1001", pipelineTemplate: "live-drm"},
name: "testName3"
},
%{
channelId: "2",
description: "dev-test_002",
tags: %{pipelineId: "1002", pipelineTemplate: "live-drm"},
name: "testName1"
},
%{
channelId: "2",
description: "dev-test_002",
tags: %{pipelineId: "1002", pipelineTemplate: "live-drm"},
name: "testName2"
},
%{
channelId: "3",
description: "dev-test_003",
tags: %{pipelineId: "1003", pipelineTemplate: "live-drm"},
# fix: "testName2" -> "testName1"
name: "testName1"
},
%{
channelId: "4",
description: "dev-test_004",
tags: %{pipelineId: "1004", pipelineTemplate: "live-drm"},
name: ""
}
]
iex> Example.sample(data1, data2) == expect_data
true
gregvaughn
I’m joining this thread late and to be honest, have not read all the correction details. I offer another approach with get_in that may or may not be of interest. I like these sorts of data manipulation puzzles though ![]()
iex(11)> data1
[
%{id: "111", name: "name1"},
%{id: "222", name: "name2"},
%{id: "333", name: "name3"},
%{id: "444", name: "name4"}
]
iex(12)> data2
[
%{mapData: %{"111": "data1", "222": "data2_test", "333": "data3_test"}},
%{mapData: %{"111": "data11_test", "222": "data22_test"}},
%{mapData: %{"111": "data111_test"}}
]
iex(13)> Enum.flat_map(data1, fn %{id: id} = d1 ->
...(13)> id2 = String.to_atom(id)
...(13)> addrs = data2 |> get_in([Access.all(), :mapData, id2]) |> Enum.reject(&is_nil/1)
...(13)> addrs = if addrs == [], do: [""], else: addrs
...(13)> Enum.map(addrs, &Map.put(d1, :address, &1))
...(13)> end)
[
%{address: "data1", id: "111", name: "name1"},
%{address: "data11_test", id: "111", name: "name1"},
%{address: "data111_test", id: "111", name: "name1"},
%{address: "data2_test", id: "222", name: "name2"},
%{address: "data22_test", id: "222", name: "name2"},
%{address: "data3_test", id: "333", name: "name3"},
%{address: "", id: "444", name: "name4"}
]
cloudytoday
Could you please post the sample data exactly the way you have it? From this example it’s not clear why in data2 the keys are quoted atoms ("111": ...) and not usual string keys ("111" => ...) and whether this is intentional.
Regardless, you could look for something like this:
iex(39)> for %{id: id, name: name} <- data1,
%{mapData: m2} <- data2,
is_map_key(m2, String.to_atom(id)),
do: %{id: id, name: name, address: m2[String.to_atom(id)]}
[
%{address: "data1.com", id: "111", name: "name1"},
%{address: "data111.com", id: "111", name: "name1"},
%{address: "data2.com", id: "222", name: "name2"},
%{address: "data3.com", id: "333", name: "name3"}
]
Eiji
Copying these over from the Elixir chat room.
Note: Fixed code formatting.








