4
\$\begingroup\$

I just started with Elixir and this is what I have now. Is there an easier or better way to write this?

defmodule GroupBySum do
  def sum(col), do: sum(%{}, col)
  defp sum(r, [h|t]) do
    {k, v} = h
    r = r |> Dict.update(k, v, fn(val) -> val + v end)
    sum(r, t)
  end
  defp sum(r, []), do: r
end

list = [a: 1, b: 2, c: 3, a: 2, b: 1, c: 0]
IO.inspect GroupBySum.sum(list) #%{a: 3, b: 3, c: 3}
\$\endgroup\$
  • 1
    \$\begingroup\$ Enum.reduce/3 is a more general wrapper for the recursion pattern you've implemented: elixir-lang.org/docs/v1.1/elixir/Enumerable.html#reduce/3 Also it might be more idiomatic to avoid needing the h variable by moving the {k, v} destructuring right into the parameters. \$\endgroup\$ – alxndr Sep 29 '15 at 17:06
5
\$\begingroup\$

It can be done several ways. Instead of explicitly using recursion we can, as @alxndr suggested, make use of the recursion capabilities of the Enum module. I am going to use Enum.map_reduce simply because I find it useful when troubleshooting to key the items and the accumulator in that form.

In general the solution is the same as yours.

  def sum(list) do
    list |> Enum.map_reduce([],
      fn({key, val}, acc) ->
        {{key, val}, Keyword.update(acc, key, val, &(&1 + val))}
      end
    ) |> elem(1)
  end

The anonmous function in map_reduce accepts and item and an accumulator. Since the item is a keyword list, it has the form {key,val} so I match that in the function definition. The return from this function must be a tuple with the accumulator as the second value. For testing purposes I returned the same {key, val} as the first item. The Keyword.update function is the accumulator. A form of this same routine that helps the intermediate values be visible is:

def sum(list) do
  list |> Enum.map_reduce([],
    fn({key, val}, acc) ->
      x = Keyword.update(acc, key, val, &(&1 + val))}
      IO.inspect(x)
      {{key, val}, x}
    end
  ) |> elem(1)
end

In the case of the input you provided, the IO.inspect helps me understand how the routine collected the desired result, as seen below.

[a: 1]
[a: 1, b: 2]
[a: 1, b: 2, c: 3]
[a: 3, b: 2, c: 3]
[a: 3, b: 3, c: 3]
[a: 3, b: 3, c: 3]
[a: 3, b: 3, c: 3]

I found this a fun exercise -- thanks for posting.

\$\endgroup\$
  • 1
    \$\begingroup\$ Welcome to CodeReview :) A fine and in-depth first answer, I hope you'll stick around! \$\endgroup\$ – Caridorc Dec 20 '15 at 11:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.