我正在尝试找到一种更有效的方法来执行以下任务。
正在向函数提供一些列表。我需要:
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
谢谢。
答案 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.accumulate
和operator.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,但请参阅其他答案以获取有关为什么这些速度更快的更多说明。