需要一种快速计算方法并在一次传递中对可迭代求和

Don*_*ell 46 python python-3.x

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

>>> sum_widths = sum(col.width for col in cols if not col.hide)
Run Code Online (Sandbox Code Playgroud)

并且还要计算此总和中的项目数,而不必进行两次传递cols.

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

经过一番思考后我想出了以下内容(这很简单,除了它的钝性之外,缺少库函数可能是可以原因的):

>>> visable_col_count = sum(col is col for col in cols if not col.hide)
Run Code Online (Sandbox Code Playgroud)

但是,使用这两个函数需要两次遍历iterable,这只是错误的方式.

作为替代方案,以下功能可以满足我的需求:

>>> def count_and_sum(iter):
>>>     count = sum = 0
>>>     for item in iter:
>>>         count += 1
>>>         sum += item
>>>     return count, sum
Run Code Online (Sandbox Code Playgroud)

这个问题是它需要100倍(根据timeit)生成器表达形式的总和.

如果有人能想出一个我想要的简单单行程,请告诉我(使用Python 3.3).

编辑1

伙计们,这里有很多好点子.感谢所有回复的人.我需要一段时间才能消化所有这些答案,但我会尽力选择一个来检查.

编辑2

我重复了两个简单的建议(count_and_sum功能和2个独立的sum功能)的时间,并发现我的原始时间已经过时,可能是由于在后台运行的自动安排的备份过程.

我也在这里给出了大部分优秀的建议,这些建议都是相同的模型.分析这些问题的答案一直是我相当的教育:新的用途deque,enumeratereduce与第一次countaccumulate.谢谢大家!

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

?????????????????????????????????????????????????????????
?                 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%?
?????????????????????????????????????????????????????????
Run Code Online (Sandbox Code Playgroud)

(我没有把复杂和折叠方法的时间视为过于模糊,但无论如何,谢谢.)

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

我希望我能接受这些令人惊讶的答案中的一个是正确的,但它们都同样好,虽然或多或少模糊,所以我只是投票给所有人并接受我自己的答案为正确(count_and_sum功能),因为这就是我正在使用的.

什么是"应该有一个 - 最好只有一个 - 明显的方式来做它."?

iru*_*var 103

使用复数

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
Run Code Online (Sandbox Code Playgroud)

  • 我无法确定这是否是我见过的最好或最差的Python代码... (77认同)
  • 啊,但如果序列中包含复数,那该怎么办:P +1让我发笑 (18认同)
  • @Morwenn*Imaginary*,而不是*无理*. (10认同)
  • 这当然很聪明但是,按照马特的说法,我不确定我的意思是以一种好的方式还是以"你是疯狂的"方式:-) (6认同)
  • @TylerDeWitt`j`是无理数的后缀.当你计算一些实数的总和并为每个元素添加"1j"时,你正在增加复数的无理部分; 因此,您使用irraitonal部分作为计数器,而实际部分保存实数的总和. (5认同)
  • 使用复数来表示**一切**. (3认同)
  • 可以优化为`sum(map(1j .__ add __(z))`. (3认同)
  • @JBernardo,我不明白.我在列表中用浮点数尝试了它并计算了正确的总和 (2认同)

DSM*_*DSM 49

我不知道速度,但这有点漂亮:

>>> from itertools import accumulate
>>> it = range(10)
>>> max(enumerate(accumulate(it), 1))
(10, 45)
Run Code Online (Sandbox Code Playgroud)

  • @DSM正确.你介意把这个添加到答案中吗? (3认同)
  • @thefourtheye:鉴于上面在Python 3.3.1中执行的,我很确定你错了.:^) (2认同)
  • @DSM抱歉:)可以从[3.2](http://docs.python.org/3.2/library/itertools.html#itertools.accumulate)获取.+1 (2认同)

M4r*_*ini 29

适应DSM的答案.使用deque(... maxlen=1)以节省内存使用.

import itertools 
from collections import deque 
deque(enumerate(itertools.accumulate(x), 1), maxlen=1)
Run Code Online (Sandbox Code Playgroud)

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)))
Run Code Online (Sandbox Code Playgroud)

结果:现在比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
Run Code Online (Sandbox Code Playgroud)

  • 是的,我对这个相对较大的差异感到有些惊讶.我只是预感到为列表分配内存可能是一个瓶颈.对于更复杂的生成器,值的计算需要更多时间.但差别应该是微不足道的. (2认同)
  • 但是`max`没有为列表分配内存.在任何时刻,只存储两个元组,即您正在查看的元组和最后一个元组.我认为它必须只是deque实现非常快和/或元组比较缓慢. (2认同)

sen*_*hin 23

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

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)))
Run Code Online (Sandbox Code Playgroud)

结果:

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
Run Code Online (Sandbox Code Playgroud)

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

此外,M4rtini的解决方案看起来像是明显的赢家.


为了澄清,这些结果在CPython 3.2.3中.有关与PyPy3的比较,请参阅James_pic的回答,其中显示了JIT编译对某些方法的一些重大收益(M4rtini的评论中也提到了这一点).

  • @JBernardo:虽然我同意这个答案非常有帮助,但我不确定如果其他人没有写出答案,我会不会将OP的方法*与*进行比较. (2认同)

Jam*_*pic 20

作为后续senshin回答,值得注意的是,性能差异主要是由于CPython实现中的怪癖,使得某些方法比其他方法慢(例如,forCPython中的循环相对较慢).我认为在PyPy(使用PyPy3 2.1 beta)中尝试完全相同的测试会很有趣,它具有不同的性能特征.在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
Run Code Online (Sandbox Code Playgroud)

在这种情况下,OP的一次通过方法最快.这是有道理的,因为它可以说是最简单的(至少从编译器的角度来看)并且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
Run Code Online (Sandbox Code Playgroud)


Joh*_*ooy 5

你可以用与此类似的技巧来计算总和

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

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


Don*_*ell 3

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

>>> cc, cs = count_and_sum(c.width for c in cols if not c.hide) 
Run Code Online (Sandbox Code Playgroud)

正如对我最初问题的编辑中所解释的那样,这被证明是最快且最具可读性的解决方案。