python - 使这个迭代函数更快

时间:2017-06-19 15:46:56

标签: python function dictionary optimization

我有这个功能

def f(l):
    stack = []
    for i in range(len(l)):
        if i == 0:
            n0 = some_function(l[0], l[0])
            stack.append(n0)
        else:
            n = some_function(l[i], stack[-1])
            stack.append(n)
    return stack

其中some_function需要2个浮点数进行一些简单的数学运算并返回一个浮点数。 问题是f的参数长度可能很大,我必须在结果上循环n次,即:

l = [float_0, float_1, ..., float_m]
for i in range(n):
    l = f(l)
问题是,如何使这个简单的算法更快?我知道地图有点提高“速度”,但我不知道在这种情况下如何使用它。 任何其他想法都很有用。 非常感谢!!!

(如果我有一些语言错误,请放纵,英语不是我的母语)

2 个答案:

答案 0 :(得分:0)

也许使用列表理解:

def f(l):
    last = l[-1]
    return [some_function(l[0], l[0])] + [some_function(x, last) for x in l[1:]]

答案 1 :(得分:0)

用地图或列表理解来代替循环迭代,最多只能提高速度 - 我的猜测最多只能是2倍。但是使用理解或地图的最后计算值是棘手的(但并非不可能)。

你的职能:

def f0(ll, fun):
    stack = []
    for i in range(len(ll)):
        if i == 0:
           n0 = fun(ll[0],ll[0])
           stack.append(n0)
        else:
           n = fun(ll[i], stack[-1])
           stack.append(n)
    return stack

示例功能:

def foo(a,b):
    return a + b

简化你的功能:

def f1(ll, fun, init):
    stack = []
    for l in ll:
        stack.append(fun(l, init))
        init = stack[-1]
    return stack

def f11(ll, fun, init):         # a bit better
    stack = []
    for l in ll:
        init = fun(l, init)
        stack.append(init)
    return stack

测试:

In [784]: ll=list(range(10,20))
In [785]: f0(ll,foo)
Out[785]: [20, 31, 43, 56, 70, 85, 101, 118, 136, 155]
In [786]: f1(ll,foo,ll[0])
Out[786]: [20, 31, 43, 56, 70, 85, 101, 118, 136, 155]
In [818]: f11(ll,foo,ll[0])
Out[818]: [20, 31, 43, 56, 70, 85, 101, 118, 136, 155]

时间测试:

In [787]: llb=list(range(10,2000))
In [788]: timeit f0(llb,foo)
1000 loops, best of 3: 1 ms per loop
In [789]: timeit f1(llb,foo,llb[0])
1000 loops, best of 3: 744 µs per loop
In [819]: timeit f11(llb,foo,llb[0])
1000 loops, best of 3: 630 µs per loop

发电机方法:

def g0(ll, fun, init):
    for l in ll:
        init = fun(l, init)
        yield init

In [804]: list(g0(ll, foo, ll[0]))
Out[804]: [20, 31, 43, 56, 70, 85, 101, 118, 136, 155]
In [805]: timeit list(g0(llb, foo, llb[0]))
1000 loops, best of 3: 509 µs per loop

使用预先分配的结果(在评论中也由@Alfe建议):

def f2(ll, fun, init):
    res=ll[:]
    res[0]=init
    for i,v in enumerate(ll):
        init=fun(v,init)
        res[i]=init
    return res
In [808]: f2(ll,foo,ll[0])
Out[808]: [20, 31, 43, 56, 70, 85, 101, 118, 136, 155]
In [809]: timeit f2(llb,foo,llb[0])
1000 loops, best of 3: 583 µs per loop

通过使用reduce

,我们可以了解理论上可能的改进
In [790]: from functools import reduce   # needed for py3
In [791]: reduce(foo,ll,ll[0])
Out[791]: 155
In [792]: timeit reduce(foo,llb,llb[0])
1000 loops, best of 3: 394 µs per loop

reduce执行此操作,但仅返回最后一个值。

Cumulative sum with list comprehension告诉我们Py3 itertools有一个accumulate函数:

In [797]: list(itertools.accumulate(ll,foo))
Out[797]: [10, 21, 33, 46, 60, 75, 91, 108, 126, 145]
In [798]: timeit list(itertools.accumulate(llb,foo))
1000 loops, best of 3: 489 µs per loop

不幸的是,accumulate没有采用初始值参数(reduce会这样做。)

让我们了解列表理解和地图可以为我们做些什么,而不是试图捕捉累积行为:

In [799]: timeit [foo(a,0) for a in llb]
1000 loops, best of 3: 471 µs per loop
In [800]: timeit list(map(lambda a: foo(a,0), llb))
1000 loops, best of 3: 686 µs per loop

发电机方法看起来很不错。这是正确的,它的时间接近accumulate和列表理解的时间。简化功能评估会产生最大的差异。在列表中收集值的方法并不那么重要。

如果您有numpy,则可以将问题转换为ufunc.accumulate。例如,这个简单的求和:

In [811]: np.cumsum(ll)
Out[811]: array([ 10,  21,  33,  46,  60,  75,  91, 108, 126, 145], dtype=int32)
In [812]: np.cumsum?
In [813]: timeit np.cumsum(llb)
1000 loops, best of 3: 211 µs per loop

https://docs.python.org/3/library/itertools.html#itertool-functions

  

可以通过在iterable中提供初始值并仅使用func参数中的累计总数来建模一阶递归关系

所以:

In [827]: list(itertools.accumulate([ll[0]]+ll,foo))[1:]
Out[827]: [20, 31, 43, 56, 70, 85, 101, 118, 136, 155]
In [828]: timeit list(itertools.accumulate([llb[0]]+llb,foo))[1:]
1000 loops, best of 3: 478 µs per loop

虽然这取决于foo(a,0)返回a