使用Python列表推导计算列表的最小值和最大值

8 投票
2 回答
19926 浏览
提问于 2025-04-18 16:03

我有以下代码,用来计算一个列表中的最小值和最大值,以提高内存使用效率。

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

假设一个点有两个属性,分别是 xy,那么你可以用下面的方式来计算所有点中 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

我非常喜欢生成器和列表推导式,但在这种情况下,它们似乎不是最好的选择,因为:

  1. 你想计算列表中的 min(最小值)和 max(最大值)
  2. 你的列表非常大

如果你只想计算 minmax 中的一个,你可以直接用相应的函数来处理。但因为你想要两个值,你就得遍历列表两次,第一次找最小值,第二次找最大值。也就是说,代码大概是这样的:

x_min = min(points)
x_max = max(points)

接下来我们来看看一些时间的对比。首先同时调用列表的 minmax

>>> 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_maxx_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

哦,惊喜

看起来虽然只循环一次的算法在纸面上更高效,但实际上被 minmax 的内部优化打败了。而且,在每次循环中设置变量和只在必要时设置变量之间的差别是巨大的。你永远在学习

撰写回答