@ sancho.s Bien repéré. Bien que les réponses à cette question soient bien meilleures que celles sur cette autre question. Je vais donc voter pour fermer l'autre en double de celui-ci.
Jean-François Corbett
Réponses:
327
Si nous ne sommes pas sûrs que la liste est triée, nous pourrions utiliser le haut- min()fonction , pour trouver l'élément qui a la distance minimale du nombre spécifié.
>>> min(myList, key=lambda x:abs(x-myNumber))4
Notez que cela fonctionne également avec les dictionnaires avec des clés int, comme {1: "a", 2: "b"}. Cette méthode prend un temps O (n).
Si la liste est déjà triée, ou si vous pourriez 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) temps (notez cependant que vérifier si une liste est déjà triée est O (n) et le tri est O (n log n).)
Parlant de complexité, c'est O(n)là qu'un petit piratage bisectvous apportera une amélioration considérable O(log n)(si votre tableau d'entrée est trié).
qu'en est-il également de renvoyer l'index que cela s'est produit dans la liste?
Charlie Parker
@CharlieParker Créez votre propre implémentation de min, exécutez-la sur un dictionnaire ( items()) au lieu d'une liste et renvoyez la clé au lieu de la valeur à la fin.
Dustin Oprea
2
Ou utilisez numpy.argminau lieu de minpour obtenir l'index au lieu de la valeur.
148
Je renommerai la fonction take_closestpour se conformer aux conventions de dénomination PEP8.
Si vous voulez dire rapide à exécuter par opposition à rapide à écrire, cela nemin devrait pas être votre arme de choix, sauf dans un cas d'utilisation très restreint. La minsolution doit examiner chaque nombre de la liste et effectuer un calcul pour chaque nombre. Utiliser à la bisect.bisect_leftplace est presque toujours plus rapide.
Le «presque» vient du fait que bisect_leftla liste doit être triée pour fonctionner. Espérons 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 appel take_closest, le bisectmodule sortira probablement en tête. En cas de doute, essayez les deux et regardez la différence dans le monde réel.
from bisect import bisect_left
def take_closest(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 à plusieurs reprises une liste et en trouvant la moitié myNumberen regardant la valeur moyenne. Cela signifie qu'il a un temps d'exécution de O (log n) par opposition au temps d'exécution O (n) de la réponse votée la plus élevée . Si nous comparons les deux méthodes et fournissons les deux avec un trié myList, voici les résultats:
$ python -m timeit -s "
depuis l'importation la plus proche take_closest
à partir de randint d'importation aléatoire
a = range (-1000, 1000, 10) "" take_closest (a, randint (-1100, 1100)) "
100000 boucles, meilleur de 3: 2,22 usec par boucle
$ python -m timeit -s "
depuis l'importation la plus proche avec_min
à partir de randint d'importation aléatoire
a = range (-1000, 1000, 10) "" with_min (a, randint (-1100, 1100)) "
10000 boucles, meilleur de 3: 43,9 usec par boucle
Donc, dans ce test particulier, bisectc'est presque 20 fois plus rapide. Pour les listes plus longues, la différence sera plus grande.
Et si nous uniformisons les règles du jeu en supprimant la condition préalable qui myListdoit être triée? Disons que nous trions une copie de la liste à chaquetake_closest appel, tout en laissant la minsolution inchangée. En utilisant la liste de 200 éléments dans le test ci-dessus, la bisectsolution est toujours la plus rapide, mais seulement d'environ 30%.
C'est un résultat étrange, étant donné que l'étape de tri est O (n log (n)) ! La seule raison de minperdre est que le tri est effectué dans un code C hautement minoptimisé, tout en appelant une fonction lambda pour chaque élément. Au myListfur et à mesure que la taille augmente, la minsolution sera éventuellement plus rapide. Notez que nous avons dû tout mettre en sa faveur pour que la minsolution l'emporte.
Le tri lui-même nécessite O (N log N), il sera donc plus lent lorsque N devient grand. Par exemple, si vous utilisez, a=range(-1000,1000,2);random.shuffle(a)vous constaterez que takeClosest(sorted(a), b)cela deviendra plus lent.
kennytm
3
@KennyTM Je vous l'accorde, et je le souligne dans ma réponse. Mais tant que longtemps getClosestpeut être appelé plus d'une fois pour chaque tri, ce sera plus rapide, et pour le cas d'utilisation de tri unique, c'est une évidence.
Lauritz V. Thaulow
qu'en est-il également de renvoyer l'index que cela s'est produit dans la liste?
Charlie Parker
Si myListest déjà un np.arrayalors utiliser np.searchsortedà la place debisect est plus rapide.
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.
Notez cependant que l'attribution de lambda à des noms est déconseillée selon PEP 8 .
Evert Heylen
6
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 Number dans la liste.
La solution donnée par KennyTM est la meilleure dans l'ensemble, mais dans les cas où vous ne pouvez pas l'utiliser (comme brython), cette fonction fera le travail
! Incorrect ! Devrait être if abs(myList[i] - myNumber) < abs(closest - myNumber): closest = myList[i];. Mieux vaut cependant stocker cette valeur à l'avance.
lk_vc
La fonction telle qu'elle est renvoie sûrement déjà l'indice du plus proche. Pour qu'il réponde aux exigences de l'OP, la deuxième dernière ligne ne devrait pas se lire le plus proche = maListe [i]
Paula Livingstone
2
Il est important de noter que l'idée de suggestion de Lauritz d'utiliser la bissectrice ne trouve pas réellement la valeur la plus proche dans MyList de MyNumber. Au lieu de cela, bisect trouve la valeur suivante dans l' ordre après MyNumber dans MyList. Donc, dans le cas d'OP, vous obtiendrez en fait la position 44 retournée au lieu de la position 4.
La fonction de Lauritz fonctionne correctement. Vous n'utilisez que bisect_left mais Lauritz a suggéré une fonction takeClosest (...) qui effectue une vérification supplémentaire.
Kanat
Si vous comptez utiliser NumPy, vous pouvez utiliser à la np.searchsortedplace de bisect_left. Et @Kanat est juste - la solution de Lauritz n'inclure le code qui picks lequel des deux candidats est plus proche.
John Y
1
Développant 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érentiels au fur et à mesure de la FORprogression de la boucle.
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 =5print(f_ClosestVal(myList, v_Num))## Gives "3," the index of the first "4" in the list.
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]):returnFalse
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
Réponses:
Si nous ne sommes pas sûrs que la liste est triée, nous pourrions utiliser le haut-
min()
fonction , pour trouver l'élément qui a la distance minimale du nombre spécifié.Notez que cela fonctionne également avec les dictionnaires avec des clés int, comme
{1: "a", 2: "b"}
. Cette méthode prend un temps O (n).Si la liste est déjà triée, ou si vous pourriez 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) temps (notez cependant que vérifier si une liste est déjà triée est O (n) et le tri est O (n log n).)
la source
O(n)
là qu'un petit piratagebisect
vous apportera une amélioration considérableO(log n)
(si votre tableau d'entrée est trié).min
, exécutez-la sur un dictionnaire (items()
) au lieu d'une liste et renvoyez la clé au lieu de la valeur à la fin.numpy.argmin
au lieu demin
pour obtenir l'index au lieu de la valeur.Je renommerai la fonction
take_closest
pour se conformer aux conventions de dénomination PEP8.Si vous voulez dire rapide à exécuter par opposition à rapide à écrire, cela ne
min
devrait pas être votre arme de choix, sauf dans un cas d'utilisation très restreint. Lamin
solution doit examiner chaque nombre de la liste et effectuer un calcul pour chaque nombre. Utiliser à labisect.bisect_left
place est presque toujours plus rapide.Le «presque» vient du fait que
bisect_left
la liste doit être triée pour fonctionner. Espérons 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 appeltake_closest
, lebisect
module sortira probablement en tête. En cas de doute, essayez les deux et regardez la différence dans le monde réel.Bisect fonctionne en divisant à plusieurs reprises une liste et en trouvant la moitié
myNumber
en regardant la valeur moyenne. Cela signifie qu'il a un temps d'exécution de O (log n) par opposition au temps d'exécution O (n) de la réponse votée la plus élevée . Si nous comparons les deux méthodes et fournissons les deux avec un triémyList
, voici les résultats:Donc, dans ce test particulier,
bisect
c'est presque 20 fois plus rapide. Pour les listes plus longues, la différence sera plus grande.Et si nous uniformisons les règles du jeu en supprimant la condition préalable qui
myList
doit être triée? Disons que nous trions une copie de la liste à chaquetake_closest
appel, tout en laissant lamin
solution inchangée. En utilisant la liste de 200 éléments dans le test ci-dessus, labisect
solution est toujours la plus rapide, mais seulement d'environ 30%.C'est un résultat étrange, étant donné que l'étape de tri est O (n log (n)) ! La seule raison de
min
perdre est que le tri est effectué dans un code C hautementmin
optimisé, tout en appelant une fonction lambda pour chaque élément. AumyList
fur et à mesure que la taille augmente, lamin
solution sera éventuellement plus rapide. Notez que nous avons dû tout mettre en sa faveur pour que lamin
solution l'emporte.la source
a=range(-1000,1000,2);random.shuffle(a)
vous constaterez quetakeClosest(sorted(a), b)
cela deviendra plus lent.getClosest
peut être appelé plus d'une fois pour chaque tri, ce sera plus rapide, et pour le cas d'utilisation de tri unique, c'est une évidence.myList
est déjà unnp.array
alors utilisernp.searchsorted
à la place debisect
est plus rapide.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:
la source
Ce code vous donnera l'index du nombre le plus proche de Number dans la liste.
La solution donnée par KennyTM est la meilleure dans l'ensemble, mais dans les cas où vous ne pouvez pas l'utiliser (comme brython), cette fonction fera le travail
la source
Parcourez la liste et comparez le nombre actuel le plus proche avec
abs(currentNumber - myNumber)
:la source
if abs(myList[i] - myNumber) < abs(closest - myNumber): closest = myList[i];
. Mieux vaut cependant stocker cette valeur à l'avance.Il est important de noter que l'idée de suggestion de Lauritz d'utiliser la bissectrice ne trouve pas réellement la valeur la plus proche dans MyList de MyNumber. Au lieu de cela, bisect trouve la valeur suivante dans l' ordre après MyNumber dans MyList. Donc, dans le cas d'OP, vous obtiendrez en fait la position 44 retournée au lieu de la position 4.
Pour obtenir la valeur la plus proche de 5, vous pouvez essayer de convertir la liste en tableau et d'utiliser argmin de numpy comme ça.
Je ne sais pas à quelle vitesse ce serait cependant, je pense que "pas très".
la source
np.searchsorted
place debisect_left
. Et @Kanat est juste - la solution de Lauritz n'inclure le code qui picks lequel des deux candidats est plus proche.Développant 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érentiels au fur et à mesure de la
FOR
progression de la boucle.la source
Si je peux ajouter à la réponse de @ Lauritz
Afin de ne pas avoir d'erreur d'exécution, n'oubliez pas d'ajouter une condition avant la
bisect_left
ligne:donc le code complet ressemblera à:
la source