使用Python列表推导计算列表的最小值和最大值
我有以下代码,用来计算一个列表中的最小值和最大值,以提高内存使用效率。
x_min = float('+inf')
x_max = float('-inf')
for p in points_in_list:
x_min = min(x_min, p)
x_max = max(x_max, p)
这里的 points_in_list 是一个(很大的)数字列表。我想知道有没有办法用列表推导式来计算最小值和最大值,同时节省内存。
2 个回答
8
假设一个点有两个属性,分别是 x
和 y
,那么你可以用下面的方式来计算所有点中 x
的最小值:
x_min = min(p['x'] for p in points_in_list)
举个例子:
>>> a = {'x': 10, 'y':10}
>>> b = {'x': 5, 'y':20}
>>> c = {'x': 50, 'y':50}
>>> points_in_list = [a,b,c]
>>> points_in_list
[{'y': 10, 'x': 10}, {'y': 20, 'x': 5}, {'y': 50, 'x': 50}]
>>> x_min = min(p['x'] for p in points_in_list)
>>> x_min
5
10
我非常喜欢生成器和列表推导式,但在这种情况下,它们似乎不是最好的选择,因为:
- 你想计算列表中的
min
(最小值)和max
(最大值) - 你的列表非常大
如果你只想计算 min
或 max
中的一个,你可以直接用相应的函数来处理。但因为你想要两个值,你就得遍历列表两次,第一次找最小值,第二次找最大值。也就是说,代码大概是这样的:
x_min = min(points)
x_max = max(points)
接下来我们来看看一些时间的对比。首先同时调用列表的 min
和 max
:
>>> import timeit
>>> def with_gens(l):
... return min(l), max(l)
...
>>> timeit.timeit('with_gens(range(6000000))', 'from __main__ import with_gens', number=5)
1.7451060887015188
然后只遍历一次,使用你的代码:
>>> def with_loop2(l):
... x_max = float('+inf')
... x_min = float('-inf')
... for el in l:
... x_min = min(x_min, el)
... x_max = max(x_max, el)
... return x_min, x_max
...
>>> timeit.timeit('with_loop2(range(6000000))', 'from __main__ import with_loop2', number=5)
11.636076105071083
是不是很疯狂?
这种方法根本没有内存问题。不过,它在每次循环中都在设置 x_max
和 x_min
,这其实是多余的浪费:你只需要在找到更大或更小的值时才重置这些变量。我们可以很容易地解决这个问题。
所以……我们试着只循环一次,但避免不必要的重置。
>>> def with_loop(l):
... x_min = float('-inf')
... x_max = float('+inf')
... for el in l:
... if el < x_min:
... x_min = el
... elif el > x_max:
... x_max = el
... return x_min, x_max
...
>>> timeit.timeit('with_loop(range(6000000))', 'from __main__ import with_loop', number=5)
3.961046726963332
哦,惊喜
看起来虽然只循环一次的算法在纸面上更高效,但实际上被 min
和 max
的内部优化打败了。而且,在每次循环中设置变量和只在必要时设置变量之间的差别是巨大的。你永远在学习。