需要一个快速的方法来统计和总结一遍可迭代

谁能帮我? 我正试图想出一个计算方法

>>> sum_widths = sum(col.width for col in cols if not col.hide) 

并且还要计算这笔数目中的物品数量,而不必在列上进行两次通过。

这似乎令人难以置信,但在扫描std-lib(内置函数,itertools,functools等)后,我甚至找不到一个函数来计算迭代中成员的数量。 我find了函数itertools.count ,这听起来像我想要的,但它实际上只是一个看似命名的range函数。

经过一番思考,我想出了以下内容(这很简单,除了它的晦涩之外,缺less一个库函数可能是不可避免的):

 >>> visable_col_count = sum(col is col for col in cols if not col.hide) 

但是,使用这两个函数需要迭代两次,这只是我错误的方式。

作为一种select,下面的函数做我想要的:

 >>> def count_and_sum(iter): >>> count = sum = 0 >>> for item in iter: >>> count += 1 >>> sum += item >>> return count, sum 

这个问题是它需要100倍的时间(根据timeit )作为一个生成器expression式的总和。

如果任何人都可以想出一个简单的单行程式,那么请让我知道(使用Python 3.3)。

编辑1

很多伟大的想法在这里,伙计们。 感谢所有回答。 消化所有这些答案需要花一些时间,但是我会和我一起挑选一个来检查。

编辑2

我重复了两个简单的build议( count_and_sum函数和2个独立的sum函数)的计时,发现我原来的计时结束了,可能是由于在后台运行了一个自动计划的备份过程。

我也把大部分优秀的build议都作为答案来计时,所有这些都用相同的模型。 分析这些答案对我来说已经是相当的教训了:新的用途,第一次用于countcountaccumulate 。 谢谢大家!

下面是使用我正在开发的软件进行显示的结果(来自我缓慢的上网本):

 ┌───────────────────────────────────────────────────────┐ │ Count and Sum Timing │ ├──────────────────────────┬───────────┬────────────────┤ │ Method │Time (usec)│Time (% of base)│ ├──────────────────────────┼───────────┼────────────────┤ │count_and_sum (base) │ 7.2│ 100%│ │Two sums │ 7.5│ 104%│ │deque enumerate accumulate│ 7.3│ 101%│ │max enumerate accumulate │ 7.3│ 101%│ │reduce │ 7.4│ 103%│ │count sum │ 7.3│ 101%│ └──────────────────────────┴───────────┴────────────────┘ 

(我没有把复杂和折叠的方法放在太模糊的地方,不过谢谢。)

由于在所有这些方法之间的时间差别很小,我决定使用count_and_sum函数(具有明确的for循环)作为最可读性,显式和简单的(Python Zen),它也恰好是最快的!

我希望我能接受这些惊人的答案之一是正确的,但他们都是同样好,虽然或多或less晦涩,所以我只是投了所有人,接受我自己的答案是正确的( count_and_sum函数),因为这就是我使用。

那是什么意思呢“应该有一个 – 最好只有一个 – 明显的方法去做”。

使用复数

 z = [1, 2, 4, 5, 6] y = sum(x + 1j for x in z) sum_z, count_z = y.real, int(y.imag) print sum_z, count_z 18.0 5 

我不知道速度,但这是相当的:

 >>> from itertools import accumulate >>> it = range(10) >>> max(enumerate(accumulate(it), 1)) (10, 45) 

适应帝斯曼的答案。 使用deque(... maxlen=1)来节省内存使用。

 import itertools from collections import deque deque(enumerate(itertools.accumulate(x), 1), maxlen=1) 

ipython中的时间码:

 import itertools , random from collections import deque def count_and_sum(iter): count = sum = 0 for item in iter: count += 1 sum += item return count, sum X = [random.randint(0, 10) for _ in range(10**7)] %timeit count_and_sum(X) %timeit deque(enumerate(itertools.accumulate(X), 1), maxlen=1) %timeit (max(enumerate(itertools.accumulate(X), 1))) 

结果:现在比OP的方法更快

 1 loops, best of 3: 1.08 s per loop 1 loops, best of 3: 659 ms per loop 1 loops, best of 3: 1.19 s per loop 

以下是一些可能感兴趣的时间数据:

 import timeit setup = ''' import random, functools, itertools, collections x = [random.randint(0, 10) for _ in range(10**5)] def count_and_sum(it): c, s = 0, 0 for i in it: c += 1 s += i return c, s def two_pass(it): return sum(i for i in it), sum(True for i in it) def functional(it): return functools.reduce(lambda pair, x: (pair[0]+1, pair[1]+x), it, [0, 0]) def accumulator(it): return max(enumerate(itertools.accumulate(it), 1)) def complex(it): cpx = sum(x + 1j for x in it) return cpx.real, int(cpx.imag) def dequed(it): return collections.deque(enumerate(itertools.accumulate(it), 1), maxlen=1) ''' number = 100 for stmt in ['count_and_sum(x)', 'two_pass(x)', 'functional(x)', 'accumulator(x)', 'complex(x)', 'dequed(x)']: print('{:.4}'.format(timeit.timeit(stmt=stmt, setup=setup, number=number))) 

结果:

 3.404 # OP's one-pass method 3.833 # OP's two-pass method 8.405 # Timothy Shields's fold method 3.892 # DSM's accumulate-based method 4.946 # 1_CR's complex-number method 2.002 # M4rtini's deque-based modification of DSM's method 

鉴于这些结果,我不确定OP如何看到100倍的一次通过减速。 即使数据看起来与随机整数列表完全不同,那也不应该发生。

而且,M4rtini的解决scheme看起来像是明显的赢家。


为了澄清,这些结果在CPython 3.2.3中。 为了与PyPy3进行比较,请参阅James_pic的答案 ,该答案显示了从某些方法的JIT编译中获得的一些重大收益(也在M4rtini的评论中提到)。

作为senshin回答的一个后续,值得注意的是性能差异很大程度上是由于CPython的实现中的一些怪异使得某些方法比其他方法慢(例如,CPython中的循环相对较慢)。 我认为这将是有趣的尝试完全相同的testing在PyPy(使用PyPy3 2.1testing版),具有不同的性能特点。 在PyPy中,结果是:

 0.6227 # OP's one-pass method 0.8714 # OP's two-pass method 1.033 # Timothy Shields's fold method 6.354 # DSM's accumulate-based method 1.287 # 1_CR's complex-number method 3.857 # M4rtini's deque-based modification of DSM's method 

在这种情况下,OP的单程法是最快的。 这是有道理的,因为它可以说是最简单的(至less从编译器的angular度来看),PyPy可以通过内联方法调用来消除许多开销,CPython不能。

为了比较,我的机器上的CPython 3.3.2提供了以下内容:

 1.651 # OP's one-pass method 1.825 # OP's two-pass method 3.258 # Timothy Shields's fold method 1.684 # DSM's accumulate-based method 3.072 # 1_CR's complex-number method 1.191 # M4rtini's deque-based modification of DSM's method 

你可以用类似于这个的技巧保持和数

 >>> from itertools import count >>> cnt = count() >>> sum((next(cnt), x)[1] for x in range(10) if x%2) 25 >>> next(cnt) 5 

但是使用for循环可能会更具可读性

你可以使用这个:

 from itertools import count lst = range(10) c = count(1) tot = sum(next(c) and x for x in lst if x % 2) n = next(c)-1 print(n, tot) # 5 25 

这是一种黑客,但它工作得很好。

我不知道什么是Python语法,但你可能会使用一个折叠。 像这样的东西:

 (count, total) = fold((0, 0), lambda pair, x: (pair[0] + 1, pair[1] + x)) 

这个想法是使用(0,0)的种子,然后在每一步添加1到第一个组件和当前编号到第二个组件。

为了比较,您可以按如下方式实现sum

 total = fold(0, lambda t, x: t + x) 

1_CR的复数解决scheme很可爱,但过于黑客。 它的工作原因是一个复数是一个2元组,这是元素总和。 对于numpy数组也是如此,我认为使用它们会稍微清晰些:

 import numpy as np z = [1, 2, 4, 5, 6] y = sum(np.array([x, 1]) for x in z) sum_z, count_z = y[0], y[1] print sum_z, count_z 18 5 

还有其他要考虑的事情:如果可以确定一个最小可能的计数,我们可以让高效的内置sum完成部分工作:

 from itertools import islice def count_and_sum(iterable): # insert favorite implementation here def count_and_sum_with_min_count(iterable, min_count): iterator = iter(iterable) slice_sum = sum(islice(iterator, None, min_count)) rest_count, rest_sum = count_and_sum(iterator) return min_count + rest_count, slice_sum + rest_sum 

例如,使用1000000个项目和min_count为5000000的序列的deque方法,计时结果为:

 count_and_sum: 1.03 count_and_sum_with_min_count: 0.63 

这个怎么样? 这似乎工作。

 from functools import reduce class Column: def __init__(self, width, hide): self.width = width self.hide = hide lst = [Column(10, False), Column(100, False), Column(1000, True), Column(10000, False)] print(reduce(lambda acc, col: Column(col.width + acc.width, False) if not col.hide else acc, lst, Column(0, False)).width) 

你可能只需要今天的总数和数量,但谁知道明天你会需要什么!

这是一个易于扩展的解决scheme:

 def fold_parallel(itr, **fs): res = { k: zero for k, (zero, f) in fs.items() } for x in itr: for k, (_, f) in fs.items(): res[k] = f(res[k], x) return res from operator import add print(fold_parallel([1, 2, 3], count = (0, lambda a, b: a + 1), sum = (0, add), )) # {'count': 3, 'sum': 6} 

感谢所有伟大的答案,但我决定使用我原来的count_and_sum函数,调用如下:

 >>> cc, cs = count_and_sum(c.width for c in cols if not c.hide) 

正如在我原来的问题编辑中解释说,这是最快和最可读的解决scheme。