Étant donné une liste d'entiers, je veux trouver quel nombre est le plus proche d'un nombre que je donne en entrée:
>>> myList = [4, 1, 88, 44, 3]
>>> myNumber = 5
>>> takeClosest(myList, myNumber)
...
4
Y at-il un moyen rapide de faire cela?
Si nous ne sommes pas sûrs que la liste est triée, nous pourrions utiliser la fonction min()
intégrée , pour rechercher l'élément dont la distance minimale est à partir du nombre spécifié.
>>> min(myList, key=lambda x:abs(x-myNumber))
4
Notez que cela fonctionne aussi avec des dict avec des clés int, comme {1: "a", 2: "b"}
. Cette méthode prend O(n) temps.
Si la liste est déjà triée, ou si vous pouvez payer le prix du tri du tableau une seule fois, utilisez la méthode de bissection illustrée dans @ la réponse de Lauritz qui ne prend que O (log n) fois déjà trié est O(n) et le tri est O (n log n).)
Si vous voulez dire rapide à exécuter par opposition à rapide à écrire, min
devrait pas être votre arme de choix, sauf dans un cas d'utilisation très restreint. La solution min
doit examiner chaque nombre de la liste et effectuer un calcul pour chaque nombre. Utiliser bisect.bisect_left
à la place est presque toujours plus rapide.
Le "presque" vient du fait que bisect_left
exige que la liste soit triée pour fonctionner. J'espère que votre cas d'utilisation est tel que vous pouvez trier la liste une fois puis la laisser tranquille. Même si ce n'est pas le cas, tant que vous n'avez pas besoin de trier avant chaque fois que vous appelez takeClosest
, le module bisect
apparaîtra en premier. Si vous avez des doutes, essayez les deux et observez la différence dans le monde réel.
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 fonctionne en divisant de façon répétée de moitié une liste et en déterminant la moitié de la variable myNumber
en examinant la valeur du milieu. Cela signifie qu'il a une durée d'exécution de O (log n) par opposition à la durée d'exécution de O(n) de la réponse la plus votée . Si nous comparons les deux méthodes et fournissons les deux avec une myList
triée, voici les résultats:
$ python -m timeit -s " de l’importation la plus proche, takeClosest , de son importation aléatoire a = intervalle (-1000, 1000, 10)" "takeClosest (a, randint (-1100, 1100)) "" 100000 boucles, au mieux de 3: 2,22 usec par boucle $ Python -m timeit -s " De la plus proche importation avec_min De Random import Randint a = plage (-1000, 1000, 10) "" with_min (a, randint (-1100, 1100)) "" 10000 boucles, meilleur de 3: 43,9 usec par boucle
Donc, dans ce test particulier, bisect
est presque 20 fois plus rapide. Pour les listes plus longues, la différence sera plus grande.
Et si on nivelle le terrain de jeu en supprimant la condition préalable que myList
doit être trié? Supposons que nous trions une copie de la liste à chaque foistakeClosest
est appelée, tout en laissant la solution min
inchangée. En utilisant la liste des 200 éléments dans le test ci-dessus, la solution bisect
reste la plus rapide, bien qu’à 30% environ.
Ceci est un résultat étrange, étant donné que l'étape de tri est O (n log (n))! La seule raison pour laquelle min
perde encore est que le tri est effectué dans un code c hautement optimisé, alors que min
doit continuer à appeler une fonction lambda pour chaque élément. La taille de myList
augmentant, la solution min
sera finalement plus rapide. Notez que nous devions tout mettre en sa faveur pour que la solution min
gagne.
>>> takeClosest = lambda num,collection:min(collection,key=lambda x:abs(x-num))
>>> takeClosest(5,[4,1,88,44,3])
4
Un lambda est une manière spéciale d’écrire une fonction "anonyme" (une fonction qui n’a pas de nom). Vous pouvez lui attribuer le nom de votre choix car un lambda est une expression.
La "longue" façon d'écrire ce qui précède serait:
def takeClosest(num,collection):
return min(collection,key=lambda x:abs(x-num))
def closest(list, Number):
aux = []
for valor in list:
aux.append(abs(Number-valor))
return aux.index(min(aux))
Ce code vous donnera l'index du nombre le plus proche de Nombre dans la liste.
La solution proposée par KennyTM est globalement la meilleure, mais dans les cas où vous ne pouvez pas l’utiliser (comme brython), cette fonction fera le travail.
Parcourez la liste et comparez le numéro actuel le plus proche avec 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
Il est important de noter que l'idée suggérée par Lauritz d'utiliser une bissectrice ne trouve pas la valeur la plus proche dans MyList to MyNumber. Au lieu de cela, Bisect recherche la valeur suivante dans order after MyNumber dans MyList. Donc, dans le cas d'OP, vous obtiendrez en fait la position 44, au lieu de 4.
>>> myList = [1, 3, 4, 44, 88]
>>> myNumber = 5
>>> pos = (bisect_left(myList, myNumber))
>>> myList[pos]
...
44
Pour obtenir la valeur la plus proche de 5, vous pouvez essayer de convertir la liste en un tableau et d’utiliser argmin de numpy, comme cela.
>>> 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
Je ne sais pas à quelle vitesse cela serait cependant, je suppose que ce serait "pas très".
Si je peux ajouter à @ la réponse de Lauritz
Pour ne pas avoir d'erreur d'exécution .__, n'oubliez pas d'ajouter une condition avant la ligne bisect_left
:
if (myNumber > myList[-1] or myNumber < myList[0]):
return False
le code complet ressemblera à ceci:
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
Développer la réponse de Gustavo Lima. La même chose peut être faite sans créer une liste entièrement nouvelle. Les valeurs de la liste peuvent être remplacées par les différences au fur et à mesure que la boucle FOR
progresse.
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.