最高对数组部分求和的有效方法

时间:2019-06-16 20:34:15

标签: python algorithm

我正在尝试找到一种更有效的方法来执行以下任务。

正在向函数提供一些列表。我需要:

1)找到列表的总和,并将结果存储在为此专门准备的另一个列表中。

2)删除输入列表的第一个元素

3)找到新列表的总和,然后从STEP 1将其追加到列表中。

4)重复直到输入列表为空。

基本上,下面的代码已经完成了任务,但是在速度方面效率低下(据我所知,append方法并不是Python工具库中最精简的工具)。哪种方法更有效?

def parts_sums(ls):
    sums = []

    if len(ls) == 0:
            sums.append(0)

    while len(ls) > 0:
        sums.append(sum(ls))
        ls.pop(0)

        if len(ls) == 0:
            sums.append(0)

    return sums

谢谢。

3 个答案:

答案 0 :(得分:2)

ls.pop(0)的计算量很大,因为这意味着所有其余元素都向左移动了一个位置。对于 n 元素,因此这是 O(n)运算。此外,您每次都要计算一个子列表的总和,这又是一个 O(n)运算。

例如,我们可以首先计算整个列表的总和,然后每次从中减去一个元素,例如:

def parts_sums(ls):
    total = sum(ls)
    yield total
    for item in ls:
        total -= item
        yield total

这给了我们

>>> list(parts_sums([1,4,2,5]))
[12, 11, 7, 5, 0]

我们可以通过对列表进行简单的 scan 计算列表,例如:

def parts_sums(ls):
    a = 0
    result = [0]
    for e in reversed(ls):
        a += e
        result.append(a)
    result.reverse()
    return result

例如:

>>> parts_sums([1,4,2,5])
[12, 11, 7, 5, 0]

因此,我们在这里计算反向列表的累积总和数组,然后反向运行该列表,这在功能上是相同的。

我们也可以使用numpy

import numpy as np

def parts_sums(iterable):
    return np.hstack((np.flip(np.cumsum(np.flip(iterable))), [0]))

例如:

>>> parts_sums([1,4,2,5])
array([12, 11,  7,  5,  0])

性能

我在2.70GHz的Intel®Core™i7-7500U CPU上进行了一些测试。如果data是1万个元素的列表,则对于1 000次运行,我们将获得以下结果:

>>> timeit(lambda: list(parts_sums1(data)), number=1000)
1.5667014829959953
>>> timeit(lambda: parts_sums2(data), number=1000)
1.095261047994427
>>> timeit(lambda: parts_sums3(data), number=1000)
0.5962606709945248

对于包含100'000个元素的列表data,我们获得了100次运行的以下结果:

>>> timeit(lambda: list(parts_sums1(data)), number=100)
1.6292997589989682
>>> timeit(lambda: parts_sums2(data), number=100)
1.1703664560045581
>>> timeit(lambda: parts_sums3(data), number=100)
0.6373857369981124

答案 1 :(得分:2)

一个带有itertools.accumulate的版本:

from itertools import accumulate
import timeit

l = [1,4,2,5]
i = [*accumulate(reversed(l + [0]))][::-1]

print(i)

l = [*range(10_000)]
print(timeit.timeit(lambda: [*accumulate(reversed(l + [0]))][::-1], number=1000))

l = [*range(100_000)]
print(timeit.timeit(lambda: [*accumulate(reversed(l + [0]))][::-1], number=100))

打印(在AMD 2400G上):

[12, 11, 7, 5, 0]
0.28572049700596835
0.45740387199475663

编辑(使用接受的答案中的parts_sums1()parts_sums2()

0.6355529940046836  # parts_sums1()/10_000 items/1000 iterations
0.7757905749967904  # parts_sums1()/100_000 items/100 iterations 
0.5660922379975091  # parts_sums2()/10_000 items/1000 iterations
0.749676775005355   # parts_sums2()/100_000 items/100 iterations

答案 2 :(得分:2)

您可以使用itertools.accumulateoperator.sub

from itertools import accumulate
from operator import sub

d = [1,4,2,5]
list(accumulate([sum(d),*d], sub))
#[12, 11, 7, 5, 0]

相对于此答案,这是最快,最容易阅读的:

timeit(lambda: list(accumulate([sum(d),*d], sub)), number=1000)
#0.0022647730002063327
timeit(lambda: [*accumulate(reversed(d + [0]))][::-1], number=1000)
#0.005318050000369112
timeit(lambda: np.hstack((np.flip(np.cumsum(np.flip(d))), [0])), number=1000)
#0.024382672000228922

可在以下REPL上找到该代码:https://repl.it/repls/ApprehensiveMuffledConfig,但请参阅其他答案以获取有关为什么这些速度更快的更多说明。