整数のリストを指定して、入力で指定した数値に最も近い数値を見つけたいと思います。
>>> myList = [4, 1, 88, 44, 3]
>>> myNumber = 5
>>> takeClosest(myList, myNumber)
...
4
これを行う簡単な方法はありますか?
リストがソートされているかどうかわからない場合は、組み込みmin()
関数を使用して、指定された数からの距離が最小の要素を見つけることができます。
>>> min(myList, key=lambda x:abs(x-myNumber))
4
のようなintキーを使用したdictでも機能することに注意してください{1: "a", 2: "b"}
。この方法にはO(n)時間がかかります。
リストがすでに並べ替えられている場合、または配列を1回だけ並べ替える代償を払うことができる場合は、@ Lauritzの回答に示されている二分法を使用します。これはO(log n)時間しかかかりません(ただし、リストが既に並べ替えられているかどうかの確認はOです。 (n)およびソートはO(n log n)です。)
take_closest
PEP8の命名規則に準拠するように、関数の名前を変更します。
すぐに書くのではなく、すぐに実行することを意味する場合は、1つの非常に狭いユースケースを除いて、選択する武器と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(n)実行時間とは対照的に、実行時間がO(log n)であることを意味します。2つの方法を比較し、両方に並べ替えを指定すると、次のような結果になります。myList
$ python -m timeit -s " 最も近いインポートからtake_closest ランダムインポートランディンから a = range(-1000、1000、10) "" take_closest(a、randint(-1100、1100)) " 100000ループ、ベスト3:ループあたり2.22 usec $ python -m timeit -s " 最も近いインポートwith_minから ランダムインポートランディンから a = range(-1000、1000、10) "" with_min(a、randint(-1100、1100)) " 10000ループ、ベスト3:ループあたり43.9 usec
したがって、この特定のテストでbisect
は、ほぼ20倍高速です。リストが長いほど、違いは大きくなります。
myList
ソートする必要のある前提条件を削除して、競技場を平準化するとどうなりますか?ソリューションを変更せずに、が呼び出されるたび にリストのコピーを並べ替えるとします。上記のテストで200項目のリストを使用すると、ソリューションは依然として最速ですが、約30%しかありません。take_closest
min
bisect
ソートステップがO(n log(n))であることを考えると、これは奇妙な結果です。唯一の理由min
は、ソートが高度に最適化されたcコードで行われる一方min
で、すべてのアイテムに対してラムダ関数を呼び出すことに沿って実行する必要があることです。サイズが大きくなるにつれてmyList
、min
ソリューションは最終的に高速になります。min
ソリューションが勝つためには、すべてを積み重ねなければならなかったことに注意してください。
>>> takeClosest = lambda num,collection:min(collection,key=lambda x:abs(x-num))
>>> takeClosest(5,[4,1,88,44,3])
4
ラムダは、「無名」関数(名前のない関数)を作成するための特別な方法です。ラムダは式であるため、任意の名前を割り当てることができます。
上記を書く「長い」方法は次のようになります。
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
def closest(list, Number):
aux = []
for valor in list:
aux.append(abs(Number-valor))
return aux.index(min(aux))
このコードは、リスト内の最も近い番号のインデックスを提供します。
KennyTMによって提供されるソリューションは全体的に最高ですが、それを使用できない場合(brythonなど)、この関数が機能します
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の提案のアイデアは、実際にはMyListでMyNumberに最も近い値を見つけられないことに注意することが重要です。代わりに、bisectは、MyListのMyNumberの次の値を順番に検索します。したがって、OPの場合、実際には4の位置ではなく44の位置が返されます。
>>> 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
これがどれほど速いかはわかりませんが、私の推測では「それほどではない」でしょう。
グスタボ・リマの答えを拡張します。まったく新しいリストを作成しなくても、同じことができます。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)