218

Given a list of integers, I want to find which number is the closest to a number I give in input:

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

Is there any quick way to do this?

4

10 回答 10

438

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

于 2012-08-27T11:37:05.970 回答
180

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

如果您的意思是快速执行而不是快速编写,min则不应成为您选择的武器,除非在一个非常狭窄的用例中。解决方案需要检查列表中的每个数字并对每个数字进行计算。相反,使用几乎总是更快。minbisect.bisect_left

“几乎”来自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)运行时间。如果我们比较这两种方法并同时提供 sorted ,结果如下:myList

$ python -m timeit -s "
从最近的进口 take_closest
从随机导入 randint
a = range(-1000, 1000, 10)" "take_closest(a, randint(-1100, 1100))"

100000 个循环,3 个中最好的:每个循环 2.22 微秒

$ python -m timeit -s "
从最近的进口 with_min
从随机导入 randint
a = range(-1000, 1000, 10)" "with_min(a, randint(-1100, 1100))"

10000 个循环,最好的 3 个:每个循环 43.9 微秒

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

如果我们通过删除myList必须排序的先决条件来平衡竞争环境怎么办?假设每次 take_closest调用时我们都会对列表的副本进行排序,同时保持min解决方案不变。使用上述测试中的 200 项列表,该bisect解决方案仍然是最快的,尽管只有大约 30%。

这是一个奇怪的结果,考虑到排序步骤是O(n log(n))!仍然失败的唯一原因min是排序是在高度优化的 c 代码中完成的,而min必须为每个项目调用一个 lambda 函数。随着myList规模的扩大,min解决方案最终会更快。请注意,我们必须将所有内容都对其有利,以使min解决方案获胜。

于 2012-08-27T11:56:07.523 回答
11
>>> 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))
于 2012-08-27T11:37:36.373 回答
6

遍历列表并将当前最接近的数字与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
于 2012-08-27T11:36:15.870 回答
6
def closest(list, Number):
    aux = []
    for valor in list:
        aux.append(abs(Number-valor))

    return aux.index(min(aux))

此代码将为您提供列表中最接近 Number 的索引。

KennyTM 给出的解决方案总体上是最好的,但是在你不能使用它的情况下(比如 brython),这个功能会起作用

于 2013-12-16T14:36:06.350 回答
3
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])
于 2021-08-31T07:35:57.163 回答
2

需要注意的是,Lauritz 关于使用 bisect 的建议思想实际上并没有在 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

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

于 2015-06-02T18:19:54.933 回答
1

扩展古斯塔沃利马的回答。无需创建全新列表即可完成相同的操作。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.
于 2017-07-25T00:36:09.437 回答
0

如果我可以添加到@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
于 2018-08-28T07:07:28.623 回答
0
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)
于 2021-06-29T06:04:45.503 回答