tutorial - Elimine los primeros N elementos que coincidan con una condición en una lista de Python
tutorial django (7)
Si tengo una función
matchCondition(x)
, ¿cómo puedo eliminar los primeros
n
elementos de una lista de Python que coinciden con esa condición?
Una solución es iterar sobre cada elemento, marcarlo para su eliminación (por ejemplo, configurándolo en
None
) y luego filtrar la lista con una comprensión.
Esto requiere iterar sobre la lista dos veces y muta los datos.
¿Hay alguna forma más idiomática o eficiente de hacer esto?
n = 3
def condition(x):
return x < 5
data = [1, 10, 2, 9, 3, 8, 4, 7]
out = do_remove(data, n, condition)
print(out) # [10, 9, 8, 4, 7] (1, 2, and 3 are removed, 4 remains)
Comenzando
Python 3.8
, y la introducción de
expresiones
de
asignación (PEP 572)
(
:=
operador), podemos usar e incrementar una variable dentro de una comprensión de lista:
# items = [1, 10, 2, 9, 3, 8, 4, 7]
total = 0
[x for x in items if not (x < 5 and (total := total + 1) <= 3)]
# [10, 9, 8, 4, 7]
Esta:
-
Inicializa una variable
total
a0
que simbolizará el número de ocurrencias previamente coincidentes dentro de la comprensión de la lista -
Comprueba cada elemento si ambos:
-
coincide con la condición de exclusión (
x < 5
) -
y si aún no hemos descartado más de la cantidad de elementos que queremos filtrar por:
-
Incremento
total
(total := total + 1
) a través de una expresión de asignación -
y al mismo tiempo comparando el nuevo valor del
total
con el número máximo de elementos a descartar (3
)
-
Incremento
-
coincide con la condición de exclusión (
Escriba un generador que tome el iterable, una condición y una cantidad para soltar. Itere sobre los datos y produzca elementos que no cumplan con la condición. Si se cumple la condición, incremente un contador y no produzca el valor. Siempre rinda artículos una vez que el contador alcance la cantidad que desea soltar.
def iter_drop_n(data, condition, drop):
dropped = 0
for item in data:
if dropped >= drop:
yield item
continue
if condition(item):
dropped += 1
continue
yield item
data = [1, 10, 2, 9, 3, 8, 4, 7]
out = list(iter_drop_n(data, lambda x: x < 5, 3))
Esto no requiere una copia adicional de la lista, solo itera sobre la lista una vez y solo llama a la condición una vez para cada elemento.
A menos que realmente quiera ver la lista completa, deje la llamada de la
list
en el resultado e itere directamente sobre el generador devuelto.
La respuesta aceptada fue demasiado mágica para mi gusto. Aquí hay uno donde el flujo es un poco más claro de seguir:
def matchCondition(x):
return x < 5
def my_gen(L, drop_condition, max_drops=3):
count = 0
iterator = iter(L)
for element in iterator:
if drop_condition(element):
count += 1
if count >= max_drops:
break
else:
yield element
yield from iterator
example = [1, 10, 2, 9, 3, 8, 4, 7]
print(list(my_gen(example, drop_condition=matchCondition)))
Es similar a la lógica en la respuesta de davidism , pero en lugar de verificar que el conteo de caídas se excede en cada paso, simplemente cortocircuitamos el resto del ciclo.
Nota:
Si no tiene
yield from
disponible, simplemente reemplácelo con otro bucle for sobre los elementos restantes en el
iterator
.
Python directo:
N = 3
data = [1, 10, 2, 9, 3, 8, 4, 7]
def matchCondition(x):
return x < 5
c = 1
l = []
for x in data:
if c > N or not matchCondition(x):
l.append(x)
else:
c += 1
print(l)
Esto puede convertirse fácilmente en un generador si lo desea:
def filter_first(n, func, iterable):
c = 1
for x in iterable:
if c > n or not func(x):
yield x
else:
c += 1
print(list(filter_first(N, matchCondition, data)))
Si se requiere mutación:
def do_remove(ls, N, predicate):
i, delete_count, l = 0, 0, len(ls)
while i < l and delete_count < N:
if predicate(ls[i]):
ls.pop(i) # remove item at i
delete_count, l = delete_count + 1, l - 1
else:
i += 1
return ls # for convenience
assert(do_remove(l, N, matchCondition) == [10, 9, 8, 4, 7])
Una forma de usar
itertools.filterfalse
e
itertools.count
:
from itertools import count, filterfalse
data = [1, 10, 2, 9, 3, 8, 4, 7]
output = filterfalse(lambda L, c=count(): L < 5 and next(c) < 3, data)
Luego, la
list(output)
le da:
[10, 9, 8, 4, 7]
Usando listas de comprensión:
n = 3
data = [1, 10, 2, 9, 3, 8, 4, 7]
count = 0
def counter(x):
global count
count += 1
return x
def condition(x):
return x < 5
filtered = [counter(x) for x in data if count < n and condition(x)]
Esto también dejará de verificar la condición después de que se encuentren n elementos gracias al cortocircuito booleano.