【发布时间】:2017-12-19 14:16:48
【问题描述】:
给定:
data = [[1,2,3, ..., n],[1,2,3, ..., n],[1,2,3, ..., n], ...]
# List with N rows of equal length
我们如何获得行总和:[3,6,9, ..., x]
目前尚不清楚使用哪个Enum 函数,或者如何使用列表推导来保持运行总和
【问题讨论】:
给定:
data = [[1,2,3, ..., n],[1,2,3, ..., n],[1,2,3, ..., n], ...]
# List with N rows of equal length
我们如何获得行总和:[3,6,9, ..., x]
目前尚不清楚使用哪个Enum 函数,或者如何使用列表推导来保持运行总和
【问题讨论】:
我想说,最易读的方式是:
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 背景的人来说,更明显(但惯用错误)的方法是使用 zip 和 map 元组到列表:
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.sum 和 Enum.reduce(&Kernel.+/2) 之间的差异微不足道,但 sum 快一点(比如 3%。)
【讨论】:
Enum.zip/2。现在我们有一个元组列表,因此是Enum.map/2。可能如果你说 what 是为了理解为什么会这样,我可以更好地表达 why。
|> Enum.zip |> Enum.map(fn t -> t |> Tuple.to_list |> Enum.sum end)?
Tuple.to_list/1快4.5倍。