给定一个整数列表,我想找到哪个数字与我在输入中提供的数字最接近:
>>> myList = [4, 1, 88, 44, 3]
>>> myNumber = 5
>>> takeClosest(myList, myNumber)
...
4
有什么快速的方法可以做到这一点吗?
给定一个整数列表,我想找到哪个数字与我在输入中提供的数字最接近:
>>> myList = [4, 1, 88, 44, 3]
>>> myNumber = 5
>>> takeClosest(myList, myNumber)
...
4
有什么快速的方法可以做到这一点吗?
Answers:
如果不确定列表是否已排序,则可以使用内置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)。)
O(n)
,在这里进行一些小小的改动bisect
将给您带来巨大的改进O(log n)
(如果您对输入数组进行了排序)。
min
,在字典(items()
)而不是列表上运行它,最后返回键而不是值。
我将重命名该函数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“ 从最近的导入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
为了赢得解决方案,我们必须堆叠所有有利条件。
a=range(-1000,1000,2);random.shuffle(a)
它,将会发现takeClosest(sorted(a), b)
速度变慢。
getClosest
每次排序都可以被多次调用,它就会更快,而且对于一次排序的用例来说,这很容易。
myList
已经是np.array
然后使用np.searchsorted
代替bisect
更快。
>>> takeClosest = lambda num,collection:min(collection,key=lambda x:abs(x-num))
>>> takeClosest(5,[4,1,88,44,3])
4
一个拉姆达是写一个“匿名”功能(即没有名称的功能)的一种特殊方式。您可以为它分配任何名称,因为lambda是一个表达式。
上面的“长篇”写法是:
def takeClosest(num,collection):
return min(collection,key=lambda x:abs(x-num))
遍历列表,然后将当前最接近的数字与进行比较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
if abs(myList[i] - myNumber) < abs(closest - myNumber): closest = myList[i];
。最好事先存储该值。
重要的是要注意,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
我不知道这会有多快,我的猜测是“不太”。
np.searchsorted
代替bisect_left
。@Kanat是正确的-Lauritz的解决方案确实包含选择两个候选人中哪个更接近的代码。
扩展了古斯塔沃·利马(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