chokchit

chokchit

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"},
  ...
]

Most Liked

imatmati

imatmati

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

amnu3387

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

gregvaughn

gregvaughn

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

peerreynders

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)> 
OvermindDL1

OvermindDL1

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))

Where Next?

Popular in Questions Top

belgoros
I’m not a pro in using Regex and can’t figure out why the following behaviour happens, especially if we take into account the difference ...
New
freewebwithme
Using vs code and installed ElixirLS: support and debugger. And I got an error popped up on start up says Failed to run ‘elixir’ comma...
New
logicmason
Hi there, I'm working through my first release with elixir/phoenix. I've built a release with distillery and found that it crashes when I...
New
sergio
In Ruby, I can go: User.find_by(email: "foobar@email.com").update(email: "hello@email.com") How can I do something similar in Elixir? ...
New
dotdotdotPaul
Okay, I'm having a heck of a time trying to figure out how to best handle the validation of belongs_to associations in Ecto. I'm sure I'...
New
pmjoe
I have a relationship of love and hate with Elixir. Lots of things are just absolutely right, but there are some things that are kind of ...
New
gshaw
What is the idiomatic way of matching for not nil in Elixir? E.g., First way: defp halt_if_not_signed_in(conn, signed_in_account) when...
New
ycv005
I have followed this StackOverflow post to install the specific version of Erlang. And When I am running mix ecto.setup then getting fol...
New
chensan
I have a User schema with a :from_id field set to type :string: defmodule TweetBot.Repo.Migrations.CreateUsers do use Ecto.Migration ...
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New

Other popular topics Top

sorentwo
Hello! tl;dr Announcing Oban, an Ecto based job processing library with a focus on reliability and historical observability. After spen...
977 41022 311
New
dotdotdotPaul
Okay, I'm having a heck of a time trying to figure out how to best handle the validation of belongs_to associations in Ecto. I'm sure I'...
New
bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
ycv005
I have followed this StackOverflow post to install the specific version of Erlang. And When I am running mix ecto.setup then getting fol...
New
chrismccord
This release brings a number of exciting features, including integration with the new Phoenix LiveDashboard and Phoenix LiveView. There h...
New
mgjohns61585
Could someone help me? I'm making my first elixir program, number guessing game. I can't figure out how to convert the user's guess from ...
New
danschultzer
None of the current solutions worked well for me, so I went ahead and built a user management system from scratch. This project took far...
548 27727 240
New
aadeshere1
I have a another noob question about loop. Since elixir is immutable, while loop is not directly possible. total = 10 while total != 0 ...
New
baxterw3b
Hi guys, i’m new in the Elixir world, and i have to say, that i love it! i’m having some problem to understand anonymous functions with ...
New
joeerl
Hello again - after a longish gap I’ve decided I really must dig into Elixir and see what’s been happening here - so I have a few questio...
New

We're in Beta

About us Mission Statement