How can I compare two list of maps?

Hey guys, just want a little help.
(1) How can I compare two list of maps and always supersedes list_2 over list_1 if they have the same name? and (2) return the map of list_1 if it doesn’t have a similar name?

Given:

list_1 = [%{id: 1, name: "John"}, %{id: 1, name: "Jane"}, %{school_id: 1, name: "Ryan"}, ...]
list_2 = [%{id: 2, name: "John"}, %{id: 2, name: "Jane"}, ...]

Expected result:

[
  %{school_id: 2, name: "John"},
  %{school_id: 2, name: "Jane"},
  %{school_id: 1, name: "Ryan"},
  ...
]
2 Likes
Enum.map(list_1, fn %{name: n} = m ->
  Enum.find(list_2, m, &match?(%{name: ^n}, &1))
end)
2 Likes
iex(1)> list_1 = [%{id: 1, name: "John"}, %{id: 1, name: "Jane"}, %{id: 1, name: "Ryan"}]
[%{id: 1, name: "John"}, %{id: 1, name: "Jane"}, %{id: 1, name: "Ryan"}]
iex(2)> list_2 = [%{id: 2, name: "John"}, %{id: 2, name: "Jane"}]
[%{id: 2, name: "John"}, %{id: 2, name: "Jane"}]
iex(3)> map_2 = Map.new(list_2, fn %{name: name} = item -> {name, item} end)
%{"Jane" => %{id: 2, name: "Jane"}, "John" => %{id: 2, name: "John"}}
iex(4)> result =
...(4)>   Enum.map(list_1, fn %{name: name} = item ->
...(4)>     case Map.fetch(map_2, name) do
...(4)>       {:ok, new_item} ->
...(4)>         new_item
...(4)>       _ ->
...(4)>         item
...(4)>     end
...(4)>   end)
[%{id: 2, name: "John"}, %{id: 2, name: "Jane"}, %{id: 1, name: "Ryan"}]
iex(5)> 
2 Likes

Another way:

iex(1)> list_1 = [%{id: 1, name: "John"}, %{id: 1, name: "Jane"}, %{school_id: 1, name: "Ryan"}]
[%{id: 1, name: "John"}, %{id: 1, name: "Jane"}, %{name: "Ryan", school_id: 1}]
iex(2)> list_2 = [%{id: 2, name: "John"}, %{id: 2, name: "Jane"}]
[%{id: 2, name: "John"}, %{id: 2, name: "Jane"}]
iex(3)> 
nil
iex(4)> Map.merge(Map.new(list_1, &{&1.name, &1}), Map.new(list_2, &{&1.name, &1})) |> Enum.map(&elem(&1, 1))
[%{id: 2, name: "Jane"}, %{id: 2, name: "John"}, %{name: "Ryan", school_id: 1}]

I.E.:

Map.new(list_1, &{&1.name, &1})
|> Map.merge(Map.new(list_2, &{&1.name, &1}))
|> Enum.map(&elem(&1, 1))
2 Likes
Enum.concat(list_2,list_1) 
|> Enum.sort( &(&1.name<= &2.name)) 
|> Enum.dedup_by(&(&1.name))
6 Likes

Thanks for all the answers guys. :muscle:t4:

Two other ways are using Enum.reduce/3 or a reduction function:

Enum.concat(Test.list_2(), Test.list_1())
|> Enum.reduce({[], %{}}, fn
      (%{name: name}, {_, map_matches} = full_acc) when :erlang.is_map_key(name, map_matches) ->
        full_acc
      (%{name: name} = el, {acc, map_matches}) ->
        {[el | acc], Map.put(map_matches, name, true)}
end)
|> elem(0)

Or

def build(list, acc \\ {[], %{}})
def build([], {acc, _}), do: acc
def build([%{name: name} | t], {_acc, map_matches} = full_acc) when :erlang.is_map_key(name, map_matches), do: build(t, full_acc)
def build([%{name: name} = el | t], {acc, map_matches}) do
  build(t, {[el | acc], Map.put(map_matches, name, true)})
end

#test.exs

defmodule Test do
  @list_1 Enum.reduce_while(Stream.cycle([0]), {0, []},
    fn(_n, {count, acc}) when count < 25000 ->
      {:cont, {count + 1, [%{id: count + 1, name: "a_#{count}_name"} | acc]}}
      (_, {_, acc}) -> {:halt, acc}
    end)

  @list_2 @list_1 |> List.delete_at(10) |> List.delete_at(50) |> List.delete_at(100) |> List.delete_at(500) |> List.delete_at(750)

  def list_1, do: @list_1
  def list_2, do: @list_2
  
  # reduce w/ function
  def build(list, acc \\ {[], %{}})
  def build([], {acc, _}), do: acc
  def build([%{name: name} | t], {_acc, map_matches} = full_acc) when :erlang.is_map_key(name, map_matches), do: build(t, full_acc)
  def build([%{name: name} = el | t], {acc, map_matches}) do
    build(t, {[el | acc], Map.put(map_matches, name, true)})
  end

  def test do
    test1 =
      Enum.concat(Test.list_2(), Test.list_1())
      |> Enum.reduce({[], %{}}, fn
      (%{name: name}, {_, map_matches} = full_acc) when :erlang.is_map_key(name, map_matches) -> full_acc
      (%{name: name} = el, {acc, map_matches}) ->
        {[el | acc], Map.put(map_matches, name, true)}
    end)
    |> elem(0)
    |> Enum.sort()
      
      test2 = 
        :lists.flatten([Test.list_2() | Test.list_1()])
        |> Enum.reduce({[], %{}}, fn
        (%{name: name}, {_, map_matches} = full_acc) when :erlang.is_map_key(name, map_matches)-> full_acc
        (%{name: name} = el, {acc, map_matches}) ->
          {[el | acc], Map.put(map_matches, name, true)}
      end)
      |> elem(0)
      |> Enum.sort()

        test3 =
          Map.new(Test.list_1(), &{&1.name, &1})
          |> Map.merge(Map.new(Test.list_2(), &{&1.name, &1}))
          |> Enum.map(&elem(&1, 1))
          |> Enum.sort()

        test4 = 
          Enum.concat(Test.list_2(), Test.list_1()) 
          |> Enum.sort( &(&1.name<= &2.name)) 
        |> Enum.dedup_by(&(&1.name))
        |> Enum.sort()

        test5 = 
          Enum.map(Test.list_1(), fn %{name: n} = m ->
            Enum.find(Test.list_2(), m, &match?(%{name: ^n}, &1))
          end)
          |> Enum.sort()

        test6 =
          Enum.concat(Test.list_2(), Test.list_1())
          |> Test.build()
          |> Enum.sort()

        IO.inspect(test1 == test2, label: "1 == 2")
        IO.inspect(test1 == test3, label: "1 == 3")
        IO.inspect(test1 == test4, label: "1 == 4")
        IO.inspect(test1 == test5, label: "1 == 5")
        IO.inspect(test1 == test6, label: "1 == 6")
  end
  
end
defmodule TestTest do
  Test.test()
end


Benchee.run(
  %{
    "Enum.reduce & concat" => fn ->
    Enum.concat(Test.list_2(), Test.list_1())
    |> Enum.reduce({[], %{}}, fn
      (%{name: name}, {_, map_matches} = full_acc) when :erlang.is_map_key(name, map_matches) -> full_acc
      (%{name: name} = el, {acc, map_matches}) ->
        {[el | acc], Map.put(map_matches, name, true)}
    end)
    |> elem(0)
  end,
    "Enum.reduce & flatten" => fn ->
      :lists.flatten([Test.list_2() | Test.list_1()])
      |> Enum.reduce({[], %{}}, fn
        (%{name: name}, {_, map_matches} = full_acc) when :erlang.is_map_key(name, map_matches)-> full_acc
        (%{name: name} = el, {acc, map_matches}) ->
          {[el | acc], Map.put(map_matches, name, true)}
      end)
      |> elem(0)
    end,
    "reduce function" => fn ->
      Enum.concat(Test.list_2(), Test.list_1())
      |> Test.build()
    end,
    "map_merge" => fn ->
      Map.new(Test.list_1(), &{&1.name, &1})
      |> Map.merge(Map.new(Test.list_2(), &{&1.name, &1}))
      |> Enum.map(&elem(&1, 1))
    end,
    "sort_dedup" => fn ->
      Enum.concat(Test.list_2(), Test.list_1()) 
      |> Enum.sort( &(&1.name<= &2.name)) 
      |> Enum.dedup_by(&(&1.name))
    end,
    "enum_find" => fn ->
      Enum.map(Test.list_1(), fn %{name: n} = m ->
        Enum.find(Test.list_2(), m, &match?(%{name: ^n}, &1))
      end)
    end
  },
  time: 2
)

Which gives:

1 == 2: true
1 == 3: true
1 == 4: true
1 == 5: true
1 == 6: true
Operating System: macOS"
CPU Information: Intel(R) Core(TM) i7-4750HQ CPU @ 2.00GHz
Number of Available Cores: 8
Available memory: 8 GB
Elixir 1.7.2
Erlang 21.0

Benchmark suite executing with the following configuration:
warmup: 2 s
time: 2 s
memory time: 0 μs
parallel: 1
inputs: none specified
Estimated total run time: 24 s


Benchmarking Enum.reduce & concat...
Benchmarking Enum.reduce & flatten...
Benchmarking enum_find...
Benchmarking map_merge...
Benchmarking reduce function...
Benchmarking sort_dedup...

Name                            ips        average  deviation         median         99th %
reduce function               53.90       18.55 ms     ±9.14%       19.02 ms       23.64 ms
Enum.reduce & flatten         51.34       19.48 ms     ±9.26%       19.40 ms       23.77 ms
Enum.reduce & concat          50.97       19.62 ms     ±8.30%       20.04 ms       22.77 ms
map_merge                     46.92       21.31 ms     ±7.83%       21.44 ms       25.13 ms
sort_dedup                    43.35       23.07 ms     ±6.16%       23.07 ms       26.70 ms
enum_find                    0.0740    13515.56 ms     ±0.00%    13515.56 ms    13515.56 ms

Comparison: 
reduce function               53.90
Enum.reduce & flatten         51.34 - 1.05x slower
Enum.reduce & concat          50.97 - 1.06x slower
map_merge                     46.92 - 1.15x slower
sort_dedup                    43.35 - 1.24x slower
enum_find                    0.0740 - 728.52x slower

The results change depending on the length of the lists, but the function is always a tad faster

4 Likes