# How to merge two list with maps based on particular key

For two list of maps

``````products =
[
%{id: 7, name: "A", count: 1},
%{id: 8, name: "B", count: 1},
%{id: 9, name: "C", count: 0}
]

price=
[
%{price: "\$14.95", p_id: 8},
%{price: "\$10.00", p_id: 7},
%{price: "\$29.95", p_id: 10},
%{price: "\$1.00", p_id: 9}

]
``````

How can we combine/merge the two ,for the final list to look like

``````products=
[
%{id: 7, name: "A", count: 1, price: "\$10.00"},
%{id: 8, name: "B", count: 1, price: "\$14.95"},
%{id: 9, name: "C", count: 0, price: "\$1.00"}
]
``````

??

Try Enum.map along with Enum.find

Can you please elaborate?? I am new to thisβ¦

Here is one example which takes the following steps:

1. Sort each list by its ID
2. Merge the second list into the first
``````defmodule Merge do

# Assumes the lists are sorted
def run do
products = [
%{id: 7, name: "A", count: 1},
%{id: 8, name: "B", count: 1},
%{id: 9, name: "C", count: 0}
]

price = [
%{price: "\$10.00", p_id: 7},
%{price: "\$14.95", p_id: 8},
%{price: "\$1.00", p_id: 9},
%{price: "\$29.95", p_id: 10}
]

merge_lists(products, price, &Map.merge/2)
end

def merge_lists([], _, _merger) do
[]
end

# A match so we merge the two maps and advance
# each list
def merge_lists([%{id: id} = head_a | rest_a], [%{p_id: id} = head_b | rest_b], merger) do
end

# No match but there are more items in list_b we can try
def merge_lists([%{id: id_a} | _rest_a] = list_a, [%{p_id: id_b}| rest_b], merger)
when id_a > id_b do
merge_lists(list_a, rest_b, merger)
end

# No merge possible = No matching list b
def merge_lists([%{id: id_a} = head_a | _rest_a], [%{p_id: id_b} | _rest_b], _merger)
when id_a < id_b do
raise ArgumentError, "No match for #{inspect head_a}"
end
end
``````

And running it:

``````iex> Merge.run
[
%{count: 1, id: 7, name: "A", p_id: 7, price: "\$10.00"},
%{count: 1, id: 8, name: "B", p_id: 8, price: "\$14.95"},
%{count: 0, id: 9, name: "C", p_id: 9, price: "\$1.00"}
]
``````
2 Likes

Thank youβ¦

``````Enum.map(products, fn p ->
relevant_price  = Enum.find(price, fn rp -> rp.p_id == p.id end)
p |> Map.put_new(:price, relevant_price.price)
end)
``````
1 Like

@sreyansjainβs solution is expressive and the intent is easy to understand. It has the challenge that `Enum.find/2` will be called for each entry in `products` and since `Enum.find/2` is a linear search that has a potential impact.

My solution has only pass over the lists but requires that the lists are sorted first.

Another solution would be to change the list of maps into a list for both `price` and `products`. Then cross-linking them would be quite efficient.

WIth Elixir I typically find that a process of `get the data in the right shape for processing -> process the data -> put the data in the right shape for returning` to be a strategy that holds up over time and decouples the processing stage from potential changes to data structures over time. That may only be my opinion of course.

3 Likes

@sreyansjain , @kip
I am using
`Enum.map(products, &Map.put(&1, :price, Enum.find(price, fn %{p_id: pid} -> pid == &1.id end).price))`
But it raises an error if id is not present in the list. How to use `Enum.find_value` for the same?`

``````Enum.map(products, fn p ->
relevant_price  = Enum.find(price, fn rp -> rp.p_id == p.id end)
if relevant_price do
p |> Map.put_new(:price, relevant_price.price)
else
p |> Map.put_new(:price, 0)
end
end)
``````
``````products =
[
%{id: 7, name: "A", count: 1},
%{id: 8, name: "B", count: 1},
%{id: 9, name: "C", count: 0}
]

price=
[
%{price: "\$14.95", id: 8},
%{price: "\$10.00", id: 7},
%{price: "\$29.95", id: 10},
%{price: "\$1.00", id: 9}

]

Enum.group_by(products++price, & Map.get(&1, :id))
|> Enum.map(fn {_, l} ->
Enum.concat(l)
|> Enum.into(%{})
end)
``````
1 Like