从整数列表中获取最接近给定值的数字

给定一个整数列表,我想find哪个数字是最接近我input的数字:

>>> myList = [4, 1, 88, 44, 3] >>> myNumber = 5 >>> takeClosest(myList, myNumber) ... 4 

有没有快速的方法来做到这一点?

如果我们不确定列表是否被sorting,我们可以使用内build的min()函数来查找与指定的数字具有最小距离的元素。

 >>> min(myList, key=lambda x:abs(x-myNumber)) 4 

请注意,它也适用于带有int键的字典,如{1: "a", 2: "b"} 。 这个方法需要O(n)次。


如果列表已经sorting,或者你可以支付一次数组sorting的代价,使用@ Lauritz的回答中所示的二分法只需要O(log n)时间(但是检查列表是否已经sorting是O (n),sorting是O(n log n)。)

如果你的意思是快速执行而不是快速写入, min应该不是你select的武器,除非在一个非常狭窄的用例中。 min解决scheme需要检查列表中的每个数字,并对每个数字进行计算。 而使用bisect.bisect_left几乎总是更快。

“几乎”来自bisect_left要求列表进行sorting才能工作的事实。 希望你的用例是这样的,你可以对列表进行一次sorting,然后单独放置。 即使没有,只要你不需要每次打电话给takeClosest之前sorting,对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) ,而不是最高投票答案的O(n)运行时间。 如果我们比较这两个方法并提供一个sorting的myList ,结果如下:

 $ python -m timeit -s“
从最接近的importtakeClosest
从随机导入randint
 a =范围(-1000,1000,10)“”takeClosest(a,randint(-1100,1100))“

 100000个循环,每个循环3:2.22最好

 $ python -m timeit -s“
从最接近的importwith_min
从随机导入randint
 a =范围(-1000,1000,10)“”with_min(a,randint(-1100,1100))“

 10000循环,最好是3:每循环43.9次

所以在这个特定的testing中, bisect几乎快了20倍。 对于更长的列表,差异会更大。

如果我们通过删除myList必须被sorting的前提条件来myList竞争领域呢? 比方说,我们每次调用takeClosest takeClosest对列表的副本进行sorting,同时保持min解决scheme不变。 在上面的testing中使用200个项目列表, bisect分解仍然是最快的,但只有大约30%。

这是一个奇怪的结果,考虑到sorting步骤是O(n log(n))min仍然失败的唯一原因是sorting是在高度优化的c代码中完成的,而min必须为每个项目调用一个lambda函数。 随着myList的规模增长, min解决scheme最终会更快。 请注意,我们不得不把所有的东西放在最有利的地方。

 >>> takeClosest = lambda num,collection:min(collection,key=lambda x:abs(x-num)) >>> takeClosest(5,[4,1,88,44,3]) 4 

lambda是写一个“匿名”函数(一个没有名字的函数)的特殊方式。 你可以指定任何你想要的名字,因为lambda是一个expression式。

写出上面的“长”的方法是:

 def takeClosest(num,collection): return min(collection,key=lambda x:abs(x-num)) 
 def closest(list, Number): aux = [] for valor in list: aux.append(abs(Number-valor)) return aux.index(min(aux)) 

这段代码会给你列表中最接近号码的索引。

由Kenny提供的解决scheme是最好的总体,但在这种情况下,你不能使用它(如brython),这个function将做的工作

遍历列表并将当前最接近的数字与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 

需要注意的是,Lauritz的使用平分线的build议实际上并没有在MyList中findMyNumber中最接近的值。 相反,bisect在MyList中的MyNumber之后依次查找下一个值。 所以在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 

我不知道这会有多快,我的猜测是“不是很”。

扩大Gustavo Lima的答案。 同样的事情可以做,而不创build一个全新的列表。 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.