给定一个整数列表,我想找到哪个数字最接近我输入的数字:
>>> myList = [4, 1, 88, 44, 3]
>>> myNumber = 5
>>> takeClosest(myList, myNumber)
...
4
有没有快速的方法可以做到这一点?
答案 0 :(得分:278)
如果我们不确定列表是否已排序,我们可以使用built-in min()
function来查找与指定数字的距离最小的元素。
>>> min(myList, key=lambda x:abs(x-myNumber))
4
请注意,它也适用于带有int键的dicts,例如{1: "a", 2: "b"}
。此方法需要O(n)时间。
如果列表已经排序,或者您只需支付一次排序数组的价格,请使用@Lauritz's answer中所示的二分法,该方法只需要O(log n)时间(注意检查是否有列表)已排序为O(n),排序为O(n log n)。)
答案 1 :(得分:124)
如果你的意思是快速执行而不是快速写入,min
不应该是你的首选武器,除非是一个非常狭窄的用例。 min
解决方案需要检查列表中的每个数字,并且对每个数字进行计算。相反,使用bisect.bisect_left
几乎总是更快。
“几乎”来自于bisect_left
要求将列表分类才能工作的事实。希望您的用例是这样的,您可以对列表进行一次排序,然后不管它。即使不是,只要您在每次拨打takeClosest
之前不需要进行排序,bisect
模块就可能会排在最前面。如果您有疑问,请尝试两者并查看现实世界的差异。
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.
"""
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 " from closest import takeClosest from random import randint a = range(-1000, 1000, 10)" "takeClosest(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
所以在这个特定的测试中,bisect
快了近20倍。对于较长的列表,差异会更大。
如果我们通过删除myList
必须排序的前提来平衡比赛场地怎么办?假设我们在每次调用 takeClosest
时对列表进行排序,同时保持min
解决方案不变。使用上述测试中的200项目列表,bisect
解决方案仍然是最快的,但只有约30%。
考虑到排序步骤 O(n log(n)),这是一个奇怪的结果! min
仍然失败的唯一原因是排序是在高度优化的c代码中完成的,而min
必须为每个项目调用lambda函数。随着myList
大小的增加,min
解决方案最终会更快。请注意,我们必须将所有内容都叠加到min
解决方案中才能获胜。
答案 2 :(得分:8)
>>> 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))
答案 3 :(得分:6)
def closest(list, Number):
aux = []
for valor in list:
aux.append(abs(Number-valor))
return aux.index(min(aux))
此代码将为您提供列表中最接近数字的索引。
KennyTM给出的解决方案总体上是最好的,但是在你无法使用它的情况下(比如brython),这个函数将完成工作
答案 4 :(得分:3)
迭代列表并将当前最接近的数字与abs(currentNumber - myNumber)
进行比较:
def takeClosest(myList, myNumber):
closest = myList[0]
for i in range(1, len(myList)):
if abs(i - myNumber) < closest:
closest = i
return closest
答案 5 :(得分:2)
重要的是要注意Lauritz关于使用bisect的建议实际上并没有找到MyList中与MyNumber最接近的值。而是,bisect在MyList中的MyList中找到 order 中的下一个值。所以在OP的情况下,你实际上得到的是44的位置,而不是4的位置。
>>> myList = [1, 3, 4, 44, 88]
>>> myNumber = 5
>>> pos = (bisect_left(myList, myNumber))
>>> myList[pos]
...
44
要获得最接近5的值,您可以尝试将列表转换为数组,并使用numpy中的argmin。
>>> import numpy as np
>>> myNumber = 5
>>> myList = [1, 3, 4, 44, 88]
>>> myArray = np.array(myList)
>>> pos = (np.abs(myArray-myNumber)).argmin()
>>> myArray[pos]
...
4
我不知道这会有多快,我的猜测是&#34;不是很好&#34;。
答案 6 :(得分:0)
扩大Gustavo Lima的答案。在不创建全新列表的情况下也可以完成同样的事情。随着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.
Below this point is the original post
答案 7 :(得分:0)
如果我可以添加到@Lauritz's answer
为了不出现运行错误
不要忘记在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
答案 8 :(得分:0)
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)