with tutorial framework español djangoproject desde con cero applications python list list-comprehension

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 a 0 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 )

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.