在 Python 中优化 for 循环以更快地工作

Posted

技术标签:

【中文标题】在 Python 中优化 for 循环以更快地工作【英文标题】:Optimizing for loop in Python to work faster 【发布时间】:2015-09-04 18:00:42 【问题描述】:

我正在努力优化 Python 代码。目标是获取一个整数列表并计算并输出列表中有多少对。一对被认为是 2 个数字,差异为 K(在这种情况下为 2) 例如:

k = 2
list = [1, 5, 3, 4, 2]

这里的配对将是(1,3), (5,3), (2,4) 答案是:3

我想提高代码效率,当前版本需要 8 秒或更长时间。

cProfile 告诉我for number in sorted_array: 是唯一需要花费所有时间的行。但我似乎无法弄清楚如何优化for 循环。

有没有人有任何经验或建议?非常感谢。

代码:

#generate random numbers
import bisect
import random
n_integers = random.sample(xrange(1, 29999), 29998)
####cProfile
import cProfile
pr = cProfile.Profile()
pr.enable()

#the difference between numbers we are looking for
k = 2
sorted_array = []
pairs_counter = 0

#insert N integers in array in sorted fashion and typecast
for number in n_integers:
    bisect.insort_left(sorted_array, number)

#iterate over the array and calculate (number + K)
for number in sorted_array:
    the_pair = number + k
    #check if the number+K is in the array
    if the_pair in sorted_array:
        pairs_counter += 1

print pairs_counter

#Close cProfile
pr.disable()
pr.print_stats(sort = 'time')

c个人资料:

30075 function calls in 7.995 seconds

   Ordered by: internal time

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    7.834    7.834    7.834    7.834 <ipython-input-5-19d578e3c582>:19(<module>)
    29998    0.143    0.000    0.143    0.000 _bisect.insort_left
        1    0.016    0.016    0.159    0.159 <ipython-input-5-19d578e3c582>:15(<module>)

【问题讨论】:

当您输入包含重复项(例如 [1,3,3,3,6])时,预期的输出是什么? 我收到30004 function calls in 0.091 seconds 是不是你的电脑速度很慢? 我也有一个运行时,其确切代码为 0.147 秒。 (2012 年末 macbook pro 15") @MorganThrapp: bisect @MorganThrapp 我有一台去年一代的 Mac,配备 8Gb DDR3 内存和 2.6 i5 处理器。可能您的计算机太强大了?))))但这是很好的结果。我在 iPython 笔记本中运行代码,所以我在控制台中尝试了 - 结果对于 30000 次调用是相同的 >7 秒。 【参考方案1】:

如果[1,3,3,3,3,3,6] 产生五个pairs (k=2),您可以使用numpybroadcasting 功能 来消除Python for循环。

import numpy as np
import random

a = random.sample(xrange(1, 29999), 29998)
a = np.array(a)
# or just a = np.random.randint(1, 29999, 29998)

k = 2

创建一个新数组,其中包含所有可以组成

的整数
b = a + k

通过在a 之间广播b 创建一个布尔数组:这会产生一个二维数组,其中包含True 的任何地方都有一个

c = a[:, np.newaxis] == b

总结所有True

np.sum(c)

或者只是:

np.sum(a[:, np.newaxis] == b)

如果如示例 input 所示,列表仅包含唯一值,numpy 解决方案将是:

a = random.sample(xrange(1, 29999), 29998)
k = 2
a = np.array(a)
b = a + k
result = np.sum(np.in1d(b, a, assume_unique=True))

哪个更快。

事实上,如果值不是唯一的,numpy.in1d 比上面的广播解决方案快得多。通过切换参数的顺序,您计算五对 [1,3,3,3,3,3,6]

result = np.sum(np.in1d(a, b))

现在有点吃乌鸦的东西:将列表变成一个集合(假设唯一值),纯 Python 解决方案比 numpy 解决方案更快。

q = 10000
a = random.sample(xrange(1, q), q-1)
a = set(a)
result = sum(n+k in a for n in a)

使用sum 消耗generator expression 不需要制作任何中间对象——这可能是其速度/效率的原因之一。

【讨论】:

我喜欢你提出的最后一个解决方案。它非常简单和优雅。你是怎么学会这样做的?我来自 Java 背景,可能这就是我的代码如此庞大的原因。我想像你一样学着写。 @solarguard89 - 花大量时间阅读文档,从 the tutorial 开始。学习一些在线学习材料。练习你遇到的每一个例子 - 解释型语言的一个很酷的地方是它很容易在 shell 中尝试和玩弄想法。 感谢您的建议 :)【参考方案2】:

使用更好的算法。在你的代码中

for number in sorted_array:
    the_pair = number + k
    #check if the number+K is in the array
    if the_pair in sorted_array:
        pairs_counter += 1

您正在检查整个数组中的the_pair,因此对列表进行排序没有任何收获。由于所有元素都是整数,所以列表排序后`the_pair,如果它出现在列表中只能出现在接下来的两个位置之一。尝试类似

for index, number in sorted_array:
    if number+k in sorted_array[index+1:index+k+1]:
        <do whatever>

【讨论】:

如果序列包含类似 - 1,3,3,3,3,3,3,3 的内容怎么办? @wwii 好点。我会说只有一对,但我不确定 OP 想要什么。另一方面,如果列表包括 1,2,2,2,2,3,我不会检测到 (1,3)。所以,如果我对这个问题的解释是正确的,我应该先从清单中拿出一套。如果需要同一对的多个实例,则可以使用内部 while 循环。 @wwii 实际上,random.sample 不给出没有替换的样本吗?这就是为什么我最初认为这些数字是不同的,但当我看到你的评论时,我突然想到了。 你是对的,random.sample 返回独特的元素。也许使用set 而不是排序列表。x in s 将是 O(1),对于小的 k 略有改进。 我认为你是对的,但 OP 的问题是“如何优化 for 循环”,这就是我想要解决的问题。【参考方案3】:

@saulspatz 是正确的,您在代码中使排序无关紧要,但是我建议您跳过排序而不是生成数千个列表切片。如果您与不可变类型(例如:tuple())进行比较,in 操作实际上非常快。因此,我将提出以下代码:

#generate random numbers
import bisect
import random
n_integers = tuple(random.sample(xrange(1, 29999), 29998))
####cProfile
import cProfile
pr = cProfile.Profile()
pr.enable()

#the difference between numbers we are looking for
k = 2
pairs_counter = 0

#iterate over the array and calculate (number + K)
for number in n_integers:
    the_pair = number + k
    #check if the number+K is in the array
    if the_pair in n_integers:
        pairs_counter += 1

print pairs_counter

#Close cProfile
pr.disable()
pr.print_stats(sort = 'time')

输出:

29996
     1 function calls in 0.000 seconds

   Ordered by: internal time

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
    1    0.000    0.000    0.000    0.000 method 'disable' of'_lsprof.Profiler' objects

【讨论】:

如果有多个 pairs - 例如(1,3,3,3,3,3,3,3,3)? x in s tuplelist 花费相同的时间,O(n)。 @Aaron 对我来说不幸的是,这段代码会在 11 秒内产生结果(((

以上是关于在 Python 中优化 for 循环以更快地工作的主要内容,如果未能解决你的问题,请参考以下文章

循环展开和优化

如何优化我的查询以更快地获得结果?

在配置文件引导优化后嵌套 for 循环更快,但缓存未命中率更高

有啥方法可以比 for 循环更快地遍历数组吗?

是否有一种 Pythonic 的方式来跳过 for 循环中的 if 语句以使我的代码运行得更快?

在 Python 中使用数组更快的 for 循环