问题
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