web-dev-qa-db-ja.com

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

整数のリストが与えられたら、入力で指定した数に最も近い数を見つけたいと思います。

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

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

134
Ricky Robinson

リストがソートされているかどうかわからない場合は、 組み込みmin() function を使用して、指定された数からの最小距離を持つ要素を見つけることができます。

>>> min(myList, key=lambda x:abs(x-myNumber))
4

{1: "a", 2: "b"}のようなintキーを持つ辞書でも動作することに注意してください。このメソッドはO(n)時間かかります。


リストが既に並べ替えられている場合、または配列を1回だけ並べ替える代価を支払うことができる場合は、 @ Lauritz's answer に示す2分法を使用します。これはO(log n)時間しかかかりません(ただし、リストは既にソートされていますO(n)で、ソートはO(n log n)です。

282
kennytm

クイック書き込みではなくクイック実行を意味する場合、minは、1つの非常に狭い用途を除いて、notの武器ではありません場合。 minソリューションは、リスト内のすべての数値を調べる必要がありますand各数値に対して計算を実行します。代わりに bisect.bisect_left を使用すると、ほとんど常に高速になります。

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

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.
    """
    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)最高得票数 の実行時間。 2つのメソッドを比較し、ソートされたmyListを両方に指定すると、結果は次のようになります。

 $ python -m timeit -s "
 from最も近いインポートtakeClosest 
 fromランダムインポートrandint 
 a = range(-1000、1000 、10) "" takeClosest(a、randint(-1100、1100)) "" ..____。] 
 100000ループ、3の最高:2.22 usec per loop 
 
 $ python -m timeit -s "
 from from最も近いインポートwith_min 
 from from random import randint 
 a = range(-1000、1000、10)" "with_min (a、randint(-1100、1100)) "[..____。] 
 10000ループ、ベスト3:ループごとに43.9 usec 

したがって、この特定のテストでは、bisectはほぼ20倍高速です。リストが長いほど、差は大きくなります。

myListを並べ替える必要があるという前提条件を削除して、競技場を平準化するとどうなりますか?リストのコピーをソートするとします毎回takeClosestが呼び出され、minソリューションは変更されません。上記のテストで200項目のリストを使用すると、bisectソリューションは約30%だけですが、最速です。

これは、ソート手順がO(n log(n))であることを考えると、奇妙な結果です! minがまだ失われている唯一の理由は、ソートが高度に最適化されたcコードで行われるのに対し、minはすべての項目に対してラムダ関数を呼び出す必要があるためです。 myListのサイズが大きくなると、最終的にminソリューションが高速になります。 minソリューションが勝つためには、すべてを積み重ねなければならないことに注意してください。

126
>>> 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))
8
Burhan Khalid
def closest(list, Number):
    aux = []
    for valor in list:
        aux.append(abs(Number-valor))

    return aux.index(min(aux))

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

KennyTMが提供するソリューションは全体的に最適ですが、使用できない場合(brythonなど)、この機能は機能します

6
Gustavo Lima

リストを反復処理し、現在の最も近い数を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
3
João Silva

Biectを使用するというLauritzの提案のアイデアは、実際にはMyListでMyNumberに最も近い値を見つけられないことに注意することが重要です。代わりに、bisectは、MyListのMyNumberの後のorderで次の値を見つけます。したがって、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
jmdeamer

@ 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
0
umn

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.
0
JayJay123