web-dev-qa-db-fra.com

à partir d'une liste d'entiers, obtenir le numéro le plus proche d'une valeur donnée

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

124
Ricky Robinson

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

261
kennytm

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.

118
Lauritz V. Thaulow
>>> 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))
8
Burhan Khalid
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.

6
Gustavo Lima

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
3
João Silva

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

1
jmdeamer

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

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.
0
JayJay123