从整数列表中获取最接近给定值的数字
给定一个整数列表,我想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.