Reputation: 1639
I have a list which may contain duplicates. I want to count how many instances there are of each item in the list. My plan was:
list
|> Enum.reduce(%{}, fn
item, %{item => count}=acc -> %{acc | item => count + 1}
item, acc -> Map.put(acc, item, 1)
end)
However, this fails to compile with the error illegal use of variable item inside map key match, maps can only match on existing variable by using ^item
.
So I changed the first pattern to item, %{^item => count}=acc
. At that point, the error became unbound variable ^item
.
I'm not sure what to do here. I know it's possible to pattern match one argument based on another (as in fn a, a -> true
for one head of a comparison function), but apparently not in this case. I tried doing it with guards but Map.has_key?/2
can't be put in guards. I've found lots of questions here about matching on maps in general, but not about doing so when the value to match on comes from another argument.
Upvotes: 10
Views: 5583
Reputation: 2867
There's a function named frequencies in the Enum module that does exactly what you need.
iex(1)> [1, 2, :a, 2, :a, :b, :a] |> Enum.frequencies()
%{1 => 1, 2 => 2, :a => 3, :b => 1}
Upvotes: 2
Reputation: 222128
Modifying a value for a key in a Map and inserting if it doesn't already exist is exactly what Map.update/4
does. To calculate frequencies, the default would be 1
and the update fn would just add 1 to the value (&(&1 + 1)
):
iex(1)> [1, 2, :a, 2, :a, :b, :a] |>
...(1)> Enum.reduce(%{}, fn x, acc -> Map.update(acc, x, 1, &(&1 + 1)) end)
%{1 => 1, 2 => 2, :a => 3, :b => 1}
Upvotes: 22
Reputation: 1639
Well, found this while writing the question. Figured I'd share it, but if someone else has a cleaner solution they're welcome to it:
The best solution I've found is to... sort of internally curry one of the arguments, so that it's bound, purely for syntactic purposes.
list
|> Enum.reduce(%{}, fn item, acc ->
f = fn %{item => count}=acc -> %{acc | item => count + 1}
acc when is_map(acc) -> Map.put(acc, item, 1)
end
f.(acc)
end)
Upvotes: 0