【问题标题】:Elegant pythonic cumsum优雅的蟒蛇cumsum
【发布时间】:2012-02-13 10:03:22
【问题描述】:

什么是实现 cumsum 的优雅和 Pythonic 方式?
或者 - 如果已经有一种内置的方法可以做到这一点,那当然会更好......

【问题讨论】:

标签: python


【解决方案1】:

可在Numpy

>>> import numpy as np
>>> np.cumsum([1,2,3,4,5])
array([ 1,  3,  6, 10, 15])

或者从 Python 3.2 开始使用itertools.accumulate

>>> from itertools import accumulate
>>> list(accumulate([1,2,3,4,5]))
[ 1,  3,  6, 10, 15]

如果 Numpy 不是一个选项,那么生成器循环将是我能想到的最优雅的解决方案:

def cumsum(it):
    total = 0
    for x in it:
        total += x
        yield total

例如

>>> list(cumsum([1,2,3,4,5]))
>>> [1, 3, 6, 10, 15]

【讨论】:

  • 请分享每个算法的运行复杂度
【解决方案2】:

我的想法是以功能的方式使用 reduce:

from operator import iadd
reduce(lambda acc, itm: iadd(acc, [acc[-1] + itm]), [1, 2, 3, 4, 5], [0])[1:]
>>> [1, 3, 6, 10, 15]

来自 operator 模块的 iadd 具有执行就地添加并返回目标作为结果的独特属性。

如果 [1:] 副本让您畏缩,您也可以这样做:

from operator import iadd
reduce(lambda acc, itm: (iadd(acc[0], [acc[1] + itm]), acc[1] + itm),
       [1, 2, 3, 4, 5], ([], 0))[0]
>>> [1, 3, 6, 10, 15]

但我发现本地第一个示例要快得多,而且 IMO 生成器比“reduce”之类的函数式编程更 Python:

reduce(lambda acc, itm: (iadd(acc[0], [acc[1] + itm]), acc[1] + itm), values_ten, ([], 0))[0]
Average: 6.4593828736e-06
reduce(lambda acc, itm: (iadd(acc[0], [acc[1] + itm]), acc[1] + itm), values_mil, ([], 0))[0]
Average: 0.727404361961
reduce(lambda acc, itm: iadd(acc, [acc[-1] + itm]), values_ten, [0])[1:]
Average: 5.16271911336e-06
reduce(lambda acc, itm: iadd(acc, [acc[-1] + itm]), values_mil, [0])[1:]
Average: 0.524223491301
cumsum_rking(values_ten)
Average: 1.9828751369e-06
cumsum_rking(values_mil)
Average: 0.234241141632
list(cumsum_larsmans(values_ten))
Average: 2.02786211569e-06
list(cumsum_larsmans(values_mil))
Average: 0.201473119335

这是基准脚本,YMMV:

from timeit import timeit

def bmark(prog, setup, number):
    duration = timeit(prog, setup=setup, number=number)
    print prog
    print 'Average:', duration / number

values_ten = list(xrange(10))
values_mil = list(xrange(1000000))

from operator import iadd

bmark('reduce(lambda acc, itm: (iadd(acc[0], [acc[1] + itm]), acc[1] + itm), \
values_ten, ([], 0))[0]',
      setup='from __main__ import iadd, values_ten', number=1000000)
bmark('reduce(lambda acc, itm: (iadd(acc[0], [acc[1] + itm]), acc[1] + itm), \
values_mil, ([], 0))[0]',
      setup='from __main__ import iadd, values_mil', number=10)

bmark('reduce(lambda acc, itm: iadd(acc, [acc[-1] + itm]), \
values_ten, [0])[1:]',
      setup='from __main__ import iadd, values_ten', number=1000000)
bmark('reduce(lambda acc, itm: iadd(acc, [acc[-1] + itm]), \
values_mil, [0])[1:]',
      setup='from __main__ import iadd, values_mil', number=10)

def cumsum_rking(iterable):
    values = list(iterable)
    for pos in xrange(1, len(values)):
        values[pos] += values[pos - 1]
    return values

bmark('cumsum_rking(values_ten)',
      setup='from __main__ import cumsum_rking, values_ten', number=1000000)
bmark('cumsum_rking(values_mil)',
      setup='from __main__ import cumsum_rking, values_mil', number=10)

def cumsum_larsmans(iterable):
    total = 0
    for value in iterable:
        total += value
        yield total

bmark('list(cumsum_larsmans(values_ten))',
      setup='from __main__ import cumsum_larsmans, values_ten', number=1000000)
bmark('list(cumsum_larsmans(values_mil))',
      setup='from __main__ import cumsum_larsmans, values_mil', number=10)

这是我的 Python 版本字符串:

Python 2.7 (r27:82525, Jul  4 2010, 09:01:59) [MSC v.1500 32 bit (Intel)] on win32

【讨论】:

    【解决方案3】:
    a = [1, 2, 3 ,4, 5]
    
    # Using list comprehention
    cumsum = [sum(a[:i+1]) for i in range(len(a))]           # [1, 3, 6, 10, 15]
    
    # Using map()
    cumsum = map(lambda i:  sum(a[:i+1]), range(len(a)))     # [1, 3, 6, 10, 15]
    

    【讨论】:

    • 虽然这很好也很容易,但请注意,在大列表中这将非常慢! :)
    【解决方案4】:
    def cumsum(a):
         return map(lambda x: sum( a[0:x+1] ), range( 0, len(a) ))
    
    cumsum([1,2,3])
    
    > [1, 3, 6]
    

    【讨论】:

      【解决方案5】:

      就地:

      a=[1,2,3,4,5]
      def cumsum(a):
          for i in range(1,len(a)):
              a[i]+=a[i-1]
      
      cumsum(a)
      print a
      "[1, 3, 6, 10, 15]"
      

      【讨论】:

        【解决方案6】:

        Python 3.8 开始,并引入assignment expressions (PEP 572):= 运算符),我们可以在列表解析中使用和递增变量:

        total = 0
        [total := total + x for x in [1, 2, 3 ,4, 5]]
        # [1, 3, 6, 10, 15]
        

        这个:

        • 将变量total初始化为0,表示累计和
        • 对于每个项目,这两个:
          • 通过赋值表达式total与当前循环项目(total := total + x)相加
          • 同时,将x 映射到total 的新值

        【讨论】:

          【解决方案7】:

          for 循环是 Python 式的

          def cumsum(vec):
              r = [vec[0]]
              for val in vec[1:]:
                  r.append(r[-1] + val)
              return r
          

          【讨论】:

            【解决方案8】:
            a=[1,2,3,4,5]
            
            def cumsum(a):
                a=iter(a)
                cc=[next(a)]
                for i in a:
                    cc.append(cc[-1]+i)
                return cc
            
            print cumsum(a)
            "[1, 3, 6, 10, 15]"
            

            【讨论】:

              【解决方案9】:
              a=[1,2,3,4,5]
              def cumsum(a):
                  b=a[:]
                  for i in range(1,len(a)):
                      b[i]+=b[i-1]
                  return b
              
              print cumsum(a)
              "[1, 3, 6, 10, 15]"
              

              【讨论】:

                【解决方案10】:

                我已经更新了@GrantJ 的答案,并在 Jupyter 中使用 Python 3 对所有内容进行了计时。我在以下简单的accumulate 示例中添加了:

                def cumsum_acc(iterable):
                    return accumulate(iterable)
                

                时间表明这种方法是最快的:

                reduce(lambda acc, itm: (iadd(acc[0], [acc[1] + itm]), acc[1] + itm), values_ten, ([], 0))[0]
                Average: 4.18553415873987e-06
                reduce(lambda acc, itm: (iadd(acc[0], [acc[1] + itm]), acc[1] + itm), values_mil, ([], 0))[0]
                Average: 0.4559302114011018
                reduce(lambda acc, itm: iadd(acc, [acc[-1] + itm]), values_ten, [0])[1:]
                Average: 3.3726942356950078e-06
                reduce(lambda acc, itm: iadd(acc, [acc[-1] + itm]), values_mil, [0])[1:]
                Average: 0.4217967894903154
                cumsum_rking(values_ten)
                Average: 2.2734952410773416e-06
                cumsum_rking(values_mil)
                Average: 0.24106813411868303
                list(cumsum_larsmans(values_ten))
                Average: 1.5819200433296032e-06
                list(cumsum_larsmans(values_mil))
                Average: 0.17061943569953542
                list(cumsum_acc(values_ten))
                Average: 9.456979988264607e-07
                list(cumsum_acc(values_mil))
                Average: 0.11057746014014924
                

                其背后的代码(由 GrantJ 编写,针对 Python 3 进行了修改):

                from timeit import timeit
                from itertools import accumulate
                from functools import reduce
                
                def bmark(prog, setup, number):
                    duration = timeit(prog, setup=setup, number=number)
                    print(prog)
                    print('Average:', duration / number)
                
                values_ten = list(range(10))
                values_mil = list(range(1000000))
                
                from operator import iadd
                
                bmark('reduce(lambda acc, itm: (iadd(acc[0], [acc[1] + itm]), acc[1] + itm), \
                values_ten, ([], 0))[0]',
                      setup='from __main__ import iadd, reduce, values_ten', number=1000000)
                bmark('reduce(lambda acc, itm: (iadd(acc[0], [acc[1] + itm]), acc[1] + itm), \
                values_mil, ([], 0))[0]',
                      setup='from __main__ import iadd, reduce, values_mil', number=10)
                
                bmark('reduce(lambda acc, itm: iadd(acc, [acc[-1] + itm]), \
                values_ten, [0])[1:]',
                      setup='from __main__ import iadd, reduce, values_ten', number=1000000)
                bmark('reduce(lambda acc, itm: iadd(acc, [acc[-1] + itm]), \
                values_mil, [0])[1:]',
                      setup='from __main__ import iadd, reduce, values_mil', number=10)
                
                def cumsum_rking(iterable):
                    values = list(iterable)
                    for pos in range(1, len(values)):
                        values[pos] += values[pos - 1]
                    return values
                
                bmark('cumsum_rking(values_ten)',
                      setup='from __main__ import cumsum_rking, values_ten', number=1000000)
                bmark('cumsum_rking(values_mil)',
                      setup='from __main__ import cumsum_rking, values_mil', number=10)
                
                def cumsum_larsmans(iterable):
                    total = 0
                    for value in iterable:
                        total += value
                        yield total
                
                bmark('list(cumsum_larsmans(values_ten))',
                      setup='from __main__ import cumsum_larsmans, values_ten', number=1000000)
                bmark('list(cumsum_larsmans(values_mil))',
                      setup='from __main__ import cumsum_larsmans, values_mil', number=10)
                
                def cumsum_acc(iterable):
                    return accumulate(iterable)
                
                bmark('list(cumsum_acc(values_ten))',
                      setup='from __main__ import cumsum_acc, accumulate, values_ten', number=1000000)
                bmark('list(cumsum_acc(values_mil))',
                      setup='from __main__ import cumsum_acc, accumulate, values_mil', number=10)
                

                【讨论】:

                  猜你喜欢
                  • 2018-07-26
                  • 2010-10-27
                  • 2010-10-21
                  • 2016-05-28
                  • 2014-08-01
                  • 2021-06-28
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多