从整数列表中,求出最接近给定值的数

给定一个整数列表,我想找到哪个数字最接近我输入的数字:

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

有什么快速的方法吗?

302792 次浏览

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

如果不确定列表是否已排序,可以使用内置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)。)

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

λ是一种编写“匿名”函数(没有名字的函数)的特殊方式。你可以给它分配任何你想要的名字,因为lambda是一个表达式。

以上内容的“长”写法是:

def takeClosest(num,collection):
return min(collection,key=lambda x:abs(x-num))

我将重命名函数take_closest以符合PEP8命名约定。

如果你指的是快速执行而不是快速编写,min应该是你选择的武器,除非在一个非常狭窄的用例中。min解决方案需要检查列表而且中的每个数字,为每个数字进行计算。而使用bisect.bisect_left几乎总是更快。

“almost"来自于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),而不是得票最高的答案的运行时间为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

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

如果我们通过移除myList必须排序的先决条件来创造一个公平的竞争环境呢?假设我们对列表每一次 take_closest的副本进行排序,而不改变min解决方案。使用上面测试中的200项列表,bisect解决方案仍然是最快的,尽管只快了大约30%。

这是一个奇怪的结果,考虑到排序步骤是O (n log (n))!min仍然失败的唯一原因是排序是在高度优化的c代码中完成的,而min必须为每一项调用lambda函数。随着myList大小的增长,min解决方案最终会更快。请注意,为了min解决方案获胜,我们必须将所有东西都堆叠在它的有利位置。

def closest(list, Number):
aux = []
for valor in list:
aux.append(abs(Number-valor))


return aux.index(min(aux))

这段代码将为您提供列表中与number最接近的数字的索引。

KennyTM给出的解决方案是最好的,但在您不能使用它的情况下(如brython),这个函数将完成工作

值得注意的是,Lauritz使用平分的建议实际上并没有在MyList中找到与MyNumber最接近的值。相反,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的回答。不用创建一个全新的列表也可以完成同样的事情。随着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.

如果我可以添加到@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
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)
def find_nearest(array, value):
array = np.asarray(array)
idx = (np.abs(array - value)).argmin()
return array[idx]

通过使用

price_near_to=find_nearest(df['Close'], df['Close'][-2])