我将重命名函数 take_closest 以符合 PEP8 命名约定。
如果您的意思是快速执行而不是快速编写,min 应该不是您选择的武器,除非在一个非常狭窄的用例中。 min 解决方案需要检查列表中的每个数字并对每个数字进行计算。使用 bisect.bisect_left 几乎总是更快。
“几乎”来自bisect_left 需要对列表进行排序才能工作的事实。希望您的用例是这样的,您可以对列表进行一次排序,然后不理会它。即使没有,只要您不需要在每次调用take_closest 之前进行排序,bisect 模块很可能会排在首位。如果您有疑问,请尝试两者并查看实际差异。
from bisect import bisect_left
def take_closest(myList, myNumber):
"""
Assumes myList is sorted. Returns closest value to myNumber.
If two numbers are equally close, return the smallest number.
"""
pos = bisect_left(myList, myNumber)
if pos == 0:
return myList[0]
if pos == len(myList):
return myList[-1]
before = myList[pos - 1]
after = myList[pos]
if after - myNumber < myNumber - before:
return after
else:
return before
Bisect 的工作原理是将列表重复减半,并通过查看中间值找出myNumber 必须在哪一半。这意味着它的运行时间为 O(log n),而 highest voted answer 的运行时间为 O(n)。如果我们比较这两种方法并为它们提供一个排序的myList,结果如下:
$ python -m timeit -s "
从最近的进口 take_closest
从随机导入 randint
a = range(-1000, 1000, 10)" "take_closest(a, randint(-1100, 1100))"
100000 个循环,3 个中最好的:每个循环 2.22 微秒
$ python -m timeit -s "
从最近的进口 with_min
从随机导入 randint
a = range(-1000, 1000, 10)" "with_min(a, randint(-1100, 1100))"
10000 个循环,最好的 3 个:每个循环 43.9 微秒
所以在这个特定的测试中,bisect 几乎快了 20 倍。对于更长的列表,差异会更大。
如果我们通过删除 myList 必须排序的前提条件来平衡竞争环境会怎样?假设我们每次对列表的副本进行排序take_closest 被调用,同时保持min 解决方案不变。使用上述测试中的 200 项列表,bisect 解决方案仍然是最快的,尽管只有大约 30%。
这是一个奇怪的结果,考虑到排序步骤是 O(n log(n))! min 仍然失败的唯一原因是排序是在高度优化的 c 代码中完成的,而 min 必须为每个项目调用一个 lambda 函数。随着myList 规模的增长,min 解决方案最终会更快。请注意,我们必须将所有内容都对其有利,min 解决方案才能获胜。