Here are my few cents …
If you are familiar with operators you can write something like a && b || c
. First let’s describe the order. It’s really simple to check it:
iex> quote do
...> 1 && 2 || 3
...> end
{:||, [(…)],
[{:&&, [(…), [1, 2]}, 3]}
Ok, so &&
operator have a bigger priority and the result of it is simply passed as a first argument to ||
operator.
We can use it to write a simple condition:
iex> func = fn term ->
...> is_nil(term) && "it's nil" || "it's not nill"
...> end
iex> func.(nil)
"it's nil"
iex> func.("not nil")
"it's not nil"
What happen inside the function? We have a 3 simple steps:
- Firstly
is_nil/1
check is called
- Secondly
&&
(boolean and operator) evaluates and returns the second expression only if the first one (is_nil/1
check) evaluates to true
(i.e. truthy value). Returns the first expression otherwise (here: false
i.e. falsy value).
- Finally
||
(boolean or operator) evaluates and returns the second expression only if the first one (result of &&
operator) does not evaluate to a truthy value (here: false
). Returns the first expression (here: second expression of &&
operator) otherwise.
Great! Now let’s use it in Enum.map/2
call with a &
(capture operator):
iex> list = [:a, :b, :c, a: 2]
[:a, :b, :c, a: 2]
iex> Enum.map(list, &(is_atom(&1) && {&1, 1} || &1))
[a: 1, b: 1, c: 1, a: 2]
In that case we do not need to write 2 pattern matching expressions inside a Enum.map/2
mapper function, but that’s not everything … Your total/1
function is not really efficient. You can even use a single Enum.reduce/3
call!
defmodule Example do
def original(list) do
list
|> Enum.map(&((is_atom(&1) && {&1, 1}) || &1))
|> Enum.group_by(fn {key, _value} -> key end, fn {_key, value} -> value end)
|> Enum.map(fn {group, value_list} ->
{group, Enum.reduce(value_list, 0, fn value, acc -> acc + value end)}
end)
|> Enum.into(%{})
end
def new(list) do
Enum.reduce(list, %{}, fn element, acc ->
{key, value} = (is_atom(element) && {element, 1}) || element
Map.update(acc, key, value, &(&1 + value))
end)
end
end
iex> list = [:a, :b, :c, a: 2]
[:a, :b, :c, a: 2]
iex> Example.original(list) == Example.new(list)
true
That’s just one iteration over one list with only 4 lines of code! What happens here?
Let’s give it a check:
- First of all we are
reducing
an existing list
over an empty map
. Since map
is our desired result we can simply use the return value of Enum.reduce/3
function without any extra transformations.
- In second line of function body we determine both key and a default
or
extra value we want to add (depending if said key
is already in map
or not).
- Finally we call a
Map.update/4
function which puts a default value
if key
does not exists or adds extra value
if key
already exists into our acc
(a map
accumulator).
Helpful resources:
- Operators
- && (boolean and operator)
- || (boolean or operator)
- Kernel.SpecialForms.quote/2
- & (capture operator)
- Enum.map/2
- Enum.reduce/3
- Map.update/4