Dato un elenco di numeri interi, voglio trovare quale numero è il più vicino a un numero che do in input:
>>> myList = [4, 1, 88, 44, 3]
>>> myNumber = 5
>>> takeClosest(myList, myNumber)
...
4
C'è un modo rapido per farlo?
Dato un elenco di numeri interi, voglio trovare quale numero è il più vicino a un numero che do in input:
>>> myList = [4, 1, 88, 44, 3]
>>> myNumber = 5
>>> takeClosest(myList, myNumber)
...
4
C'è un modo rapido per farlo?
Risposte:
Se non siamo sicuri che la lista è ordinata, potremmo usare il built-in min()
funzione , di trovare l'elemento che ha la distanza minima dal numero specificato.
>>> min(myList, key=lambda x:abs(x-myNumber))
4
Nota che funziona anche con dicts con i tasti int, come {1: "a", 2: "b"}
. Questo metodo richiede O (n) tempo.
Se l'elenco è già ordinato o potresti pagare il prezzo di ordinamento dell'array una sola volta, usa il metodo di bisection illustrato nella risposta di @ Lauritz che impiega solo O (log n) tempo (nota tuttavia che verificare se un elenco è già ordinato è O (n) e l'ordinamento è O (n log n).)
O(n)
, dove un po 'di hacking bisect
ti darà un enorme miglioramento O(log n)
(se il tuo array di input è ordinato).
min
, eseguila su un dizionario ( items()
) anziché su un elenco e restituisce la chiave invece del valore alla fine.
take_closest
Rinominerò la funzione in conformità con le convenzioni di denominazione PEP8.
Se intendi eseguire rapidamente l'esecuzione invece di scrivere rapidamente, nonmin
dovrebbe essere la tua arma preferita, tranne in un caso d'uso molto ristretto. La soluzione deve esaminare tutti i numeri nell'elenco e fare un calcolo per ciascun numero. L'uso invece è quasi sempre più veloce.min
bisect.bisect_left
Il "quasi" deriva dal fatto che bisect_left
richiede che l'elenco sia ordinato per funzionare. Eventualmente, il tuo caso d'uso è tale che puoi ordinare l'elenco una volta e poi lasciarlo da solo. Anche in caso contrario, purché non sia necessario effettuare l'ordinamento prima di ogni chiamata take_closest
, bisect
è probabile che il modulo esca in cima. In caso di dubbi, prova entrambi e osserva la differenza nel mondo reale.
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 lavora dimezzando ripetutamente un elenco e scoprendo quale metà myNumber
deve essere presente osservando il valore medio. Ciò significa che ha un tempo di esecuzione di O (log n) rispetto al tempo di esecuzione O (n) della risposta più votata . Se confrontiamo i due metodi e forniamo entrambi con un ordinato myList
, questi sono i risultati:
$ python -m timeit -s " dalla più vicina importazione take_closest da randint di importazione casuale a = range (-1000, 1000, 10) "" take_closest (a, randint (-1100, 1100)) " 100000 loop, meglio di 3: 2,22 usec per loop $ python -m timeit -s " dall'importazione più vicina con_min da randint di importazione casuale a = range (-1000, 1000, 10) "" with_min (a, randint (-1100, 1100)) " 10000 loop, meglio di 3: 43,9 usec per loop
Quindi, in questo particolare test, bisect
è quasi 20 volte più veloce. Per elenchi più lunghi, la differenza sarà maggiore.
E se livelliamo il campo di gioco rimuovendo il presupposto che myList
deve essere ordinato? Diciamo che ordiniamo una copia dell'elenco ogni volta che take_closest
viene chiamato, lasciando min
inalterata la soluzione. Utilizzando l'elenco di 200 voci nel test sopra, la bisect
soluzione è ancora la più veloce, anche se solo del 30% circa.
Questo è un risultato strano, considerando che la fase di ordinamento è O (n log (n)) ! L'unica ragione che min
sta ancora perdendo è che l'ordinamento viene eseguito in codice c altamente ottimizzato, mentre min
deve spostarsi lungo chiamando una funzione lambda per ogni articolo. Con l' myList
aumentare delle dimensioni, la min
soluzione alla fine sarà più veloce. Nota che abbiamo dovuto impilare tutto a suo favore affinché la min
soluzione vincesse.
a=range(-1000,1000,2);random.shuffle(a)
, scoprirai che takeClosest(sorted(a), b)
diventerebbe più lento.
getClosest
può essere chiamato più di una volta per ogni tipo, questo sarà più veloce, e per il caso d'uso sort-once, è un gioco da ragazzi.
myList
è già un np.array
allora usare np.searchsorted
al posto di bisect
è più veloce.
>>> takeClosest = lambda num,collection:min(collection,key=lambda x:abs(x-num))
>>> takeClosest(5,[4,1,88,44,3])
4
Una lambda è un modo speciale di scrivere una funzione "anonima" (una funzione che non ha un nome). Puoi assegnargli il nome che vuoi perché un lambda è un'espressione.
Il "lungo" modo di scrivere quanto sopra sarebbe:
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))
Questo codice ti darà l'indice del numero più vicino di Numero nell'elenco.
La soluzione fornita da KennyTM è la migliore in assoluto, ma nei casi in cui non è possibile utilizzarla (come brython), questa funzione farà il lavoro
Scorri l'elenco e confronta il numero più vicino corrente con 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
if abs(myList[i] - myNumber) < abs(closest - myNumber): closest = myList[i];
. Meglio conservare quel valore in anticipo però.
È importante notare che l'idea del suggerimento di Lauritz di usare bisect in realtà non trova il valore più vicino in MyList a MyNumber. Invece, bisect trova il valore successivo in ordine dopo MyNumber in MyList. Quindi nel caso di OP otterresti effettivamente la posizione 44 restituita invece della posizione 4.
>>> myList = [1, 3, 4, 44, 88]
>>> myNumber = 5
>>> pos = (bisect_left(myList, myNumber))
>>> myList[pos]
...
44
Per ottenere il valore più vicino a 5 potresti provare a convertire l'elenco in un array e usare argmin da numpy in questo modo.
>>> 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
Non so quanto veloce sarebbe, però, la mia ipotesi sarebbe "non molto".
np.searchsorted
invece di bisect_left
. E @Kanat è giusto - la soluzione di Lauritz fa includere il codice che raccoglie quale dei due candidati è più vicina.
Espandendo la risposta di Gustavo Lima. La stessa cosa può essere fatta senza creare un elenco completamente nuovo. I valori nell'elenco possono essere sostituiti con i differenziali mentre il FOR
ciclo avanza.
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.
Se posso aggiungere alla risposta di @ Lauritz
Per non avere un errore di esecuzione, non dimenticare di aggiungere una condizione prima della bisect_left
riga:
if (myNumber > myList[-1] or myNumber < myList[0]):
return False
quindi il codice completo sarà simile a:
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