web-dev-qa-db-fra.com

trouver l'index d'un élément le plus proche de la valeur d'une liste qui n'est pas entièrement triée

À titre d'exemple, ma liste est la suivante:

[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]

et je cherche l'indice de la valeur la plus proche de 11.5. J'ai essayé d'autres méthodes telles que la recherche binaire et bisect_left mais elles ne fonctionnent pas.

Je ne peux pas trier ce tableau, car l'index de la valeur sera utilisé sur un tableau similaire pour récupérer la valeur à cet index.

47
emad

Essayez ce qui suit:

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

Par exemple:

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

Ou pour obtenir l'index et la valeur:

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

Que diriez-vous: vous zippez les deux listes, puis vous triez le résultat?

2
Marcin

Si vous ne pouvez pas trier le tableau, il n'y a pas de moyen rapide de trouver l'élément le plus proche - vous devez effectuer une itération sur toutes les entrées.

Il existe une solution de contournement mais elle demande beaucoup de travail: écrivez un algorithme de tri qui trie le tableau et (en même temps) met à jour un second tableau qui vous indique où cette entrée était avant le tableau a été trié.

De cette façon, vous pouvez utiliser la recherche binaire pour rechercher l'index de l'entrée la plus proche, puis utiliser cet index pour rechercher l'index d'origine à l'aide du "tableau d'index".

[EDIT] Avec Zip(), c'est assez simple à réaliser:

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

Vous pouvez maintenant effectuer une recherche binaire de la valeur (en utilisant item[0]). item[1] vous donnera l'index original.

2
Aaron Digulla

Traverser tous les articles n’est que linéaire. Si vous voulez trier le tableau, ce serait pire.

Je ne vois pas d'inconvénient à garder une deltax supplémentaire (la différence minimale jusqu'à présent) et une idx (l'index de cet élément) et à ne parcourir qu'une fois la liste.

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

Dans le cas où a est déjà un tableau, la transformation correspondante peut être omise.

2
Carsten König

Si vous recherchez souvent une longue liste, alors min échelles très mauvaises (O (n) ou même O (n ^ 2) si vous ajoutez certaines de vos recherches à la liste, je pense) . Bisect est ton ami. Voici ma solution. Il met à l'échelle O(log(n)) dans le pire des cas:

class Closest:
    """Assumes *no* redundant entries - all inputs must be unique"""
    def __init__(self, numlist=[], firstdistance=0):
        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

Utilisez-le comme ceci:

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]])
1
Holger Bille

Gardez à l'esprit que si l'espace n'est pas important, vous pouvez trier n'importe quelle liste sans déplacer le contenu en créant une liste secondaire des index triés. 

N'oubliez pas non plus que si vous le faites, il vous suffit de parcourir tous les éléments de la liste O (n). (Si plusieurs fois, vous voudrez probablement trier pour augmenter l'efficacité ultérieurement)

1
NominSim