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