web-dev-qa-db-ja.com

完全にソートされていないリスト内の値に最も近いアイテムのインデックスを見つける

例として私のリストは次のとおりです。

[25.75443, 26.7803, 25.79099, 24.17642, 24.3526, 22.79056, 20.84866, 19.49222, 18.38086, 18.0358, 16.57819, 15.71255, 14.79059, 13.64154, 13.09409, 12.18347, 11.33447, 10.32184, 9.544922, 8.813385, 8.181152, 6.983734, 6.048035, 5.505096, 4.65799]

そして、11.5に最も近い値のインデックスを探しています。バイナリ検索やbisect_leftなどの他の方法を試しましたが、機能しません。

値のインデックスは、そのインデックスで値を取得するために同様の配列で使用されるため、この配列を並べ替えることはできません。

50
emad

以下を試してください:

min(range(len(a)), key=lambda i: abs(a[i]-11.5))

例えば:

>>> a = [25.75443, 26.7803, 25.79099, 24.17642, 24.3526, 22.79056, 20.84866, 19.49222, 18.38086, 18.0358, 16.57819, 15.71255, 14.79059, 13.64154, 13.09409, 12.18347, 11.33447, 10.32184, 9.544922, 8.813385, 8.181152, 6.983734, 6.048035, 5.505096, 4.65799]
>>> min(range(len(a)), key=lambda i: abs(a[i]-11.5))
16

または、インデックスと値を取得するには:

>>> min(enumerate(a), key=lambda x: abs(x[1]-11.5))
(16, 11.33447)
119
Andrew Clark

方法:2つのリストを圧縮して、結果を並べ替えますか?

2
Marcin

配列を並べ替えることができない場合、最も近いアイテムをすばやく見つける方法はありません。すべてのエントリを反復処理する必要があります。

回避策はありますが、かなり手間がかかります。配列をソートするソートアルゴリズムを記述し、(同時に)2番目の配列を更新して、このエントリがbefore配列がソートされました。

このように、バイナリ検索を使用して最も近いエントリのインデックスを検索し、このインデックスを使用して「インデックス配列」を使用して元のインデックスを検索できます。

[EDIT]Zip()を使用すると、これは簡単に実現できます。

 array_to_sort = Zip( original_array, range(len(original_array)) )
 array_to_sort.sort( key=i:i[0] )

これで、値をバイナリ検索できます(item[0])。 item[1]は元のインデックスを提供します。

2
Aaron Digulla

すべてのアイテムをたどるのは線形にすぎません。配列を並べ替えると、さらに悪化します。

追加のdeltax(これまでの最小差)とidx(その要素のインデックス)を保持して、リストを1回ループするだけで問題が発生することはありません。

2
Bogdan
import numpy as np

a = [25.75443, 26.7803, 25.79099, 24.17642, 24.3526, 22.79056, 20.84866, 19.49222, 18.38086, 18.0358, 16.57819, 15.71255, 14.79059, 13.64154, 13.09409, 12.18347, 11.33447, 10.32184, 9.544922, 8.813385, 8.181152, 6.983734, 6.048035, 5.505096, 4.65799]

index = np.argmin(np.abs(np.array(a)-11.5))
a[index] # here is your result

Aが既に配列の場合、対応する変換は省略できます。

2
Carsten König

スペースが重要でない場合は、ソートされたインデックスのセカンダリリストを作成して、コンテンツを移動せずにリストをソートできることに注意してください。

また、このルックアップを1回だけ行う場合は、リストO(n)内のすべての要素をトラバースするだけでよいことに注意してください。 (複数回の場合、後で効率を上げるためにソートすることをお勧めします)

1
NominSim

長いリストを何度も検索する場合、minのスケーリングは非常に悪くなります(検索リストに検索の一部を追加すると、O(n ^ 2)になると思います)。

Bisectはあなたの友達です。これが私の解決策です。 O(n * log(n))をスケーリングします:

class Closest:
    """Assumes *no* redundant entries - all inputs must be unique"""
    def __init__(self, numlist=None, firstdistance=0):
        if numlist == None:
            numlist=[]
        self.numindexes = dict((val, n) for n, val in enumerate(numlist))
        self.nums = sorted(self.numindexes)
        self.firstdistance = firstdistance

    def append(self, num):
        if num in self.numindexes:
            raise ValueError("Cannot append '%i' it is already used" % num)
        self.numindexes[num] = len(self.nums)
        bisect.insort(self.nums, num)

    def rank(self, target):
        rank = bisect.bisect(self.nums, target)
        if rank == 0:
            pass
        Elif len(self.nums) == rank:
            rank -= 1
        else:
            dist1 = target - self.nums[rank - 1]
            dist2 = self.nums[rank] - target
            if dist1 < dist2:
                rank -= 1
        return rank

    def closest(self, target):
        try:
            return self.numindexes[self.nums[self.rank(target)]]
        except IndexError:
            return 0

    def distance(self, target):
        rank = self.rank(target)
        try:
            dist = abs(self.nums[rank] - target)
        except IndexError:
            dist = self.firstdistance
        return dist

次のように使用します。

a = [25.75443, 26.7803, 25.79099, 24.17642, 24.3526, 22.79056, 20.84866, 19.49222, 18.38086, 18.0358, 16.57819, 15.71255, 14.79059, 13.64154, 13.09409, 12.18347, 11.33447, 10.32184, 9.544922, 8.813385, 8.181152, 6.983734, 6.048035, 5.505096, 4.65799]
cl = Closest(a)
for x in targets:
    rank = cl.rank(x)
    print("Closest number:", cl.nums[rank])
    print("Closest index:",  cl.numindexes[cl.nums[rank]])
0
Holger Bille