繁体   English   中英

从整数列表中,获取最接近给定值的数字

[英]From list of integers, get number closest to a given value

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

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

有什么快速的方法可以做到这一点吗?

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

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

如果你的意思是使用能快速执行,而不是快速到写, min应该是你的首选武器,除了在一个很窄的用例。 min解决方案需要检查列表中的每个数字做每个数字的计算。 bisect.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)运行时间。 如果我们比较这两种方法并提供一个排序的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必须排序的先决条件来myList会怎样? 假设每次调用take_closest我们都会对列表的副本进行排序,同时保持min解不变。 在上面的测试中使用 200 项列表, bisect解决方案仍然是最快的,尽管只有大约 30%。

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

>>> 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))
def closest(list, Number):
    aux = []
    for valor in list:
        aux.append(abs(Number-valor))

    return aux.index(min(aux))

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

KennyTM 给出的解决方案总体上是最好的,但在您不能使用它的情况下(如 brython),此功能将完成工作

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM