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.
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}
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}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With