Dada una lista de enteros, quiero encontrar qué número es el más cercano a un número que ingreso como entrada:
>>> myList = [4, 1, 88, 44, 3]
>>> myNumber = 5
>>> takeClosest(myList, myNumber)
...
4
¿Hay alguna forma rápida de hacer esto?
Dada una lista de enteros, quiero encontrar qué número es el más cercano a un número que ingreso como entrada:
>>> myList = [4, 1, 88, 44, 3]
>>> myNumber = 5
>>> takeClosest(myList, myNumber)
...
4
¿Hay alguna forma rápida de hacer esto?
Respuestas:
Si no estamos seguros de que la lista está ordenada, podríamos utilizar el incorporado en min()
la función , para encontrar el elemento que tiene la distancia mínima desde el número especificado.
>>> min(myList, key=lambda x:abs(x-myNumber))
4
Tenga en cuenta que también funciona con dictos con claves int, como {1: "a", 2: "b"}
. Este método lleva O (n) tiempo.
Si la lista ya está ordenada, o podría pagar el precio de ordenar la matriz solo una vez, use el método de bisección ilustrado en la respuesta de @ Lauritz que solo toma tiempo O (log n) (tenga en cuenta que verificar si una lista ya está ordenada es O (n) y la clasificación es O (n log n).)
O(n)
, donde un poco de pirateo bisect
le dará una mejora masiva O(log n)
(si su matriz de entrada está ordenada).
min
, ejecútela en un diccionario ( items()
) en lugar de una lista, y devuelva la clave en lugar del valor al final.
take_closest
Cambiaré el nombre de la función para cumplir con las convenciones de nomenclatura PEP8.
Si te refieres a rápido de ejecutar en lugar de rápido de escribir, nomin
debería ser tu arma de elección, excepto en un caso de uso muy limitado. La solución debe examinar cada número de la lista y hacer un cálculo para cada número. Usar en cambio es casi siempre más rápido.min
bisect.bisect_left
El "casi" proviene del hecho de que bisect_left
la lista debe estar ordenada para funcionar. Con suerte, su caso de uso es tal que puede ordenar la lista una vez y luego dejarla en paz. Incluso si no es así, siempre que no necesite ordenar cada vez que llame take_closest
, bisect
es probable que el módulo quede en la parte superior. Si tiene dudas, pruebe ambas y observe la diferencia del mundo real.
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 funciona reduciendo a la mitad repetidamente una lista y descubriendo en qué mitad myNumber
tiene que estar mirando el valor medio. Esto significa que tiene un tiempo de ejecución de O (log n) en oposición al tiempo de ejecución de O (n) de la respuesta más votada . Si comparamos los dos métodos y suministramos ambos con un orden myList
, estos son los resultados:
$ python -m timeit -s " de la importación más cercana take_closest de importación aleatoria randint a = rango (-1000, 1000, 10) "" take_closest (a, randint (-1100, 1100)) " 100000 bucles, lo mejor de 3: 2.22 usec por bucle $ python -m timeit -s " desde la importación más cercana con_min de importación aleatoria randint a = rango (-1000, 1000, 10) "" with_min (a, randint (-1100, 1100)) " 10000 bucles, lo mejor de 3: 43.9 usec por bucle
Entonces, en esta prueba en particular, bisect
es casi 20 veces más rápido. Para listas más largas, la diferencia será mayor.
¿Qué sucede si nivelamos el campo de juego eliminando la condición previa que myList
debe clasificarse? Digamos que clasificamos una copia de la lista cada vez que take_closest
se llama, dejando la min
solución sin alterar. Usando la lista de 200 elementos en la prueba anterior, la bisect
solución sigue siendo la más rápida, aunque solo en un 30%.
¡Este es un resultado extraño, considerando que el paso de clasificación es O (n log (n)) ! La única razón por la min
que sigue perdiendo es que la clasificación se realiza en un código c altamente optimizado, mientras que min
tiene que seguir adelante llamando a una función lambda para cada elemento. A medida que myList
crece en tamaño, la min
solución eventualmente será más rápida. Tenga en cuenta que tuvimos que apilar todo a su favor para que la min
solución ganara.
a=range(-1000,1000,2);random.shuffle(a)
, encontrará que takeClosest(sorted(a), b)
se volvería más lento.
getClosest
se pueda llamar más de una vez para cada tipo, será más rápido, y para el caso de uso de ordenar una vez, es obvio.
myList
ya es un np.array
uso np.searchsorted
en lugar de bisect
es más rápido.
>>> takeClosest = lambda num,collection:min(collection,key=lambda x:abs(x-num))
>>> takeClosest(5,[4,1,88,44,3])
4
Una lambda es una forma especial de escribir una función "anónima" (una función que no tiene nombre). Puede asignarle el nombre que desee porque una lambda es una expresión.
La forma "larga" de escribir lo anterior sería:
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))
Este código le dará el índice del número más cercano de Número en la lista.
La solución dada por KennyTM es la mejor en general, pero en los casos en que no puede usarla (como brython), esta función hará el trabajo.
Iterar sobre la lista y comparar el número más cercano actual 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];
. Sin embargo, es mejor almacenar ese valor de antemano.
Es importante tener en cuenta que la idea de sugerencia de Lauritz de usar bisect en realidad no encuentra el valor más cercano en MyList a MyNumber. En cambio, bisect encuentra el siguiente valor en orden después de MyNumber en MyList. Entonces, en el caso de OP, en realidad obtendría la posición de 44 en lugar de la posición de 4.
>>> myList = [1, 3, 4, 44, 88]
>>> myNumber = 5
>>> pos = (bisect_left(myList, myNumber))
>>> myList[pos]
...
44
Para obtener el valor más cercano a 5, puede intentar convertir la lista a una matriz y usar argmin de numpy de esta manera.
>>> 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
Sin embargo, no sé qué tan rápido sería, supongo que sería "no muy".
np.searchsorted
lugar de bisect_left
. Y @Kanat es correcto - La solución de Lauritz hace incluya el código que recoge cuál de los dos candidatos está más cerca.
Ampliando la respuesta de Gustavo Lima. Se puede hacer lo mismo sin crear una lista completamente nueva. Los valores en la lista se pueden reemplazar con los diferenciales a medida que FOR
avanza el ciclo.
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.
Si puedo agregar a la respuesta de @ Lauritz
Para no tener un error de ejecución, no olvide agregar una condición antes de la bisect_left
línea:
if (myNumber > myList[-1] or myNumber < myList[0]):
return False
entonces el código completo se verá así:
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