【问题标题】:Generate a running-sum or row-sum in Elixir在 Elixir 中生成运行和或行和
【发布时间】: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 函数,或者如何使用列表推导来保持运行总和

【问题讨论】:

    标签: elixir list-comprehension


    【解决方案1】:

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

    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%。)

    【讨论】:

    • 问题是:复制和重用您的代码很容易......但理解为什么它有效,或者为什么这是显而易见的方法并不那么清楚
    • 我试图写一个描述性的“为什么”,但我有点想不通,这里有什么合适的措辞。我们有两个长度相同的列表,因此我想到了Enum.zip/2。现在我们有一个元组列表,因此是Enum.map/2。可能如果你说 what 是为了理解为什么会这样,我可以更好地表达 why
    • 不,但我会更新 N 个列表情况的答案。
    • 为什么不对 N 个列表执行此操作:|> Enum.zip |> Enum.map(fn t -> t |> Tuple.to_list |> Enum.sum end)
    • @Dogbert 根据基准,递归比Tuple.to_list/1快4.5倍。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-01-05
    • 1970-01-01
    • 1970-01-01
    • 2012-10-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多