给定一个整数列表,我想找到哪个数字最接近我输入的数字:
>>> myList = [4, 1, 88, 44, 3]
>>> myNumber = 5
>>> takeClosest(myList, myNumber)
...
4
有什么快速的方法吗?
给定一个整数列表,我想找到哪个数字最接近我输入的数字:
>>> myList = [4, 1, 88, 44, 3]
>>> myNumber = 5
>>> takeClosest(myList, myNumber)
...
4
有什么快速的方法吗?
当前回答
我将重命名函数take_nearest以符合PEP8命名约定。
如果你指的是快速执行而不是快速编写,那么min不应该是你选择的武器,除非在一个非常狭窄的用例中。最小解需要检查列表中的每个数字,并对每个数字进行计算。使用平分。而Bisect_left几乎总是更快。
“几乎”来自于bisect_left需要对列表进行排序才能工作的事实。希望您的用例是这样的,您可以对列表进行一次排序,然后不去管它。即使不是这样,只要您不需要在每次调用take_nearest之前进行排序,那么平分模块很可能会排在最前面。如果你有疑问,两种都试一下,看看现实世界的区别。
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),而投票最多的答案的运行时间为O(n)。如果我们比较这两个方法,并为它们提供一个排序后的myList,结果如下:
$ python -m timeit -s " from closest import take_closest from random import randint a = range(-1000, 1000, 10)" "take_closest(a, randint(-1100, 1100))" 100000 loops, best of 3: 2.22 usec per loop $ python -m timeit -s " from closest import with_min from random import randint a = range(-1000, 1000, 10)" "with_min(a, randint(-1100, 1100))" 10000 loops, best of 3: 43.9 usec per loop
在这个测试中,平分法几乎快20倍。对于较长的列表,差异会更大。
如果我们通过移除myList必须排序的前提条件来创造一个公平的竞争环境呢?假设每次调用take_nearest时,我们对列表的一个副本进行排序,同时保持最小解不变。使用上述测试中的200项列表,平分解决方案仍然是最快的,尽管只快了大约30%。
这是一个奇怪的结果,考虑到排序步骤是O(nlog (n))!min仍然失败的唯一原因是排序是在高度优化的c代码中完成的,而min必须为每一项调用lambda函数。随着myList规模的增长,最小解最终会更快。请注意,为了让最小解获胜,我们必须把一切都叠在对它有利的地方。
其他回答
扩展Gustavo Lima的回答。不用创建一个全新的列表也可以完成同样的事情。随着FOR循环的进行,列表中的值可以替换为差值。
def f_ClosestVal(v_List, v_Number):
"""Takes an unsorted LIST of INTs and RETURNS INDEX of value closest to an INT"""
for _index, i in enumerate(v_List):
v_List[_index] = abs(v_Number - i)
return v_List.index(min(v_List))
myList = [1, 88, 44, 4, 4, -2, 3]
v_Num = 5
print(f_ClosestVal(myList, v_Num)) ## Gives "3," the index of the first "4" in the list.
>>> takeClosest = lambda num,collection:min(collection,key=lambda x:abs(x-num))
>>> takeClosest(5,[4,1,88,44,3])
4
lambda是一种书写“匿名”函数(没有名字的函数)的特殊方式。你可以给它分配任何你想要的名字,因为lambda是一个表达式。
以上内容的“长”写法是:
def takeClosest(num,collection):
return min(collection,key=lambda x:abs(x-num))
如果我们不确定列表是否已排序,可以使用内置的min()函数来查找与指定数字距离最小的元素。
>>> min(myList, key=lambda x:abs(x-myNumber))
4
注意,它也适用于具有int键的字典,如{1:"a", 2: "b"}。该方法耗时O(n)。
如果列表已经排序了,或者你可以只对数组排序一次,使用@Lauritz的回答中说明的平分方法,它只需要O(log n)时间(注意,检查列表是否已经排序是O(n),排序是O(n log n))。
def takeClosest(myList, myNumber):
newlst = []
for i in myList:
newlst.append(i - myNumber)
lstt = [abs(ele) for ele in newlst]
print(myList[lstt.index(min(lstt))])
myList = [4, 1, 88, 44, 3]
myNumber = 5
takeClosest(myList,myNumber)
如果我可以补充@Lauritz的回答
为了不出现运行错误 不要忘记在bisect_left行之前添加一个条件:
if (myNumber > myList[-1] or myNumber < myList[0]):
return False
所以完整的代码看起来像这样:
from bisect import bisect_left
def takeClosest(myList, myNumber):
"""
Assumes myList is sorted. Returns closest value to myNumber.
If two numbers are equally close, return the smallest number.
If number is outside of min or max return False
"""
if (myNumber > myList[-1] or myNumber < myList[0]):
return False
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