Elixir remove common elements from two lists

人走茶凉 提交于 2020-01-15 06:44:09

问题


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

回答1:


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.




回答2:


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

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



回答3:


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




回答4:


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)



回答5:


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




回答6:


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

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


来源:https://stackoverflow.com/questions/46614957/elixir-remove-common-elements-from-two-lists

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!