整数のリストが与えられた場合、入力で指定した数値に最も近い数値を見つけたい:
>>> myList = [4, 1, 88, 44, 3]
>>> myNumber = 5
>>> takeClosest(myList, myNumber)
...
4
これを行う簡単な方法はありますか?
整数のリストが与えられた場合、入力で指定した数値に最も近い数値を見つけたい:
>>> 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の回答に示されている二分法を使用します。 (n)およびソートはO(n log n)です。
O(n)
。少しハッキングするbisect
と、大幅に改善されO(log n)
ます(入力配列がソートされている場合)。
min
ディクショナリ(items()
)上で実行し、最後に値ではなくキーを返します。
take_closest
PEP8命名規則に準拠するように関数の名前を変更します。
書くのが速いのでmin
はなく、実行するのが速いという意味の場合は、1つの非常に狭い使用例を除いて、これを選択すべきではありません。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は、リストを2等分して繰り返しmyNumber
、中央の値を見て、どちらが必要かを調べます。これは、投票数が最も多い回答のO(n)実行時間とは対照的に、O(log n)の実行時間があることを意味します。2つのメソッドを比較して、両方にソートされたを指定すると、結果は次のようになります。myList
$ python -m timeit -s " 最も近いインポートからtake_closest ランダムインポートrandintから a = range(-1000、1000、10) "" take_closest(a、randint(-1100、1100)) " 100000ループ、ベスト3:ループあたり2.22 usec $ python -m timeit -s " 最も近いインポートからwith_min ランダムインポートrandintから 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
ソリューションが勝つためには、すべてを積み重ねる必要があったことに注意してください。
a=range(-1000,1000,2);random.shuffle(a)
ことがわかりtakeClosest(sorted(a), b)
ます。
getClosest
、すべての並べ替えで複数回呼び出される可能性がある場合、これはより高速になり、並べ替え1回の使用の場合は非常に簡単です。
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
ラムダは「匿名」機能(名前を持たない関数)を書くの特別な方法です。ラムダは式なので、任意の名前を割り当てることができます。
上記の「長い」方法は次のようになります。
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の場合、実際には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
これがどれほど速くなるかはわかりませんが、私の推測では「それほど」ではないでしょう。
np.searchsorted
代わりに使用できますbisect_left
。そして、@ Kanatは正解です。Lauritzのソリューションには、2つの候補のうちどちらがより近いかを選択するコードが含まれています。
グスタボリマの答えを拡張します。まったく新しいリストを作成しなくても、同じことができます。リストの値は、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