我有一个同时检索列表的最小值和最大值的函数:
def min_max(iterable, key=None):
"""Retrieve the min and max values of `iterable` simultaneously."""
if key is None: key = lambda x: x
if not iterable:
return None, None
min_ = max_ = key(iterable[0])
for i in iterable[1:]:
if key(i) > key(max_): max_ = i
if key(i) < key(min_): min_ = i
return min_, max_
但它让我感到疑惑,因为我在for循环中进行了两次比较,单独使用min
和max
会不会更快?如果是,我如何编辑此功能以提高效率?
答案 0 :(得分:6)
找出列表中的最小值或最大值的昂贵部分不是比较。比较值非常快,并且不会在这里产生问题。相反,影响运行时间的是循环。
当您使用min()
或max()
时,其中每个都必须迭代迭代一次。它们分别执行此操作,因此当您需要最小值和最大值时,通过使用内置函数,您将迭代两次。
您的函数只需迭代一次,因此其理论运行时间更短。现在作为评论中提到的chepter,min
和max
已实现in native code,因此它们肯定比自己在Python代码中实现它更快。
现在,它很大程度上取决于你的迭代是否两个本机循环将比你的Python函数更快。对于更长的列表,迭代它已经很昂贵,迭代一次肯定会更好,但对于较短的列表,你可能会使用本机代码获得更好的结果。我无法确定确切的阈值在哪里,但您可以轻松地测试实际数据的速度。但在大多数情况下,它很少会因为min / max不会成为你的应用程序的瓶颈,所以你不应该担心它会成为一个问题。
顺便说一下。你的实现现在有一些问题,如果你想使用它你应该修复:
iterable
为序列,而不是可迭代(因为您在其上使用索引)not iterable
时,这不一定会告诉您序列/可迭代的长度。自定义类型可以轻松提供自己的布尔值和/或序列行为。_min
和_max
,但稍后(正确)只是从可迭代项中分配原始项目。所以我建议你改用迭代器,并修复那个关键的东西 - 你也可以存储关键结果,以便为更复杂的关键函数保存一些计算:
it = iter(iterable)
try:
min_ = max_ = next(it)
minv = maxv = key(min_)
except StopIteration:
return None, None
for i in it:
k = key(i)
if k > maxv:
max_, maxv = i, k
elif k < minv:
min_, minv = i, k
我对此做了一些测试,事实证明 - 没有自定义键功能 - 使用内置的max / min是不可能的。即使对于非常大的列表,purce C实现也太快了。但是,只要添加一个键函数(用Python代码编写),情况就会完全颠倒过来。使用键功能,您可以获得与单个min
或max
调用完全相同的时序结果,而完整功能同时执行两者。因此,使用Python编写的解决方案很多更快。
因此,这导致了这样的想法,即,Python中的实现可能不是实际问题,而是使用的key
函数。事实上,实际的关键功能是使Python实现成本高昂的原因。它也很有道理。即使使用identity-lamba,你仍然有函数调用的开销; len(iterable)
许多函数调用(使用上面的优化变体)。函数调用非常昂贵。
在我的测试中,由于支持取出的关键功能,实际预期的结果出现了:迭代一次比两次快。但对于非常大的迭代,差异非常小。因此,除非迭代迭代是非常昂贵的(尽管你可以使用tee
并仍然迭代两次)或者你想要遍历它(在这种情况下你将它与min / max检查结合),使用内置的max()
和min()
功能将分别更快,也更容易使用。并且,如果您没有指定关键函数,它们都会带有内部优化功能。
最后,您如何在代码中添加关键函数优化?好吧,不幸的是,只有一种方法可以做到这一点,这涉及到复制代码。您基本上必须检查是否指定了键功能,并在不执行时跳过函数调用。所以,像这样:
def min_max(iterable, key=None):
if key:
# do it with a key function
else:
# do it without
答案 1 :(得分:0)
请点击此处:
这不是你正在寻找的,但我可以减少循环:
def min_max(iterable):
if not iterable:
raise Exception('Required iterable object')
_min = _max = iterable[0]
ind = 0
if len(iterable) & 1:
ind = 1
for elm in iterable[1::2]:
ind += 2
try:
if iterable[ind] < iterable[ind + 1]:
if _min > iterable[ind]:
_min = iterable[ind]
if _max < iterable[ind + 1]:
_max = iterable[ind + 1]
else:
if _min > iterable[ind + 1]:
_min = iterable[ind + 1]
if _max < iterable[ind]:
_max = iterable[ind]
except:
pass
return _min, _max
print min_max([11,2,3,5,0,1000,14,5,100,1,999])
输出:
(0, 1000)
答案 2 :(得分:-1)
使用此代码:
for i in iterable[1:]:
if key(i) > key(max_):
max_ = i
elif key(i) < key(min_):
min_ = i