Charles Okwuagwu
Charles Okwuagwu

Reputation: 10876

Generate a running-sum or row-sum in Elixir

Given:

 data = [[1,2,3, ..., n],[1,2,3, ..., n],[1,2,3, ..., n], ...] 
# List with N rows of equal length

How may we get a row sum: [3,6,9, ..., x]

It is not immediately obvious which of the Enum functions to use, or how to hold a running sum using list comprehension

Upvotes: 0

Views: 464

Answers (1)

Aleksei Matiushkin
Aleksei Matiushkin

Reputation: 121010

I would say, the most readable way would be:

data
|> Enum.zip()
|> Enum.map(fn {v1, v2} -> v1 + v2 end)    
#⇒ [2, 4, 6, ..., x]

For the case of N lists, there is a recursion to be used:

data = [[1,2,3],[1,2,3],[1,2,3]]

defmodule ListSum do
  def mapper([inner1 | [inner2 | rest]]) do
    reduced = inner1
              |> Enum.zip(inner2)
              |> Enum.map(fn {v1, v2} -> v1 + v2 end)
    mapper([reduced | rest])
  end
  def mapper([list]) when is_list(list), do: list
end

IO.inspect ListSum.mapper(data)
#⇒ [3, 6, 9]

The thing is in Erlang/Elixir the easiest approach to extend the solution to a list of inputs is to recursively simplify anything down to a case of a single argument. There are [probably] many ways to rewrite the example above to be better optimized, but I explicitly wrote it the most evident way.


The more evident (yet idiomatically wrong) way for those coming from OO background would be to zip and map tuples to lists:

data
|> Enum.zip()
|> Enum.map(fn e -> e |> Tuple.to_list() |> Enum.reduce(&Kernel.+/2) end)

Benchmarks:

defmodule ListSum do
  def mapper([inner1 | [inner2 | rest]]) do
    reduced = inner1
              |> Enum.zip(inner2)
              |> Enum.map(fn {v1, v2} -> v1 + v2 end)
    mapper([reduced | rest])
  end
  def mapper([list]) when is_list(list), do: list

  def ttler(data) do
    data
    |> Enum.zip()
    |> Enum.map(fn e -> e |> Tuple.to_list() |> Enum.sum() end)
  end
end

defmodule ListSumBench do
  use Benchfella

  @list Enum.to_list(1..1_000)
  @lists List.duplicate(@list, 1_000)

  bench "mapper" do
    ListSum.mapper @lists
  end

  bench "ttler" do
    ListSum.ttler @lists
  end
end

Results:

Compiling 2 files (.ex)
Generated ttl app
Settings:
  duration:      1.0 s

## ListSumBench
[16:49:06] 1/2: mapper
[16:49:09] 2/2: ttler

Finished in 5.56 seconds

## ListSumBench
benchma iterations   average time 
mapper          50   50194.78 µs/op
ttler           10   223662.80 µs/op

The difference between Enum.sum and Enum.reduce(&Kernel.+/2) is insignificant, but sum is a bit faster (like 3%.)

Upvotes: 3

Related Questions