For the sake of completeness, I’d post more erlangish solution that uses bare recursion.
def merge_am(elems, totals, acc \\ []) do
elems
|> Enum.sort_by(& &1.id)
|> do_merge_am(Enum.sort_by(totals, & &1.id), acc)
|> Enum.reverse()
end
defp do_merge_am([], [], acc), do: acc
defp do_merge_am([], list, acc), do: Enum.reverse(list) ++ acc
defp do_merge_am(list, [], acc), do: Enum.reverse(list) ++ acc
defp do_merge_am([%{id: id} = he | te], [%{id: id} = ht | tt], acc),
do: do_merge_am(te, tt, [Map.merge(he, ht) | acc])
defp do_merge_am([%{id: ide} = he | te], [%{id: idt} = ht | tt], acc)
when ide < idt, do: do_merge_am(te, [ht | tt], [he | acc])
defp do_merge_am([%{id: _ide} = he | te], [%{id: _idt} = ht | tt], acc),
do: do_merge_am([he | te], tt, [ht | acc])
The idea is to walk through both lists, picking up the next closest by id
item, wherever it’s found. If both lists have it, Map.merge/2
is called (3rd clause,) otherwise the closest by id
is prepended to the result. The approach requires the lists to be sorted upfront.
It might seem a bit verbose, but it wins the benchmark battle (slightly against @7stud’s solution and drastically beating José’s one :)
defmodule Merger.Bench do
use Benchfella
@entries for i <- 1..1000, do: %{id: i, total: 10 * i}
@totals for i <- 1..1000,
do: %{id: i, name: "Name_#{i}", age: Enum.random(18..100)}
bench("7s", do: Merger.merge_7s(@entries, @totals))
bench("jv", do: Merger.merge_jv(@entries, @totals))
bench("am", do: Merger.merge_am(@entries, @totals))
end
Resulting in:
Settings:
duration: 1.0 s
## Merger.Bench
[06:27:16] 1/3: 7s
[06:27:20] 2/3: am
[06:27:22] 3/3: jv
Finished in 7.39 seconds
## Merger.Bench
ben iterations average time
am 5000 355.31 µs/op
7s 5000 511.49 µs/op
jv 100 18755.67 µs/op