【问题标题】:How to find the cumulative sum of numbers in a list?如何找到列表中数字的累积总和?
【发布时间】:2013-03-31 03:02:56
【问题描述】:
time_interval = [4, 6, 12]

我想将[4, 4+6, 4+6+12] 之类的数字相加,以得到t = [4, 10, 22] 列表。

我尝试了以下方法:

t1 = time_interval[0]
t2 = time_interval[1] + t1
t3 = time_interval[2] + t2
print(t1, t2, t3)  # -> 4 10 22

【问题讨论】:

标签: python list sum accumulate


【解决方案1】:

如果您对这样的数组进行大量数值运算,我建议您使用numpy,它带有一个累积求和函数cumsum

import numpy as np

a = [4,6,12]

np.cumsum(a)
#array([4, 10, 22])

对于这种事情,Numpy 通常比纯 python 更快,请参阅与@Ashwini's accumu 的比较:

In [136]: timeit list(accumu(range(1000)))
10000 loops, best of 3: 161 us per loop

In [137]: timeit list(accumu(xrange(1000)))
10000 loops, best of 3: 147 us per loop

In [138]: timeit np.cumsum(np.arange(1000))
100000 loops, best of 3: 10.1 us per loop

当然,如果它是您将使用 numpy 的唯一地方,则可能不值得依赖它。

【讨论】:

  • 这应该有一个以列表开头的np.cumsun case,以考虑转换时间。
  • 好点@hpaulj,对于那些从list开始(或目标)的人,我不推荐numpy
  • 我认为 numpy 不是最快的stackoverflow.com/questions/15889131/…
  • 同意,如上所述。避免像您和@hpaulj 这样的反应是我试图在我的答案的第一行和最后一行限制其范围的原因:-/
  • @alex: Using timeit, "如果没有给出-n,则通过尝试连续的10次方来计算合适的循环次数,直到总时间至少为0.2秒." 如果您希望它有所作为,您可以提供-n 1000 以使它们都等效。
【解决方案2】:

在 Python 2 中,您可以像这样定义自己的生成器函数:

def accumu(lis):
    total = 0
    for x in lis:
        total += x
        yield total

In [4]: list(accumu([4,6,12]))
Out[4]: [4, 10, 22]

在 Python 3.2+ 中你可以使用itertools.accumulate():

In [1]: lis = [4,6,12]

In [2]: from itertools import accumulate

In [3]: list(accumulate(lis))
Out[3]: [4, 10, 22]

【讨论】:

  • PEP 572 -- Assignment Expressions(Python 3.8 预期)展示了一个有趣的替代方案total = 0; partial_sums = [total := total + v for v in values]。我仍然希望accumulate 更快。
  • @StevenRumbalski 伙计,我个人认为这是有史以来最糟糕的 PEP。 Bad enough...
【解决方案3】:

我使用 Python 3.4 对前两个答案进行了基准测试,我发现 itertools.accumulate 在许多情况下都比 numpy.cumsum 快,而且通常要快得多。但是,正如您从 cmets 中看到的那样,情况可能并非总是如此,并且很难详尽地探索所有选项。 (如果您有更多感兴趣的基准测试结果,请随时添加评论或编辑这篇文章。)

一些时间...

对于短名单accumulate 大约快 4 倍:

from timeit import timeit

def sum1(l):
    from itertools import accumulate
    return list(accumulate(l))

def sum2(l):
    from numpy import cumsum
    return list(cumsum(l))

l = [1, 2, 3, 4, 5]

timeit(lambda: sum1(l), number=100000)
# 0.4243644131347537
timeit(lambda: sum2(l), number=100000)
# 1.7077815784141421

对于更长的列表,accumulate 大约快 3 倍:

l = [1, 2, 3, 4, 5]*1000
timeit(lambda: sum1(l), number=100000)
# 19.174508565105498
timeit(lambda: sum2(l), number=100000)
# 61.871223849244416

如果numpy array 没有转换为listaccumulate 仍然快大约 2 倍:

from timeit import timeit

def sum1(l):
    from itertools import accumulate
    return list(accumulate(l))

def sum2(l):
    from numpy import cumsum
    return cumsum(l)

l = [1, 2, 3, 4, 5]*1000

print(timeit(lambda: sum1(l), number=100000))
# 19.18597290944308
print(timeit(lambda: sum2(l), number=100000))
# 37.759664884768426

如果您将导入放在两个函数之外并且仍然返回 numpy arrayaccumulate 仍然快近 2 倍:

from timeit import timeit
from itertools import accumulate
from numpy import cumsum

def sum1(l):
    return list(accumulate(l))

def sum2(l):
    return cumsum(l)

l = [1, 2, 3, 4, 5]*1000

timeit(lambda: sum1(l), number=100000)
# 19.042188624851406
timeit(lambda: sum2(l), number=100000)
# 35.17324400227517

【讨论】:

  • 您不会期望飞机比火车更快地穿越城镇,尤其是在购票和安检方面。同样,您不会使用 numpy 来处理包含五个项目的 list,特别是如果您不愿意接受 array 作为回报。如果有问题的列表真的很短,那么它们的运行时间将是无关紧要的——依赖性和易读性肯定会占主导地位。但是广泛使用具有显着长度的统一数值数据类型的list 是愚蠢的;为此,一个 numpy array 是合适的,而且通常更快。
  • @askewchan 好吧,我不只是为短列表找到它,而且 OP 的问题要求将列表作为输出而不是 numpy 数组。也许您可以编辑您的答案,以便更清楚地了解每次使用何时合适:)
  • @askewchan 事实上,我已经通过更详细的比较编辑了我的答案。在任何情况下,我是否发现 numpy 更快,除非我忽略了什么?
  • 哦,天哪,确实是 :) 我不会说您忽略了某些东西,但是如果不考虑您的输入和输出,很难单独进行比较。 sum2 函数中的大部分时间可能是将l 转换为数组。分别尝试计时a = np.array(l)np.cumsum(a)。然后尝试a = np.tile(np.arange(1, 6), 1000)l = [1,2,3,4,5]*1000。在执行其他数值过程的程序中(例如首先创建或加载l),您的工作数据可能已经在一个数组中,并且创建将是一个恒定的成本。
  • @askewchan 我和你有同样的想法,因此我做了 a = np.array(l) 的时间。对于没有转换为列表的 sum2,并且使用 numpy 数组作为输入,sum2 的速度要快 5 倍,感谢我的计算机中的 sum1,以防列表/数组很长。
【解决方案4】:

看:

a = [4, 6, 12]
reduce(lambda c, x: c + [c[-1] + x], a, [0])[1:]

将输出(如预期):

[4, 10, 22]

【讨论】:

  • 没有效率。一遍又一遍地执行c + [c[-1] + x] 的总费用加起来就是输入长度的总运行时间二次方。
  • reduce 适用于一次性累积和,但如果您对 cumsum 函数进行大量调用,生成器将有助于“预处理”您的累积和值并在 O 中访问它们(1) 对于每个后续调用。
【解决方案5】:

试试 itertools.accumulate()函数。

import itertools  

list(itertools.accumulate([1,2,3,4,5]))
# [1, 3, 6, 10, 15]

【讨论】:

  • 您不需要传递operator.add,因为默认操作无论如何都是添加。
【解决方案6】:

赋值表达式from PEP 572(Python 3.8 中的新功能)提供了另一种解决此问题的方法:

time_interval = [4, 6, 12]

total_time = 0
cum_time = [total_time := total_time + t for t in time_interval]

【讨论】:

    【解决方案7】:

    您可以使用简单的for 循环以线性时间计算累积和列表:

    def csum(lst):
        s = lst.copy()
        for i in range(1, len(s)):
            s[i] += s[i-1]
        return s
    
    time_interval = [4, 6, 12]
    print(csum(time_interval))  # [4, 10, 22]
    

    标准库的itertools.accumulate 可能是更快的替代方案(因为它是用 C 实现的):

    from itertools import accumulate
    time_interval = [4, 6, 12]
    print(list(accumulate(time_interval)))  # [4, 10, 22]
    

    【讨论】:

      【解决方案8】:

      由于python 3.8可以使用Assignment expressions,所以这样的事情变得更容易实现

      nums = list(range(1, 10))
      print(f'array: {nums}')
      
      v = 0
      cumsum = [v := v + n for n in nums]
      print(f'cumsum: {cumsum}')
      

      生产

      array: [1, 2, 3, 4, 5, 6, 7, 8, 9]
      cumsum: [1, 3, 6, 10, 15, 21, 28, 36, 45]
      

      同样的技术可以用于求积、均值等。

      p = 1
      cumprod = [p := p * n for n in nums]
      print(f'cumprod: {cumprod}')
      
      s = 0
      c = 0
      cumavg = [(s := s + n) / (c := c + 1) for n in nums]
      print(f'cumavg: {cumavg}')
      

      结果

      cumprod: [1, 2, 6, 24, 120, 720, 5040, 40320, 362880]
      cumavg: [1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0]
      

      【讨论】:

        【解决方案9】:

        如果您想要一种没有 numpy 在 2.7 中工作的 Pythonic 方式,这将是我的方式

        l = [1,2,3,4]
        _d={-1:0}
        cumsum=[_d.setdefault(idx, _d[idx-1]+item) for idx,item in enumerate(l)]
        

        现在让我们尝试一下并针对所有其他实现进行测试

        import timeit, sys
        L=list(range(10000))
        if sys.version_info >= (3, 0):
            reduce = functools.reduce
            xrange = range
        
        
        def sum1(l):
            cumsum=[]
            total = 0
            for v in l:
                total += v
                cumsum.append(total)
            return cumsum
        
        
        def sum2(l):
            import numpy as np
            return list(np.cumsum(l))
        
        def sum3(l):
            return [sum(l[:i+1]) for i in xrange(len(l))]
        
        def sum4(l):
            return reduce(lambda c, x: c + [c[-1] + x], l, [0])[1:]
        
        def this_implementation(l):
            _d={-1:0}
            return [_d.setdefault(idx, _d[idx-1]+item) for idx,item in enumerate(l)]
        
        
        # sanity check
        sum1(L)==sum2(L)==sum3(L)==sum4(L)==this_implementation(L)
        >>> True    
        
        # PERFORMANCE TEST
        timeit.timeit('sum1(L)','from __main__ import sum1,sum2,sum3,sum4,this_implementation,L', number=100)/100.
        >>> 0.001018061637878418
        
        timeit.timeit('sum2(L)','from __main__ import sum1,sum2,sum3,sum4,this_implementation,L', number=100)/100.
        >>> 0.000829620361328125
        
        timeit.timeit('sum3(L)','from __main__ import sum1,sum2,sum3,sum4,this_implementation,L', number=100)/100.
        >>> 0.4606760001182556 
        
        timeit.timeit('sum4(L)','from __main__ import sum1,sum2,sum3,sum4,this_implementation,L', number=100)/100.
        >>> 0.18932826995849608
        
        timeit.timeit('this_implementation(L)','from __main__ import sum1,sum2,sum3,sum4,this_implementation,L', number=100)/100.
        >>> 0.002348129749298096
        

        【讨论】:

          【解决方案10】:

          根据列表的长度和性能,可能会有很多答案。我可以不考虑性能的一种非常简单的方法是这样的:

          a = [1, 2, 3, 4]
          a = [sum(a[0:x]) for x in range(1, len(a)+1)]
          print(a)
          

          [1, 3, 6, 10]

          这是通过使用列表推导,这可能工作得很好,只是在这里我在子数组上添加了很多次,你可以即兴创作并让它变得简单!

          为你的努力干杯!

          【讨论】:

          • 这种方法是O(n²),所以它只适用于小列表。
          【解决方案11】:
          values = [4, 6, 12]
          total  = 0
          sums   = []
          
          for v in values:
            total = total + v
            sums.append(total)
          
          print 'Values: ', values
          print 'Sums:   ', sums
          

          运行此代码给出

          Values: [4, 6, 12]
          Sums:   [4, 10, 22]
          

          【讨论】:

            【解决方案12】:

            首先,您需要一个正在运行的子序列列表:

            subseqs = (seq[:i] for i in range(1, len(seq)+1))
            

            然后你只需在每个子序列上调用sum

            sums = [sum(subseq) for subseq in subseqs]
            

            (这不是最有效的方法,因为您要重复添加所有前缀。但这对于大多数用例来说可能无关紧要,如果您没有前缀则更容易理解想想运行的总数。)

            如果您使用的是 Python 3.2 或更新版本,您可以使用 itertools.accumulate 为您完成:

            sums = itertools.accumulate(seq)
            

            如果您使用的是 3.1 或更早版本,则可以直接从文档中复制“等效于”源代码(对于 2.5 和更早版本,将 next(it) 更改为 it.next() 除外)。

            【讨论】:

            • 这在二次时间内运行(也许这对 OP 来说并不重要,但值得一提)。
            • 首先,当N=3时,谁在乎二次时间?而且我不认为它过于复杂。这是两个非常简单的步骤,每个步骤都将一个迭代器转换为另一个,直接翻译英文描述。 (事实上​​,他使用了一种不常见的定义系列的方式,其中不计算 0 长度前缀,这确实使它变得更加复杂……但这是问题所固有的,我认为最好把它放在range 而不是通过在最后执行 [1:] 来绕过它,或者忽略它。)
            • 大概 OP 的实际问题不是得到 [4,6,12] 的部分总和,因为正如他在问题中所写,他已经知道那是什么!
            • @ChrisTaylor:他明确表示他已经知道如何写这个,但想要“一种更简单的写法”。
            【解决方案13】:

            试试这个:

            result = []
            acc = 0
            for i in time_interval:
                acc += i
                result.append(acc)
            

            【讨论】:

              【解决方案14】:

              在 Python3 中,查找 ith 元素所在的列表的累积和 是原始列表中前 i+1 个元素的总和,您可以这样做:

              a = [4 , 6 , 12]
              b = []
              for i in range(0,len(a)):
                  b.append(sum(a[:i+1]))
              print(b)
              

              或者你可以使用列表推导:

              b = [sum(a[:x+1]) for x in range(0,len(a))]
              

              输出

              [4,10,22]
              

              【讨论】:

              • 这看起来不错,但可以删除文档的链接,否则我无法投票。
              【解决方案15】:
              In [42]: a = [4, 6, 12]
              
              In [43]: [sum(a[:i+1]) for i in xrange(len(a))]
              Out[43]: [4, 10, 22]
              

              slighlty比@Ashwini上面的生成器方法快

              In [48]: %timeit list(accumu([4,6,12]))
                100000 loops, best of 3: 2.63 us per loop
              
              In [49]: %timeit [sum(a[:i+1]) for i in xrange(len(a))]
                100000 loops, best of 3: 2.46 us per loop
              

              对于较大的列表,生成器肯定是要走的路。 . .

              In [50]: a = range(1000)
              
              In [51]: %timeit [sum(a[:i+1]) for i in xrange(len(a))]
                100 loops, best of 3: 6.04 ms per loop
              
              In [52]: %timeit list(accumu(a))
                10000 loops, best of 3: 162 us per loop
              

              【讨论】:

              • 您只需要 3 项列表,请尝试 10^4 项。
              • 没错,对于较大的列表,生成器要快得多!
              【解决方案16】:
              l = [1,-1,3]
              cum_list = l
              
              def sum_list(input_list):
                  index = 1
                  for i in input_list[1:]:
                      cum_list[index] = i + input_list[index-1]
                      index = index + 1 
                  return cum_list
              
              print(sum_list(l))
              

              【讨论】:

                【解决方案17】:

                有点老套,但似乎有效:

                def cumulative_sum(l):
                  y = [0]
                  def inc(n):
                    y[0] += n
                    return y[0]
                  return [inc(x) for x in l]
                

                我确实认为内部函数可以修改在外部词法作用域中声明的y,但这不起作用,所以我们用结构修改来玩一些讨厌的黑客攻击。使用生成器可能更优雅。

                【讨论】:

                  【解决方案18】:

                  无需使用 Numpy,您可以直接在数组上循环并沿途累加总和。例如:

                  a=range(10)
                  i=1
                  while((i>0) & (i<10)):
                      a[i]=a[i-1]+a[i]
                      i=i+1
                  print a
                  

                  结果:

                  [0, 1, 3, 6, 10, 15, 21, 28, 36, 45]
                  

                  【讨论】:

                    【解决方案19】:

                    用于累积和的纯python oneliner:

                    cumsum = lambda X: X[:1] + cumsum([X[0]+X[1]] + X[2:]) if X[1:] else X
                    

                    这是一个受recursive cumulative sums 启发的递归版本。一些解释:

                    1. 第一个词 X[:1] 是一个包含前一个元素的列表,与 [X[0]] 几乎相同(会抱怨空列表)。
                    2. 第二项中的递归cumsum 调用处理当前元素[1] 以及长度将减一的剩余列表。
                    3. if X[1:]if len(X)&gt;1 更短。

                    测试:

                    cumsum([4,6,12])
                    #[4, 10, 22]
                    
                    cumsum([])
                    #[]
                    

                    对于累积产品也是类似的:

                    cumprod = lambda X: X[:1] + cumprod([X[0]*X[1]] + X[2:]) if X[1:] else X
                    

                    测试:

                    cumprod([4,6,12])
                    #[4, 24, 288]
                    

                    【讨论】:

                      【解决方案20】:

                      这是另一个有趣的解决方案。这利用了理解的locals() dict,即在列表理解范围内生成的局部变量:

                      >>> [locals().setdefault(i, (elem + locals().get(i-1, 0))) for i, elem 
                           in enumerate(time_interval)]
                      [4, 10, 22]
                      

                      locals() 在每次迭代中查找的内容如下:

                      >>> [[locals().setdefault(i, (elem + locals().get(i-1, 0))), locals().copy()][1] 
                           for i, elem in enumerate(time_interval)]
                      [{'.0': <enumerate at 0x21f21f7fc80>, 'i': 0, 'elem': 4, 0: 4},
                       {'.0': <enumerate at 0x21f21f7fc80>, 'i': 1, 'elem': 6, 0: 4, 1: 10},
                       {'.0': <enumerate at 0x21f21f7fc80>, 'i': 2, 'elem': 12, 0: 4, 1: 10, 2: 22}]
                      

                      小型列表的性能并不差:

                      >>> %timeit list(accumulate([4, 6, 12]))
                      387 ns ± 7.53 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
                      
                      >>> %timeit np.cumsum([4, 6, 12])
                      5.31 µs ± 67.8 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
                      
                      >>> %timeit [locals().setdefault(i, (e + locals().get(i-1,0))) for i,e in enumerate(time_interval)]
                      1.57 µs ± 12 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
                      

                      对于较大的列表,显然是持平的。

                      >>> l = list(range(1_000_000))
                      >>> %timeit list(accumulate(l))
                      95.1 ms ± 5.22 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
                      
                      >>> %timeit np.cumsum(l)
                      79.3 ms ± 1.07 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
                      
                      >>> %timeit np.cumsum(l).tolist()
                      120 ms ± 1.23 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
                      
                      >>> %timeit [locals().setdefault(i, (e + locals().get(i-1, 0))) for i, e in enumerate(l)]
                      660 ms ± 5.14 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
                      

                      虽然方法丑陋且不实用,但确实很有趣。

                      【讨论】:

                        【解决方案21】:
                        lst = [4, 6, 12]
                        
                        [sum(lst[:i+1]) for i in xrange(len(lst))]
                        

                        如果您正在寻找更有效的解决方案(更大的列表?),生成器可能是一个不错的选择(或者如果您真的关心性能,请使用 numpy)。

                        def gen(lst):
                            acu = 0
                            for num in lst:
                                yield num + acu
                                acu += num
                        
                        print list(gen([4, 6, 12]))
                        

                        【讨论】:

                          【解决方案22】:

                          这将是 Haskell 风格:

                          def wrand(vtlg):
                          
                              def helpf(lalt,lneu): 
                          
                                  if not lalt==[]:
                                      return helpf(lalt[1::],[lalt[0]+lneu[0]]+lneu)
                                  else:
                                      lneu.reverse()
                                      return lneu[1:]        
                          
                              return helpf(vtlg,[0])
                          

                          【讨论】:

                            猜你喜欢
                            • 1970-01-01
                            • 2018-07-02
                            • 1970-01-01
                            • 2016-12-27
                            • 2017-10-05
                            • 2021-07-18
                            • 1970-01-01
                            • 1970-01-01
                            • 2019-02-15
                            相关资源
                            最近更新 更多