jahin07
jahin07

Reputation: 77

Elixir remove common elements from two lists

I want to remove the elements found in list b from list a. The list a is printing [1,2,3,4] after execution of this code.

defmodule Test do
def listing do
    a = [1,2,3,4]
    b = [3,4,5,6]

    Enum.each b, fn elemB ->
        a = Enum.filter(a, fn(x) -> x != elemB == true end)
        #IO.inspect a
    end 
    IO.inspect a
end
end

Test.listing()

Upvotes: 4

Views: 7597

Answers (6)

Horacio
Horacio

Reputation: 2965

I'm not an expert on elixir but you can try something like this

Enum.reject(a, fn x -> Enum.member?(b, x) end)

Upvotes: 0

Carlos Eduardo
Carlos Eduardo

Reputation: 114

In Elixir >= v1.4 there is the function List.myers_difference, so you can do

a = [1,2,3,4]
b = [3,4,5,6]
List.myers_difference(a,b) |> 
Keyword.get(:del)

[1,2]

doc to List.myers_difference

https://hexdocs.pm/elixir/List.html#myers_difference/2

Upvotes: 2

faigy langsam
faigy langsam

Reputation: 2176

Following to @Tyler answer,
You can make your code clearer using Enum.reject instead of Enum.filter:

 Enum.reject(a, fn el -> Enum.member?(b, el) end)       

This will give the same result as:

Enum.filter(a, fn el -> !Enum.member?(b, el) end)

Upvotes: 1

denis.peplin
denis.peplin

Reputation: 9841

Both ways presented in other answers at the moment (Enum.filter and --) will work well on small lists. But, lists are large, they are very inefficient.

If lists are large, better to use MapSet:

MapSet.difference(MapSet.new(a), MapSet.new(b)) |> MapSet.to_list

It spends some time to convert both lists to MapSets, and then to convert result back to list, but these operations are n log(n), while Enum.filter and substraction (--) here are quadratic.

I've prepared a gist with benchmarks.

Summary: for very short list substraction is fastest, for lists around 100 elements long Enum.filter is fastest, and for lists around 1000 elements MapSet.difference is fastest. It is hundreds times faster on lists with 100K elements.

Actually, on lists this size MapSet.difference works 0.08 seconds, Enum.filter 16 seconds, and substraction 44 seconds.

UPDATE: Dogbert asked me to also benchmark Erlang's ordsets:

:ordsets.subtract(:ordsets.from_list(a), :ordsets.from_list(b)) |> :ordsets.to_list

It works faster than MapSet, especially on mid-size lists about 1000 records long (MapSet is about 1.4 times slower).

Upvotes: 16

Mike Buhot
Mike Buhot

Reputation: 4885

You can also use the -- operator (https://hexdocs.pm/elixir/Kernel.html#--/2)

iex> [1, 2, 3] -- [1, 2]
[3]

Upvotes: 14

Tyler
Tyler

Reputation: 18177

You don't need that outer Enum.each, you can do it with a single filter by enumerating over a and checking each element to see if it is a member of b:

Enum.filter(a, fn el -> !Enum.member?(b, el) end)

Outputs:

[1, 2]

It looks like with your current solution you are trying to modify a but that won't work because Elixir is functional and the function can't have side effects; the a inside your each is not the same as the a that is the original list.

Upvotes: 4

Related Questions