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

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

有什么快速的方法吗?


当前回答

如果我可以补充@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

其他回答

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

如果我可以补充@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

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

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

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

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