整数のリストから、指定された値に最も近い数値を取得します


158

整数のリストが与えられた場合、入力で指定した数値に最も近い数値を見つけたい:

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

これを行う簡単な方法はありますか?


2
これがリストで発生したインデックスも返すのはどうですか?
チャーリーパーカー


1
@ sancho.sきちんと見つけた。この質問への回答は、他の質問の回答よりもはるかに優れていますが。だから、私はこれを複製して、もう一方を閉じるように投票するつもりです。
ジャン=フランソワ・コルベット

回答:


326

リストがソートされているかどうかわからない場合は、組み込み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)です。


13
複雑さといえば、これはですO(n)。少しハッキングするbisectと、大幅に改善されO(log n)ます(入力配列がソートされている場合)。
mic_e

5
@mic_e:それはラウリッツの答えです。
kennytm

3
リストでこれが起こったインデックスも返すのはどうですか?
チャーリーパーカー

@CharlieParkerの独自の実装を作成し、リストではなくminディクショナリ(items())上で実行し、最後に値ではなくキーを返します。
ダスティンオプレア2016年

2
または、numpy.argmin代わりにminを使用して、値の代わりにインデックスを取得します。

148

take_closestPEP8命名規則に準拠するように関数の名前を変更します。

書くのが速いのでminなく、実行するのが速いという意味の場合は、1つの非常に狭い使用例を除いて、これを選択すべきでありませminソリューションは、リスト内のすべての番号を調べる必要各番号についての計算を行います。bisect.bisect_left代わりに使用すると、ほとんど常に高速です。

「ほぼ」はbisect_left、リストを機能させるためにソートする必要があるという事実から来ています。うまくいけば、あなたのユースケースは、リストを一度ソートして、それをそのままにしておくことができるようなものです。そうでない場合でも、を呼び出すたびにソートする必要がない限りtake_closestbisectモジュールはおそらく最上位に表示されます。疑問がある場合は、両方を試して実際の違いを確認してください。

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_closestminbisect

ソート手順がO(n log(n))であることを考えると、これは奇妙な結果です。それminでも失われている唯一の理由は、並べ替えが高度に最適化されたcコードで行われる一方でmin、すべてのアイテムのラムダ関数の呼び出しに沿って処理する必要があることです。通りmyListのサイズに成長し、min解決策は最終的に速くなります。minソリューションが勝つためには、すべてを積み重ねる必要があったことに注意してください。


2
ソート自体はO(N log N)を必要とするため、Nが大きくなると遅くなります。たとえば、使用すると、速度が遅くなるa=range(-1000,1000,2);random.shuffle(a)ことがわかりtakeClosest(sorted(a), b)ます。
kennytm 2012

3
@KennyTM私はあなたにそれを許可します、そして私は私の答えでそれを指摘します。ただしgetClosest、すべての並べ替えで複数回呼び出される可能性がある場合、これはより高速になり、並べ替え1回の使用の場合は非常に簡単です。
Lauritz V. Thaulow 2012

リストでこれが起こったインデックスも返すのはどうですか?
チャーリーパーカー

の場合myListは、代わりにnp.array使用np.searchsortedするbisect方が高速です。
マイケルホール

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

2
ただし、名前にラムダを割り当てることは、PEP 8に従って推奨されていません。
Evert Heylen 2017年

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

    return aux.index(min(aux))

このコードは、リスト内の最も近い番号のインデックスを提供します。

KennyTMによって与えられた解決策は全体的に最高ですが、それを使用できない場合(brythonなど)、この関数は機能します


5

リストを反復処理し、現在の最も近い数をと比較し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
インデックスを返すこともできます。
チャーリーパーカー

1
!不正解です。する必要がありますif abs(myList[i] - myNumber) < abs(closest - myNumber): closest = myList[i];。ただし、事前にその値を保存することをお勧めします。
lk_vc 2018

確かに、この関数はすでに最も近いインデックスを返します。OPの要件を満たすためには、最後の2行目が最も近い= myList [i]
Paula Livingstone

2

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

これがどれほど速くなるかはわかりませんが、私の推測では「それほど」ではないでしょう。


2
ラウリッツの関数は正しく動作します。あなたはbisect_leftだけを使用していますが、Lauritzは追加のチェックを行う関数takeClosest(...)を提案しました。
Kanat

NumPyを使用する場合は、のnp.searchsorted代わりに使用できますbisect_left。そして、@ Kanatは正解です。Lauritzのソリューションに、2つの候補のうちどちらがより近いかを選択するコード含まれています。
John Y

1

グスタボリマの答えを拡張します。まったく新しいリストを作成しなくても、同じことができます。リストの値は、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.

1

@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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.