@ sancho.s Muy bien visto. Aunque las respuestas a esta pregunta son mucho mejores que las de esa otra pregunta. Así que voy a votar para cerrar el otro como duplicado de este.
Jean-François Corbett
Respuestas:
327
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).)
¿Qué hay de devolver también el índice de que esto sucedió en la lista?
Charlie Parker
@CharlieParker Cree su propia implementación de min, ejecútela en un diccionario ( items()) en lugar de una lista, y devuelva la clave en lugar del valor al final.
Dustin Oprea
2
O use en numpy.argminlugar de minpara obtener el índice en lugar del valor.
148
take_closestCambiaré 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.minbisect.bisect_left
El "casi" proviene del hecho de que bisect_leftla 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, bisectes 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 myNumbertiene 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, bisectes 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 myListdebe clasificarse? Digamos que clasificamos una copia de la lista cada vez quetake_closest se llama, dejando la minsolución sin alterar. Usando la lista de 200 elementos en la prueba anterior, la bisectsolució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 minque sigue perdiendo es que la clasificación se realiza en un código c altamente optimizado, mientras que mintiene que seguir adelante llamando a una función lambda para cada elemento. A medida que myListcrece en tamaño, la minsolución eventualmente será más rápida. Tenga en cuenta que tuvimos que apilar todo a su favor para que la minsolución ganara.
La clasificación en sí misma necesita O (N log N), por lo que será más lento cuando N se haga grande. Por ejemplo, si usa a=range(-1000,1000,2);random.shuffle(a), encontrará que takeClosest(sorted(a), b)se volvería más lento.
kennytm
3
@KennyTM Te lo concederé, y lo señalaré en mi respuesta. Pero siempre que getClosestse 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.
Lauritz V. Thaulow
¿Qué hay de devolver también el índice de que esto sucedió en la lista?
Charlie Parker
Si myListya es un np.arrayuso np.searchsorteden lugar de bisectes más rápido.
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.
! Incorrecto! Debe ser if abs(myList[i] - myNumber) < abs(closest - myNumber): closest = myList[i];. Sin embargo, es mejor almacenar ese valor de antemano.
lk_vc
Seguramente la función tal como está ya devuelve el índice del más cercano. Para que cumpla con los requisitos del OP, ¿no debería leer la segunda última línea más cercana = myList [i]
Paula Livingstone
2
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.
La función de Lauritz funciona correctamente. Solo usa bisect_left, pero Lauritz sugirió una función takeClosest (...) que realiza una verificación adicional.
Kanat
Si vas a usar NumPy, puedes usarlo en np.searchsortedlugar 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.
John Y
1
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 FORavanza 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 =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
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.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).)
fuente
O(n)
, donde un poco de pirateobisect
le dará una mejora masivaO(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.numpy.argmin
lugar demin
para obtener el índice en lugar del valor.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, no
min
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 llametake_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.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 ordenmyList
, estos son los resultados: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 quetake_closest
se llama, dejando lamin
solución sin alterar. Usando la lista de 200 elementos en la prueba anterior, labisect
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 quemin
tiene que seguir adelante llamando a una función lambda para cada elemento. A medida quemyList
crece en tamaño, lamin
solución eventualmente será más rápida. Tenga en cuenta que tuvimos que apilar todo a su favor para que lamin
solución ganara.fuente
a=range(-1000,1000,2);random.shuffle(a)
, encontrará quetakeClosest(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 unnp.array
usonp.searchsorted
en lugar debisect
es más rápido.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:
fuente
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.
fuente
Iterar sobre la lista y comparar el número más cercano actual con
abs(currentNumber - myNumber)
:fuente
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.
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.
Sin embargo, no sé qué tan rápido sería, supongo que sería "no muy".
fuente
np.searchsorted
lugar debisect_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.fuente
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:entonces el código completo se verá así:
fuente