web-dev-qa-db-fra.com

Trouver k nombres les plus proches d'un nombre donné

Disons que j'ai une liste [1,2,3,4,5,6,7]. Je veux trouver les 3 numéros les plus proches de, par exemple, 6.5. La valeur renvoyée serait alors [5,6,7].

Trouver un numéro proche n’est pas si difficile en python, ce qui peut être fait en utilisant

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

Mais j'essaie de ne pas boucler la boucle pour trouver les k nombres les plus proches. Existe-t-il un moyen pythonique pour réaliser la tâche ci-dessus?

27
Fraz

La réponse courte

La fonction heapq.nsmallest () le fera proprement et efficacement:

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

En gros, cela indique: "Donnez-moi les trois valeurs d'entrée qui présentent la différence absolue la plus faible du nombre 6,5 ".

L'algorithme et son temps d'exécution

L'algorithme pour nsmallest effectue un seul passage sur les données, en ne conservant pas plus que les valeurs n meilleures en mémoire à tout moment (cela signifie qu'il fonctionne avec tous les itérateurs d'entrée, est en cache -efficace, et peu encombrant). 

L'algorithme ajoute de nouvelles valeurs au segment de mémoire uniquement lorsqu'une nouvelle "meilleure" valeur est trouvée. En conséquence, cela minimise le nombre de comparaisons effectuées. Par exemple, si vous recherchez les 100 meilleures valeurs sur 1 000 000 d'entrées aléatoires, il effectue généralement moins de 1 008 000 comparaisons (environ 0,8% de plus que comparé à l'aide de min () pour trouver l'unique meilleure valeur).

Les fonctions touches pour min () , nsmallest () et triées () toutes garantissent que la fonction touche est appelée exactement une fois par valeur dans l'entrée itérable. Cela signifie que cette technique sera efficace pour des exemples encore plus complexes et intéressants du problème de valeur le plus proche (c'est-à-dire des mots qui se ressemblent le plus , plus proches couleurs , plus petites différences , moins mutations génétiques, distance euclidienne, etc.).

Les deux nsmallest () et sortis () renverront un classement par ordre de proximité (les égalités sont réglées en fonction de la valeur vue en premier).

Pour ceux qui sont intéressés, il existe une analyse assez complexe du nombre attendu de comparaisons ici et ici . Résumé rapide:

  • Cas moyen pour les entrées aléatoires: n + k * (log(k, 2) * log(n/k) + log(k, 2) + log(n/k))
  • Meilleur cas pour les entrées croissantes: n + k * log(k, 2)
  • Pire cas pour les entrées décroissantes: n * log(k, 2) 

Optimisation pour les recherches répétées

Dans les commentaires, @Phylliida a demandé comment optimiser les recherches répétées avec des points de départ différents. La clé consiste à pré-trier les données, puis à utiliser bissect pour localiser le centre d'un petit segment de recherche:

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

Par exemple:

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

Les deux bisect () et nsmallest () tirent parti des données triées. Le premier exécute O (log2 k) time et le dernier s'exécute dans O(n) time.

45

Vous pouvez calculer les distances et trier:

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

Cela fait ce qui suit:

  1. Créez une séquence de tuples (d, x)d est la distance qui vous sépare de votre cible
  2. Sélectionnez les premiers éléments k de cette liste.
  3. Extraire uniquement les valeurs numériques du résultat, en ignorant la distance
3
Greg Hewgill

Les deux réponses étaient bonnes et Greg avait raison. La réponse de Raymond est plus détaillée et plus facile à mettre en œuvre, mais je me suis appuyée sur la réponse de Greg car il était plus facile à manipuler pour répondre à mes besoins. 

Au cas où quelqu'un chercherait un moyen de trouver les n valeurs les plus proches à partir d'une liste de repères.

Mon dict ressemble à ceci, où npi est juste un identifiant dont j'ai besoin avec la valeur:

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

Pour obtenir une liste [id, value] (pas seulement une liste de valeurs), j'utilise ceci:

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

Qui produit ceci:

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

MODIFIER

En fait, j'ai trouvé assez facile de manipuler la réponse de Raymond aussi, si vous avez affaire à un dict (ou une liste de listes).

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

Cela produira la même chose que la sortie ci-dessus.

Et ça

nsmallest(6, snpis, key=lambda x: abs(x['value']-value)) produira un dict à la place.

1
tmthyjames