列出运行总计的理解

时间:2010-08-08 02:19:28

标签: python list-comprehension running-total

我希望从一系列数字中获得一个总计。

出于演示目的,我首先使用range

创建一个连续的数字列表
a = range(20)

runningTotal = []
for n in range(len(a)):
    new = runningTotal[n-1] + a[n] if n > 0 else a[n]
    runningTotal.append(new)

# This one is a syntax error
# runningTotal = [a[n] for n in range(len(a)) if n == 0 else runningTotal[n-1] + a[n]]

for i in zip(a, runningTotal):
    print "{0:>3}{1:>5}".format(*i)

产量

  0    0
  1    1
  2    3
  3    6
  4   10
  5   15
  6   21
  7   28
  8   36
  9   45
 10   55
 11   66
 12   78
 13   91
 14  105
 15  120
 16  136
 17  153
 18  171
 19  190

如您所见,我在每个循环迭代中初始化一个空列表[],然后append()。是否有更优雅的方式,如列表理解?

13 个答案:

答案 0 :(得分:27)

列表理解没有好的(干净的,可移植的)方式来引用它正在构建的列表。一个好的和优雅的方法可能是在生成器中完成工作:

def running_sum(a):
  tot = 0
  for item in a:
    tot += item
    yield tot

将此作为列表而不是,当然,请使用list(running_sum(a))

答案 1 :(得分:24)

如果你可以使用numpy,它有一个名为cumsum的内置函数可以执行此操作。

import numpy
tot = numpy.cumsum(a)  # returns a numpy.ndarray
tot = list(tot)        # if you prefer a list

答案 2 :(得分:10)

这可以在Python中用2行实现。

使用默认参数消除了在外部维护辅助变量的需要,然后我们只对列表执行map

def accumulate(x, l=[0]): l[0] += x; return l[0];
map(accumulate, range(20))

答案 3 :(得分:8)

我不确定'优雅',但我认为以下内容更简单,更直观(以额外变量为代价):

a = range(20)

runningTotal = []

total = 0
for n in a:
  total += n
  runningTotal.append(total)

执行相同操作的功能方法是:

a = range(20)
runningTotal = reduce(lambda x, y: x+[x[-1]+y], a, [0])[1:]

......但是那些可读性/维护性较差等等。

@Omnifarous建议将其改进为:

a = range(20)
runningTotal = reduce(lambda l, v: (l.append(l[-1] + v) or l), a, [0])

......但我仍然发现,与我最初的建议相比,这一点不易理解。

记住Kernighan的话:“调试的难度是首先编写代码的两倍。因此,如果你尽可能巧妙地编写代码,那么根据定义,你不够聪明,无法调试它。”

答案 4 :(得分:7)

当我们取一个列表的总和时,我们指定一个累加器(memo),然后遍历列表,将二进制函数“x + y”应用于每个元素和累加器。程序上,这看起来像:

def mySum(list):
    memo = 0
    for e in list:
        memo = memo + e
    return memo

这是一种常见模式,除了获取总和之外的其他东西很有用 - 我们可以将它推广到任何二元函数,我们将其作为参数提供,并让调用者指定初始值。这为我们提供了一个称为reducefoldlinject [1] 的函数:

def myReduce(function, list, initial):
    memo = initial
    for e in list:
        memo = function(memo, e)
    return memo

def mySum(list):
    return myReduce(lambda memo, e: memo + e, list, 0)

在Python 2中,reduce是一个内置函数,但在Python 3中,它已移至functools模块:

from functools import reduce

根据我们提供的函数作为第一个参数,我们可以用reduce做各种很酷的东西。如果我们将“sum”替换为“list concatenation”,将“zero”替换为“empty list”,我们得到(浅)copy函数:

def myCopy(list):
    return reduce(lambda memo, e: memo + [e], list, [])

myCopy(range(10))
> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

如果我们将transform函数作为另一个参数添加到copy,并在连接之前应用它,我们会得到map

def myMap(transform, list):
    return reduce(lambda memo, e: memo + [transform(e)], list, [])

myMap(lambda x: x*2, range(10))
> [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

如果我们添加一个predicate函数,它将e作为参数并返回一个布尔值,并使用它来决定是否连接,我们得到filter

def myFilter(predicate, list):
    return reduce(lambda memo, e: memo + [e] if predicate(e) else memo, list, [])

myFilter(lambda x: x%2==0, range(10))
> [0, 2, 4, 6, 8]

mapfilter是编写列表推导的一种不切实际的方式 - 我们也可以说[x*2 for x in range(10)][x for x in range(10) if x%2==0]reduce没有相应的列表推导语法,因为reduce根本不需要返回列表(正如我们之前使用sum看到的那样,Python也恰好提供了内置功能)。

事实证明,对于计算运行总和,reduce的列表构建能力正是我们想要的,并且可能是解决此问题的最优雅方式,尽管它的声誉(以及{{1}作为一种非pythonic shibboleth的东西。 lambda版本在运行时留下旧值的副本称为reducereductions [1] ,它看起来像这样:< / p>

scanl

如此装备,我们现在可以定义:

def reductions(function, list, initial):
    return reduce(lambda memo, e: memo + [function(memo[-1], e)], list, [initial])

虽然在概念上很优雅,但这种精确的方法在Python的实践中表现不佳。因为Python的def running_sum(list): first, rest = list[0], list[1:] return reductions(lambda memo, e: memo + e, rest, first) running_sum(range(10)) > [0, 1, 3, 6, 10, 15, 21, 28, 36, 45] 改变了一个列表但没有返回它,我们不能在lambda中有效地使用它,而是必须使用list.append()运算符。这构造了一个全新的列表,其占用的时间与累积列表的长度成比例(即O(n)操作)。因为当我们这样做时,我们已经在+的O(n)for循环中,所以总的时间复杂度复合到O(n 2 )。

在像Ruby [2] 这样的语言中,reduce返回变异的array.push e,等价物在O(n)时间内运行:

array

相同的JavaScript [2] ,其class Array def reductions(initial, &proc) self.reduce [initial] do |memo, e| memo.push proc.call(memo.last, e) end end end def running_sum(enumerable) first, rest = enumerable.first, enumerable.drop(1) rest.reductions(first, &:+) end running_sum (0...10) > [0, 1, 3, 6, 10, 15, 21, 28, 36, 45] 返回array.push(e)(不是e),但其匿名函数允许我们包含多个语句,我们可以用来分别指定一个返回值:

array

那么,我们如何解决这个问题,同时保留function reductions(array, callback, initial) { return array.reduce(function(memo, e) { memo.push(callback(memo[memo.length - 1], e)); return memo; }, [initial]); } function runningSum(array) { var first = array[0], rest = array.slice(1); return reductions(rest, function(memo, e) { return x + y; }, first); } function range(start, end) { return(Array.apply(null, Array(end-start)).map(function(e, i) { return start + i; } } runningSum(range(0, 10)); > [0, 1, 3, 6, 10, 15, 21, 28, 36, 45] 函数的概念简单性,我们只是将reductions传递给它来创建运行和函数?让我们在程序上重写lambda x, y: x + y。我们可以修复accidentally quadratic问题,当我们处理它时,预先分配结果列表以避免堆颠簸 [3]

reductions

这对我来说是个好消息:O(n)性能,优化的程序代码隐藏在一个有意义的名称下,下次你需要编写一个累积中间值的函数时可以重复使用它一个清单。

  1. 名称def reductions(function, list, initial): result = [None] * len(list) result[0] = initial for i in range(len(list)): result[i] = function(result[i-1], list[i]) return result def running_sum(list): first, rest = list[0], list[1:] return reductions(lambda memo, e: memo + e, rest, first) running_sum(range(0,10)) > [0, 1, 3, 6, 10, 15, 21, 28, 36, 45] / reduce来自LISP传统,reductions / foldl来自ML传统,scanl来自Smalltalk传统。< / LI>
  2. Python的inject和Ruby的List都是自动调整大小的数据结构的实现,称为“动态数组”(或C ++中的Array)。 JavaScript的std::vector稍微有点巴洛克式,但如果您没有指定超出范围的索引或变异Array,则表现相同。
  3. 每次列表的长度超过2的幂时,在Python运行时中形成列表的后备存储的动态数组将自行调整大小。调整列表大小意味着在堆上分配一个新列表的新列表,将旧列表的内容复制到新列表中,并将旧列表的内存返回给系统。这是一个O(n)操作,但是因为随着列表变得越来越大,它的发生频率越来越低,所以附加到列表的时间复杂度在平均情况下达到O(1)。但是,旧列表留下的“洞”有时难以回收,具体取决于它在堆中的位置。即使使用垃圾收集和强大的内存分配器,预先分配已知大小的数组也可以为底层系统节省一些工作。在没有操作系统优势的嵌入式环境中,这种微观管理变得非常重要。

答案 5 :(得分:6)

使用itertools.accumulate()。这是一个例子:

from itertools import accumulate

a = range(20)
runningTotals = list(accumulate(a))

for i in zip(a, runningTotals):
    print "{0:>3}{1:>5}".format(*i)

这仅适用于Python 3.在Python 2上,您可以使用more-itertools包中的backport。

答案 6 :(得分:4)

我想做同样的事情来生成我可以使用bisect_left的累积频率 - 这就是我生成列表的方式;

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

答案 7 :(得分:2)

这是一个线性时间解决方案:

list(reduce(lambda (c,s), a: (chain(c,[s+a]), s+a), l,(iter([]),0))[0])

示例:

l = range(10)
list(reduce(lambda (c,s), a: (chain(c,[s+a]), s+a), l,(iter([]),0))[0])
>>> [0, 1, 3, 6, 10, 15, 21, 28, 36, 45]

简而言之,reduce会超过列表累积总和并构建一个列表。最终x[0]返回列表,x[1]将是运行总值。

答案 8 :(得分:2)

另一个单线,在线性时间和空间。

def runningSum(a):
    return reduce(lambda l, x: l.append(l[-1]+x) or l if l else [x], a, None)

我在这里强调线性空间,因为我在其他提出的答案中看到的大多数单行 - 基于模式list + [sum]或使用chain迭代器的答案 - - 与此相比,生成O(n)列表或生成器并对垃圾收集器施加压力以至于它们的性能非常差。

答案 9 :(得分:1)

我会使用协程:

def runningTotal():
    accum = 0
    yield None
    while True:
        accum += yield accum

tot = runningTotal()
next(tot)
running_total = [tot.send(i) for i in xrange(N)]

答案 10 :(得分:0)

这是低效的,因为每次从开始就这样做但可能是:

a = range(20)
runtot=[sum(a[:i+1]) for i,item in enumerate(a)]
for line in zip(a,runtot):
    print line

答案 11 :(得分:0)

你正在寻找两件事:fold(reduce)和一个有趣的函数,它保存了另一个函数的结果列表,我称之为运行。我创建了带有和没有初始参数的版本;无论哪种方式,这些都需要用初始[]来减少。

def last_or_default(list, default):
    if len(list) > 0:
        return list[-1]
    return default

def initial_or_apply(list, f, y):
    if list == []:
        return [y]
    return list + [f(list[-1], y)]

def running_initial(f, initial):
    return (lambda x, y: x + [f(last_or_default(x,initial), y)])

def running(f):
    return (lambda x, y: initial_or_apply(x, f, y))

totaler = lambda x, y: x + y
running_totaler = running(totaler)
running_running_totaler = running_initial(running_totaler, [])

data = range(0,20)
running_total = reduce(running_totaler, data, [])
running_running_total = reduce(running_running_totaler, data, [])

for i in zip(data, running_total, running_running_total):
    print "{0:>3}{1:>4}{2:>83}".format(*i)

由于+运算符,这些在真正大的列表上需要很长时间。在函数式语言中,如果正确完成,则此列表构造将为O(n)。

以下是输出的前几行:

0   0                      [0]
1   1                   [0, 1]
2   3                [0, 1, 3]
3   6             [0, 1, 3, 6]
4  10         [0, 1, 3, 6, 10]
5  15     [0, 1, 3, 6, 10, 15]
6  21 [0, 1, 3, 6, 10, 15, 21]

答案 12 :(得分:0)

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

# items = range(7)
total = 0
[(x, total := total + x) for x in items]
# [(0, 0), (1, 1), (2, 3), (3, 6), (4, 10), (5, 15), (6, 21)]

此:

  • 将变量total初始化为0,这表示运行总和
  • 对于每个项目,这两个都:
      通过分配表达式 使当前循环项(total
    • 递增total := total + x
    • ,同时返回新的total值作为生成的映射元组的一部分