优雅的Python累积和

22 投票
10 回答
15158 浏览
提问于 2025-04-17 12:57

有没有一种优雅又符合Python风格的方法来实现累加和(cumsum)呢?
另外,如果Python里已经有现成的方法可以做到这一点,那当然更好了……

10 个回答

4
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]

当然可以!请把你想要翻译的内容发给我,我会帮你用简单易懂的语言解释清楚。

6

我的想法是用一种函数式的方法来使用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]

但我发现第一个例子在本地运行时要快得多,而且在我看来,生成器比像'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

这是我的基准测试脚本,结果可能因人而异:

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
48

这个功能在 Numpy 中可以找到:

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

或者可以使用 itertools.accumulate,这个从 Python 3.2 开始就可以用了:

>>> 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]

撰写回答