给定一个整数列表,我想找出哪个数字最接近我在输入中给出的数字:
>>> myList = [4, 1, 88, 44, 3]
>>> myNumber = 5
>>> takeClosest(myList, myNumber)
...
4
有什么快速的方法可以做到这一点吗?
如果我们不确定列表是否已排序,我们可以使用 built-in min()
function 来查找与指定数字距离最小的元素。
>>> min(myList, key=lambda x:abs(x-myNumber))
4
请注意,它也适用于具有 int 键的 dicts,例如 {1: "a", 2: "b"}
。此方法需要 O(n) 时间。
如果列表已经排序,或者您可以只对数组进行一次排序,请使用 @Lauritz's answer 中说明的二等分方法,它只需要 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),而 highest voted answer 的运行时间为 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
必须排序的先决条件来平衡竞争环境会怎样?假设我们每次调用 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 是编写“匿名”函数(没有名称的函数)的一种特殊方式。您可以为其指定任何您想要的名称,因为 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
if abs(myList[i] - myNumber) < abs(closest - myNumber): closest = myList[i];
。不过最好事先存储该值。
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])
需要注意的是,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 的解决方案 确实 包含选择两个候选者中哪个更接近的代码。
扩展古斯塔沃利马的回答。无需创建全新列表即可完成相同的操作。随着 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's answer
为了不出现运行错误,不要忘记在 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)
不定期副业成功案例分享
O(n)
,对bisect
稍加修改就会大大改进O(log n)
(如果您的输入数组已排序)。min
实现,通过字典 (items()
) 而不是列表运行它,最后返回键而不是值。numpy.argmin
而不是min
来获取索引而不是值。