Python:如何有效地计算带条件的和?

Python:如何有效地计算带条件的和?,python,performance,loops,iterator,Python,Performance,Loops,Iterator,编辑:我正在处理一个性能敏感的案例,需要使用用户定义的检查点计算数据的总和或最大值。请参阅演示代码: from itertools import izip timestamp=[1,2,3,4,...]#len(timestamp)=N checkpoints=[1,3,5,7,..]#user defined data=([1,1,1,1,...], [2,2,2,2,...], ...)#len(data)=M,len(data[any])=N processtype

编辑:我正在处理一个性能敏感的案例,需要使用用户定义的检查点计算数据的总和或最大值。请参阅演示代码:

from itertools import izip
timestamp=[1,2,3,4,...]#len(timestamp)=N
checkpoints=[1,3,5,7,..]#user defined
data=([1,1,1,1,...],
      [2,2,2,2,...],
      ...)#len(data)=M,len(data[any])=N
processtype=('sum','max','min','snapshot',...)#len(processtype)=M

def processdata(timestamp, checkpoints, data, processtype):
    checkiter=iter(checkpoints)
    checher=checkiter.next()
    tmp=[0 if t=='sum' else None for t in processtype]
    for x, d in izip(timestamp,izip(*data)):
        tmp =[tmp[i]+d[i] if t=='sum' else
              d[i] if (t=='snapshot'
                   or (tmp[i] is None)
                   or (t=='max' and tmp[i]<d[i])
                   or (t=='min' and tmp[i]>d[i])) else
              tmp[i] for (i,t) in enumerate(processtype)]
        if x>checher:
            yield (checher,tmp)
            checher=checkiter.next()
            tmp=[0 if t=='sum' else None for t in processtype]
编辑:感谢@M4rtini和@Chronial,我在以下测试代码上运行了banchmark:

from timeit import timeit

it=xrange(100001)
condition=lambda x: x % 100 == 0

def speratedsum(it, condition):
    tmp=0
    for x in it:
        if condition(x):
            yield tmp+x
            tmp=0
        else:
            tmp+=x

def test1():
    return list(speratedsum(it,condition))

def red_func2(acc, x):
    if condition(x):
        acc[0].append(acc[1]+x)
        return (acc[0], 0)
    else:
        return (acc[0], acc[1] + x)

def test2():
    return reduce(red_func2, it,([], 0))[0]

def red_func3(l, x):
    if condition(x):
        l[-1] += x
        l.append(0)
    else:
        l[-1] += x
    return l

def test3():
    return reduce(red_func3, it, [0])[:-1]

import itertools
def test4():
    groups = itertools.groupby(it, lambda x: (x-1) / 100)
    return map(lambda g: sum(g[1]), groups)

import numpy as np
import numba
@numba.jit(numba.int_[:](numba.int_[:],numba.int_[:]),
           locals=dict(si=numba.int_,length=numba.int_))
def jitfun(arr,con):    
    length=arr.shape[0]
    out=np.zeros(con.shape[0],int)
    si=0
    for i in range(length):        
        out[si]+=arr[i]
        if(arr[i]>=con[si]):
            si+=1
    return out

conditionlist=[x for x in it if condition(x)]
a=np.array(it, int)
c=np.array(conditionlist,int)
def test5():
    return list(jitfun(a,c))
test5() #warm up for JIT

time1=timeit(test1,number=100)
time2=timeit(test2,number=100)
time3=timeit(test3,number=100)
time4=timeit(test4,number=100)
time5=timeit(test5,number=100)

print "test1:",test1()==test1(),time1/time1
print "test2:",test1()==test2(),time1/time2
print "test3:",test1()==test3(),time1/time3
print "test4:",test1()==test4(),time1/time4
print "test5:",test1()==test5(),time1/time5
输出:

test1: True 1.0
test2: True 0.369117307201
test3: True 0.496470798051
test4: True 0.833137283359
test5: True 34.1052257366
你对我该去哪里找有什么建议吗?谢谢


编辑:我设法使用带有回调的numba解决方案来替换yield,这是最省力的解决方案。因此接受了@M4rtini的回答。然而,要注意numba的局限性。通过我两天的尝试,numba可以提高numpy数组索引迭代的性能,但仅此而已。

为了实现这一点,下面是一个使用reduce的实现,它的性能应该非常糟糕:

res = reduce(lambda acc, x:
            (acc[0] + [acc[1]], 0) if condition(x) else
            (acc[0], acc[1] + x),
            iter,
            ([], 0))[0]
这应该要快得多,但我不是那么“干净”,因为它会改变累积列表

def red_func(l, x):
    if condition(x):
        l.append(0)
    else:
        l[-1] = l[-1] + x
    return l
res = reduce(red_func, iter, [0])[:-1]

您的原始版本可以通过groupby解决:

这假设条件返回True或False或其他两种可能性。如果它可以返回0、1、2、3或类似的值,那么首先需要将返回值转换为bool

for key, group in itertools.groupby(iter, lambda x: bool(condition(x))):
    #...
groupby将按顺序将具有相同键的项分组到单个组中。在这里,我们将在条件下为False的连续项集组合在一起,然后生成组的总和


在这种情况下,一行中的两个项目为真,而在这种情况下,原始版本的结果为0。

您似乎非常确定这是程序的缓慢部分,但标准建议是为了可读性而编写,然后根据需要进行修改以提高性能—在分析之后

这是我不久前写的一篇关于加快Python速度的文章:

如果您不使用任何第三方C扩展模块,Pypy可能是一个很好的选择。如果您使用的是第三方C扩展模块,请查看numba和/或Cython。

以下是使用和的解决方案:


请注意,它会产生稍微不同的结果;结果列表中不会有前导零,并且会产生一个额外的组,因为您不会产生最后一个组。

如果您将性能置于可读性之上,Python不是最佳的语言选择。每当x%100==0时,您会重置tmp=0,这是您要寻找的条件吗?您需要显示更多的代码,这部分并不是你的瓶颈所在,如果这实际上需要0.7秒来运行的话。这段代码对我来说运行速度不到1ms。转换为numpy数组,用你的条件进行Bolean索引,求和结果。你正在计时的代码不会简单地给生成器对象吗?谢谢!仍在消化。使用llower L作为变量是一个坏习惯:Drun time与fair condition相比:[original:0.29][reduce with tuplelist,int:0.78][reduce with list:0.67]我想列表操作是python中循环比列表理解、映射和减少慢的原因。它们只是为了避免列表附加、索引而设计的。换句话说,python中的循环并不慢,但列表操作很慢。感谢groupby方法,也很抱歉我的坏条件示例。在我更新的条件示例中,我发现很难实现类似于lambda x:x/100的东西。我尝试了numba,它已经显示出显著的改进,应该是研究目的的好选择。然而,考虑到numba软件包的依赖性以及目前不支持GeneratoryField,对于我目前的情况来说,这不是一个好的选择。我对python的性能声誉感到困惑,并怀疑与C相比处理速度较慢是因为我对python没有很好的理解。根据更新的演示案例,numpy是否可以提供与numba类似的性能?好的观点,但请检查我更新的问题中的运行时基准。
import numba
@numba.autojit
def speratedsum2():
    s = 0
    tmp=0
    for x in xrange(10000):
        if x % 100 == 0:
            s += tmp
            tmp=0
        else:
            tmp+=x
    return s


In [140]: %timeit sum([x for x in speratedsum1()])
1000 loops, best of 3: 625 µs per loop

In [142]: %timeit speratedsum2()
10000 loops, best of 3: 113 µs per loop
for key, group in itertools.groupby(iter, lambda x: bool(condition(x))):
    #...
iter = xrange(0, 10000)
groups = itertools.groupby(iter, lambda x: x / 100)
sums = itertools.imap(lambda g: sum(list(g[1])[1:]), groups)
import numba
@numba.autojit
def speratedsum2():
    s = 0
    tmp=0
    for x in xrange(10000):
        if x % 100 == 0:
            s += tmp
            tmp=0
        else:
            tmp+=x
    return s


In [140]: %timeit sum([x for x in speratedsum1()])
1000 loops, best of 3: 625 µs per loop

In [142]: %timeit speratedsum2()
10000 loops, best of 3: 113 µs per loop