Me preguntaba si había una solución fácil para el siguiente problema. El problema aquí es que quiero mantener todos los elementos que aparecen dentro de esta lista después de que la condición inicial sea verdadera. La condición aquí es que quiero eliminar todo antes de que la condición de que un valor sea mayor que 18 sea verdadera, pero conservar todo después. Ejemplo
Aporte:
p = [4,9,10,4,20,13,29,3,39]
Rendimiento esperado:
p = [20,13,29,3,39]
Sé que puedes filtrar toda la lista a través de
[x for x in p if x>18]
Pero quiero detener esta operación una vez que se encuentra el primer valor por encima de 18, y luego incluir el resto de los valores independientemente de si cumplen la condición o no. Parece un problema fácil, pero aún no he encontrado la solución.
En mi experiencia, al menos para las listas de enteros, usar enumerate
solo para encontrar un índice y descartar todos los demás índices es un desperdicio que es más rápido encontrar primero solo el elemento y luego usar list.index
para encontrar su índice. Basado en algunas pruebas, espero que sea un factor 1.44 más rápido que la solución explicit_loop
(que usa enumerate
) en el punto de referencia de @enke .
def start_over_18(p): for x in p: if x > 18: return p[p.index(x):] return []
Otra solución, usar un iterador para no tener que preocuparme por los índices. Parece ser el doble de rápido que la solución explicit_loop
:
def start_over_18(p): it = iter(p) for x in it: if x > 18: return [x, *it] return []
Puedes usar itertools.dropwhile
:
from itertools import dropwhile p = [4,9,10,4,20,13,29,3,39] p = dropwhile(lambda x: x <= 18, p) print(*p) # 20 13 29 3 39
En mi opinión, esta es posiblemente la versión más fácil de leer. Esto también corresponde a un patrón común en otros lenguajes de programación funcionales, como dropWhile (<=18) p
en Haskell y p.dropWhile(_ <= 18)
en Scala.
Alternativamente, usando el operador morsa (solo disponible en python 3.8+):
exceeded = False p = [x for x in p if (exceeded := exceeded or x > 18)] print(p) # [20, 13, 29, 3, 39]
Pero supongo que a algunas personas no les gusta este estilo. En ese caso, se puede hacer un bucle for
explícito (sugerencia de ilkkachu):
for i, x in enumerate(p): if x > 18: output = p[i:] break else: output = [] # alternatively just put output = [] before for
Excelentes soluciones aquí, solo quería demostrar cómo hacerlo con numpy:
>>> import numpy as np >>> p[(np.array(p) > 18).argmax():] [20, 13, 29, 3, 39]
Como hay muchas buenas respuestas aquí, decidí ejecutar algunos puntos de referencia simples. El primero usa la matriz de muestra del OP ( [4,9,10,4,20,13,29,3,39]
) de longitud 9. El segundo usa una matriz generada aleatoriamente de longitud 20 mil, donde la primera mitad está entre 0 y 15, y la segunda mitad está entre -20 y 30 (para que la división no ocurra justo en el centro).
Usando los datos del OP (matriz de longitud 9):
%timeit enke() 650 ns ± 15.9 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each) %timeit j1lee1() 546 ns ± 4.22 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each) %timeit j1lee2() 551 ns ± 19 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each) %timeit j2lee3() 536 ns ± 12.9 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each) %timeit richardec() 2.08 µs ± 16 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
Usando una matriz de longitud 20,000 (20 mil):
%timeit enke() 1.5 ms ± 34.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) %timeit j1lee1() 1.95 ms ± 43 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) %timeit j1lee2() 2.1 ms ± 53.7 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) %timeit j2lee3() 2.33 ms ± 96.2 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) %timeit richardec() 13.3 µs ± 461 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
Código para generar la segunda matriz:
p = np.hstack([np.random.randint(0,15,10000),np.random.randint(-20,30,10000)])
Entonces, para el caso pequeño, numpy es una babosa y no es necesario. ¡Pero el caso grande, numpy es casi 100 veces más rápido y el camino a seguir! :)
Me di cuenta de que el OP menciona en una respuesta que p
es en realidad un Pandas DataFrame. Aquí hay un método para filtrar todos los elementos hasta la primera instancia de un número mayor a 18 usando Pandas:
import pandas as pd df = pd.DataFrame([4,9,10,4,20,13,29,3,39]) df = df[df[0].gt(18).idxmax():] print(df)
Salidas:
0 4 20 5 13 6 29 7 3 8 39
Nota: estoy ciego a la estructura real de su DataFrame, así que solo usé exactamente lo que se me dio.
no debería break
suficiente?
algo como:
import numpy as np input= np.array([4,9,10,4,20,13,29,3,39]) for index, item in enumerate(input): if(item>=18): print(input[index:]) break
más información en:
https://problemsolutionwithpython.com/05-NumPy-and-Arrays/05.06-Array-Slicing/