elixir-lang Finding non-duplicate elements in a list

穿精又带淫゛_ 提交于 2021-01-26 10:00:28

问题


I'm trying to find non duplicate values from a list e.g.

original list:

iex> list = [2, 3, 4, 4, 5, 6, 6, 6, 7, 8, 8, 8, 9, 9, 10, 10, 10]
[2, 3, 4, 4, 5, 6, 6, 6, 7, 8, 8, 8, 9, 9, 10, 10, 10]

iex> unique = Enum.uniq(list)
[2, 3, 4, 5, 6, 7, 8, 9, 10]

iex> nondupes = unique -- Enum.uniq(list -- unique)
[2, 3, 5, 7]

result: [2, 3, 5, 7]

I was wondering if there was a better way to achieve this in elixir/erlang


回答1:


Another method (not necessarily better) that might be faster for large data is to build a map from elements to their counts and select the ones where count is 1:

list
|> Enum.reduce(%{}, fn (el, acc) -> Dict.put(acc, el, Dict.get(acc, el, 0) + 1) end)
|> Enum.filter(fn {key, val} -> val == 1 end)
|> Enum.map(fn {key, val} -> key end)

This should have runtime O(n * log(n)) instead of the O(n ^ 2) your solution requires (the subtraction should be quadratic if the whole input is unique already).




回答2:


NOTE: With elixir 1.10, you can do this with frequencies function.

[2, 3, 4, 4, 5, 6, 6, 6, 7, 8, 8, 8, 9, 9, 10, 10, 10] 
|> Enum.frequencies 
|> Enum.map(fn {k,v} ->  if v == 1, do: k  end) 
|> Enum.reject( &is_nil/1)

OR

[2, 3, 4, 4, 5, 6, 6, 6, 7, 8, 8, 8, 9, 9, 10, 10, 10] 
|> Enum.frequencies 
|> Enum.filter(fn {_k,v} ->  v == 1  end) 
|> Enum.map( fn {k,_v} -> k  end)

With this you can leverage cozy composable pipe operator 😊




回答3:


Here's an implementation of José Valim's suggestion, although that suggestion was made before Enum.frequencies/1 was available, and I think arpit's version is simpler and cleaner.

list
|> Enum.reduce({MapSet.new(), MapSet.new()}, fn item, {seen, uniq} ->
  case MapSet.member?(seen, item) do
    true -> {seen, MapSet.delete(uniq, item)}
    false -> {MapSet.put(seen, item), MapSet.put(uniq, item)}
  end
end)
|> elem(1)
|> MapSet.to_list()



回答4:


REDO

(As Paweł pointed out, I did not answer the question, rather only addressed the uniqifying bit. So I went overboard for fun below.)

Here is a purely recursive, single-pass, matching-only method:

-module(nondupes).
-export([leave_uniques/1]).

leave_uniques(L) ->
    lists:reverse(lu(lists:sort(L))).

lu(L = [H,H|_]) -> lu(L, [], dupe);
lu([H|T])       -> lu(T, [H], solo).

lu([H,H], A, dupe)           -> A;
lu([_,H], A, dupe)           -> [H|A];
lu([H,H], A, solo)           -> A;
lu([P,H], A, solo)           -> [P,H|A];
lu([H | T = [H|_]], A, dupe) -> lu(T, A, dupe);
lu([_ | T], A, dupe)         -> lu(T, A, solo);
lu([H | T = [H|_]], A, solo) -> lu(T, A, dupe);
lu([P | T], A, solo)         -> lu(T, [P|A], solo).

If past experience is any judge, it is probably more performant on large lists than list/set subtraction -- but past experience also tells me that most of the time performance is really not much of an issue. Anyway, this was sort of entertaining.

Use:

2> nondupes:leave_uniques([2, 3, 4, 4, 5, 6, 6, 6, 7, 8, 8, 8, 9, 9, 10, 10, 10]).
[2,3,5,7]


来源:https://stackoverflow.com/questions/33567828/elixir-lang-finding-non-duplicate-elements-in-a-list

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