2012-08-27 23 views
95

,我想找到哪个号码是最接近号码,我给中输入:从整数列表,获取数最接近鉴于整数列表的给定值

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

有没有什么快捷方式去做这个?

+2

约也返回此发生在列表中的索引内容。 –

+1

[找到最接近列表中未完全排序值的项目的索引的可能重复项](https://stackoverflow.com/questions/9706041/finding-index-of-an-item-closest-to-the -value-a-list-thats-not-entire-sort) –

+0

@ sancho.s很好的发现。尽管这个问题的答案比其他问题的答案好得多。所以我要投票关闭另一个作为这个副本。 –

回答

205

如果我们不能肯定列表排序,我们可以使用built-in min() function,寻找具有最小距离元素从指定的数字开始。

>>> min(myList, key=lambda x:abs(x-myNumber)) 
4 

请注意,它也适用于带有int键的字典,如{1: "a", 2: "b"}。这个方法需要O(n)次。


如果列表已经排序,或者你可以交一次只排数组的价格,使用@Lauritz's answer所示的二分法只需要O(log n)的但是时间(注意检查是否列表已经排序是O(n)和排序为O(n log n)的。)

+12

在复杂性方面,这是'O(n)',其中'bisect'的一点点黑客会给你'O(log n)'(如果你的输入数组已被排序)的巨大改进。 –

+3

@mic_e:这只是[劳瑞兹的答案](http://stackoverflow.com/a/12141511/224671)。 – kennytm

+3

怎么样也返回列表中发生的索引? –

3

遍历列表,并与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 
+1

你也可以返回索引。 –

+0

!不正确!应该是'if abs(myList [i] - myNumber)

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

但请注意,根据[PEP 8](https://www.python.org/dev/peps/pep-0008/#programming-recommendations),不鼓励将lambda分配给名称。 – evertheylen

92

如果你的意思是使用能快速执行,而不是快速到写,min应该是你的武器除了一个非常狭窄的用例外。该min解决方案需要检查列表做每个数字的计算每一个数字。反而使用bisect.bisect_left几乎总是更快。

的“差不多”来自于bisect_left要求列表进行排序工作的事实。希望您的用例能够让您对列表进行一次排序,然后保持独立。即使没有,只要你不需要每次调用takeClosest时间之前进行排序,则bisect模块将可能拔得头筹。如果你有疑问,试试看看现实世界的差异。

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. 
    """ 
    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 

平分的工作原理是反复减半列表,并找出其中一半myNumber具有通过观察中间值是英寸这意味着它具有的○运行时间(log n)的而不是在highest voted answer为O(n)运行时间。如果我们比较这两种方法,并同时提供了分类myList,这些都是结果:

 
$ python -m timeit -s " 
from closest import takeClosest 
from random import randint 
a = range(-1000, 1000, 10)" "takeClosest(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必须排序的前提条件来平等竞技场怎么办?假设我们每次都对列表进行排序,takeClosest被调用,同时保持min解决方案不变。使用上述测试中的200个项目列表,bisect解决方案仍然是最快的,但只有大约30%。

这是一个奇怪的结果,考虑到排序步骤是O(n log(n))min仍然失败的唯一原因是排序是在高度优化的c代码中完成的,而min必须沿着为每个项目调用lambda函数的方式进行。由于myList的规模增长,min解决方案最终会更快。请注意,我们不得不将所有内容都堆叠起来以便获得min解决方案。

+2

排序本身需要O(N log N),所以当N变大时它会变慢。例如,如果你使用'a = range(-1000,1000,2); random.shuffle(a)',你会发现'takeClosest(sorted(a),b)'会变慢。 – kennytm

+3

@KennyTM我会给你,我会在我的答案中指出。但只要'getClosest'可能会被每次调用多次,这将会更快,而且对于一次性使用情况来说,这是毫不费力的。 –

+0

那么返回列表中发生的索引呢? –

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

    return aux.index(min(aux)) 

此代码会给你列表中最接近号码的索引。

通过KennyTM给出的解决方案是最佳的整体,但在情况下,你不能使用它(如brython),此功能将做的工作

1

需要注意的是使用对开的Lauritz的建议,想法不很重要实际上在MyList中找到MyNumber中最接近的值。相反,平分在MyList中的MyNumber之后找到订单中的下一个值。因此,在OP的情况下,你实际上得到的44的位置返回而不是4.

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

的位置,以获得这就是最接近5的值,你可以尝试列表转换为一个数组,并使用argmin从numpy的像这样。

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

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

+2

Lauritz的功能正常工作。您只使用bisect_left,但Lauritz建议使用takeClosest(...)函数进行额外检查。 – Kanat

+0

如果你打算使用NumPy,你可以使用'np.searchsorted'而不是'bisect_left'。 @Kanat是对的 - Lauritz的解决方案*包括代码,它选择两个候选人中哪一个更接近。 –

0

扩大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.