web-dev-qa-db-ja.com

与えられた数に最も近いk個の数を見つける

リスト[1,2,3,4,5,6,7]があるとします。たとえば、6.5に最も近い3つの数字を見つけたいと思います。その場合、戻り値は[5,6,7]になります。

最も近い番号を1つ見つけることは、Pythonではそれほど難しいことではありません。

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

しかし、私はk個の最も近い数を見つけるためにこれの周りにループを置かないようにしています。上記のタスクを達成するためのPythonの方法はありますか?

28
Mohit

短い答え

heapq.nsmallest() 関数は、これをきちんと効率的に実行します。

_>>> from heapq import nsmallest
>>> s = [1,2,3,4,5,6,7]
>>> nsmallest(3, s, key=lambda x: abs(x-6.5))
[6, 7, 5]
_

基本的に、これは「数値6.5との絶対差が最も小さい3つの入力値を教えてください」と言います。

アルゴリズムとその実行時間

nsmallestのアルゴリズムは、データを1回パスし、いつでもn個の最良の値をメモリに保持します(つまり、任意の入力イテレータで動作し、キャッシュ効率が高く、スペース効率が高いということです)。

アルゴリズムは、新しい「最良の」値が見つかった場合にのみ、ヒープに新しい値を追加します。したがって、行われる比較の数が最小限に抑えられます。たとえば、1,000,000のランダム入力から100の最良の値を探している場合、通常は1,008,000未満の比較を行います( min() 単一の最良の値を見つけるため)。

キー関数 formin()nsmallest()、およびsorted()はすべて、キー関数が入力iterableの値ごとに1回だけ呼び出されることを保証します。つまり、この手法は、n-最も近い値の問題のさらに複雑で興味深い例(つまり、 最も似ている 、最も近い 、-の単語に対して効率的です。 最小の差分 、最小の遺伝子変異、ユークリッド距離など)。

nsmallest()sorted()はどちらも、近さの順に並べられたリストランクを返します(関係は、値が最初に表示されたものによって決定されます) )。

興味のある人のために、予想される比較の数のいくらか複雑な分析があります ここここ 。簡単な要約:

  • ランダム入力の平均的なケース:n + k * (log(k, 2) * log(n/k) + log(k, 2) + log(n/k))
  • 昇順入力のベストケース:n + k * log(k, 2)
  • 降順入力の最悪の場合:n * log(k, 2)

繰り返しルックアップ用に最適化

コメントの中で、@ Phylliidaは、開始点が異なる繰り返しルックアップを最適化する方法を尋ねました。重要なのは、データを事前に並べ替えてから、 bisect を使用して小さな検索セグメントの中心を見つけることです。

_from bisect import bisect

def k_nearest(k, center, sorted_data):
    'Return *k* members of *sorted_data* nearest to *center*'
    i = bisect(sorted_data, center)
    segment = sorted_data[max(i-k, 0) : i+k]
    return nsmallest(k, segment, key=lambda x: abs(x - center))
_

例えば:

_>>> s.sort()
>>> k_nearest(3, 6.5, s)
[6, 7, 5]
>>> k_nearest(3, 0.5, s)
[1, 2, 3]
>>> k_nearest(3, 4.5, s)    
[4, 5, 3]
>>> k_nearest(3, 5.0, s)
[5, 4, 6]
_

bisect()nsmallest()はどちらも、ソートされたデータを利用します。前者はO(log2 k)時間で実行され、後者はO(n)時間で実行されます。

45

距離を計算して、次のように並べ替えることができます。

[n for d, n in sorted((abs(x-myNumber), x) for x in myList)[:k]]

これは次のことを行います。

  1. タプルのシーケンスを作成します(d, x)ここで、dはターゲットまでの距離です
  2. そのリストの最初のk要素を選択します
  3. 結果から数値のみを抽出し、距離を破棄します
3
Greg Hewgill

どちらの答えも良かったし、グレッグは正しかった。レイモンドの答えはより高レベルで実装が簡単だったが、ニーズに合わせて操作するのが簡単だったので、グレッグの答えに基づいて構築した。

誰かがdictのリストからn個の最も近い値を見つける方法を探している場合。

私のdictは次のようになります。ここで、npiは、値とともに必要な単なる識別子です。

_mydict = {u'fnpi': u'1982650024',
 u'snpi': {u'npi': u'1932190360', u'value': 2672},
 u'snpis': [{u'npi': u'1831289255', u'value': 20},
  {u'npi': u'1831139799', u'value': 20},
  {u'npi': u'1386686137', u'value': 37},
  {u'npi': u'1457355257', u'value': 45},
  {u'npi': u'1427043645', u'value': 53},
  {u'npi': u'1477548675', u'value': 53},
  {u'npi': u'1851351514', u'value': 57},
  {u'npi': u'1366446171', u'value': 60},
  {u'npi': u'1568460640', u'value': 75},
  {u'npi': u'1326046673', u'value': 109},
  {u'npi': u'1548281124', u'value': 196},
  {u'npi': u'1912989989', u'value': 232},
  {u'npi': u'1336147685', u'value': 284},
  {u'npi': u'1801894142', u'value': 497},
  {u'npi': u'1538182779', u'value': 995},
  {u'npi': u'1932190360', u'value': 2672},
  {u'npi': u'1114020336', u'value': 3264}]}

value = mydict['snpi']['value'] #value i'm working with below
npi = mydict['snpi']['npi'] #npi (identifier) i'm working with below
snpis = mydict['snpis'] #dict i'm working with below
_

_[id, value]_リスト(値のリストだけでなく)を取得するには、次を使用します。

_[[id,val] for diff, val, id in sorted((abs(x['value']-value), x['value'], x['npi']) for x in snpis)[:6]]
_

これを生成します:

_[[u'1932190360', 2672],
 [u'1114020336', 3264],
 [u'1538182779', 995],
 [u'1801894142', 497],
 [u'1336147685', 284],
 [u'1912989989', 232]]
_

[〜#〜]編集[〜#〜]

Dict(またはリストのリスト)を扱っている場合、レイモンドの答えを操作するのも非常に簡単であることが実際にわかりました。

_from heapq import nsmallest
[[i['npi'], i['value']] for i in nsmallest(6, snpis, key=lambda x: abs(x['value']-value))]
_

これにより、上記の出力と同じ結果が生成されます。

この

nsmallest(6, snpis, key=lambda x: abs(x['value']-value))は代わりにdictを生成します。

1
tmthyjames