Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Elixir: Reduce list to map counting occurrences (pattern match a map key on another argument)

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.

like image 688
Vivian Avatar asked Mar 24 '17 19:03

Vivian


2 Answers

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}
like image 163
Dogbert Avatar answered Oct 15 '22 06:10

Dogbert


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}
like image 1
Tiago Ávila Avatar answered Oct 15 '22 07:10

Tiago Ávila