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.
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
Puede usar enumerate
y listar el corte en una expresión generadora y next
:
out = next((p[i:] for i, item in enumerate(p) if item > 18), [])
Producción:
[20, 13, 29, 3, 39]
En términos de tiempo, depende de la estructura de datos. Los gráficos a continuación muestran la diferencia de tiempo entre las respuestas aquí para varias longitudes de p
.
Si los datos originales son una lista, usar una expresión generadora y next
(o un ciclo que se interrumpe cuando se cumple la condición) son generalmente los más rápidos:
import perfplot import numpy as np import pandas as pd import random import itertools def it_dropwhile(p): return list(dropwhile(lambda x: x <= 18, p)) def walrus(p): exceeded = False return [x for x in p if (exceeded := exceeded or x > 18)] def explicit_loop(p): for i, x in enumerate(p): if x > 18: output = p[i:] break else: output = [] return output def genexpr_next(p): return next((p[i:] for i, item in enumerate(p) if item > 18), []) def np_argmax(p): return p[(np.array(p) > 18).argmax():] def pd_idxmax(p): s = pd.Series(p) return s[s.gt(18).idxmax():] perfplot.show( setup=lambda n: random.choices(range(0, 15), k=10*n) + random.choices(range(-20,30), k=10*n), kernels=[it_dropwhile, walrus, explicit_loop, genexpr_next, np_argmax, pd_idxmax], labels=['it_dropwhile','walrus','explicit_loop','genexpr_next','np_argmax','pd_idxmax'], n_range=[2 ** k for k in range(17)], equality_check=np.allclose, xlabel='~10*n' )
Pero si los datos iniciales son objetos ndarray, entonces las operaciones vectorizadas en numpy o pandas son mejores para matrices grandes:
perfplot.show( setup=lambda n: np.hstack([np.random.randint(0,15,10*n), np.random.randint(-20,30,10*n)]), kernels=[it_dropwhile, walrus, explicit_loop, genexpr_next, np_argmax, pd_idxmax], labels=['it_dropwhile','walrus','explicit_loop','genexpr_next','np_argmax','pd_idxmax'], n_range=[2 ** k for k in range(17)], equality_check=np.allclose, xlabel='~10*n' )
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.