在Elixir中生成一个运行总和或行和

时间:2017-07-14 12:58:51

标签: elixir list-comprehension

假设:

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

我们如何获得行总和:[3,6,9, ..., x]

使用列表理解

1 个答案:

答案 0 :(得分:3)

我想说,最可读的方式是:

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

对于N列表的情况,可以使用递归:

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]

事情是在Erlang / Elixir中,将解决方案扩展到输入列表的最简单方法是递归地将任何内容简化为单个参数的情况。 [可能]有很多方法可以重写上面的例子以便更好地进行优化,但我明确地以最明显的方式编写了它。

对于来自OO背景的人来说,更明显(但却是惯用错误的)方式是zipmap元组列表:

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

基准:

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

结果:

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

Enum.sumEnum.reduce(&Kernel.+/2)之间的差异无关紧要,但sum稍微快一点(例如3%)。