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

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

有什么快速的方法吗?


当前回答

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

其他回答

我将重命名函数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规模的增长,最小解最终会更快。请注意,为了让最小解获胜,我们必须把一切都叠在对它有利的地方。

>>> 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))

扩展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.
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])

值得注意的是,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

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