¿Cómo eliminar elementos de una lista mientras se itera?

934

Estoy iterando sobre una lista de tuplas en Python, e intento eliminarlas si cumplen con ciertos criterios.

for tup in somelist:
    if determine(tup):
         code_to_remove_tup

¿Qué debo usar en lugar de code_to_remove_tup? No puedo entender cómo quitar el artículo de esta manera.

lfaraone
fuente
La mayoría de las respuestas en esta página realmente no explican por qué eliminar elementos mientras se itera sobre una lista produce resultados extraños, pero la respuesta aceptada en esta pregunta sí , y probablemente sea una mejor trampa para los principiantes que se encuentran con este problema por primera vez.
ggorlen

Respuestas:

827

Puede usar una comprensión de la lista para crear una nueva lista que contenga solo los elementos que no desea eliminar:

somelist = [x for x in somelist if not determine(x)]

O, al asignar al segmento somelist[:], puede mutar la lista existente para que contenga solo los elementos que desee:

somelist[:] = [x for x in somelist if not determine(x)]

Este enfoque podría ser útil si hay otras referencias somelistque necesiten reflejar los cambios.

En lugar de una comprensión, también podría usar itertools. En Python 2:

from itertools import ifilterfalse
somelist[:] = ifilterfalse(determine, somelist)

O en Python 3:

from itertools import filterfalse
somelist[:] = filterfalse(determine, somelist)

En aras de la claridad y para aquellos que encuentran el uso de la [:]notación hackish o difusa, aquí hay una alternativa más explícita. Teóricamente, debería realizar lo mismo con respecto al espacio y el tiempo que las frases anteriores.

temp = []
while somelist:
    x = somelist.pop()
    if not determine(x):
        temp.append(x)
while temp:
    somelist.append(templist.pop())

También funciona en otros idiomas que pueden no tener la capacidad de reemplazar elementos de las listas de Python, con modificaciones mínimas. Por ejemplo, no todos los idiomas lanzan listas vacías a a Falsecomo lo hace Python. Puede sustituir while somelist:por algo más explícito como while len(somelist) > 0:.

David Raznick
fuente
44
¿Puede hacerlo más rápido si sabe que solo se eliminarán unos pocos, es decir, solo elimine esos y deje los otros en su lugar en lugar de volver a escribirlos?
highBandWidth
20
¿Qué pasa si mi lista es enorme y no puedo permitirme hacer una copia?
jpcgt
15
@jpcgt Debe usar somelist[:] = (x for x in somelist if determine(x))esto creará un generador que puede no crear copias innecesarias.
Rostislav Kondratenko
8
@RostislavKondratenko: list_ass_slice()función que implementa somelist[:]=llamadas PySequence_Fast()internamente. Esta función siempre devuelve una lista, es decir, la solución de @Alex Martelli que ya utiliza una lista en lugar de un generador es muy probablemente más eficiente
JFS
66
¿Le gustaría explicar cuáles son las diferencias entre asignar la comprensión de la lista a la lista y clonar la lista, por favor? ¿No somelistestaría mutada la lista original en ambos métodos?
Bowen Liu
589

Las respuestas que sugieren la comprensión de la lista son casi correctas, excepto que construyen una lista completamente nueva y luego le dan el mismo nombre que la lista anterior, ya que NO modifican la lista anterior en su lugar. Eso es diferente de lo que estaría haciendo mediante la eliminación selectiva, como en la sugerencia de @ Lennart : es más rápido, pero si se accede a su lista a través de múltiples referencias, el hecho de que simplemente está volviendo a colocar una de las referencias y NO está alterando el objeto de la lista en sí mismo puede conducir a errores sutiles y desastrosos.

Afortunadamente, es extremadamente fácil obtener tanto la velocidad de la comprensión de la lista como la semántica requerida de la alteración en el lugar: solo codifique:

somelist[:] = [tup for tup in somelist if determine(tup)]

Tenga en cuenta la sutil diferencia con otras respuestas: esta NO se está asignando a un nombre desnudo: se está asignando a un segmento de lista que resulta ser la lista completa, reemplazando así el contenido de la lista dentro del mismo objeto de lista de Python , en lugar de simplemente restablecer una referencia (desde el objeto de lista anterior al nuevo objeto de lista) como las otras respuestas.

Alex Martelli
fuente
1
¿Cómo hago la misma tarea en rodajas con un dict? En Python 2.6?
PaulMcG
11
@Paul: Dado que los dictados no están ordenados, los cortes no tienen sentido para los dictados. Si desea reemplazar el contenido de dict apor el contenido de dict b, use a.clear(); a.update(b).
Sven Marnach
1
¿Por qué "volver a colocar" una de las referencias reemplazando lo que la variable hace referencia para causar errores? Parece que eso solo sería un problema potencial en aplicaciones de subprocesos múltiples, no de un solo subproceso.
Derek Dahmer
59
@Derek x = ['foo','bar','baz']; y = x; x = [item for item in x if determine(item)];Esto reasigna xal resultado de la comprensión de la lista, pero yaún se refiere a la lista original['foo','bar','baz'] . Si esperaba xy hace yreferencia a la misma lista, es posible que haya introducido errores. A prevenir esto asignando a una rebanada de toda la lista, como Alex espectáculos, y que muestro aquí: x = ["foo","bar","baz"]; y = x; x[:] = [item for item in x if determine(item)];. La lista se modifica en su lugar. asegurando que todas las referencias a la lista (ambas xy yaquí) hagan referencia a la nueva lista.
Steven T. Snyder
de hecho, el uso de la filterfunción también crea una nueva lista, no modifica los elementos en su lugar ... soloolist[:] = [i for i in olist if not dislike(i)]
John Strood
303

Debe tomar una copia de la lista e iterarla primero, o la iteración fallará con lo que pueden ser resultados inesperados.

Por ejemplo (depende de qué tipo de lista):

for tup in somelist[:]:
    etc....

Un ejemplo:

>>> somelist = range(10)
>>> for x in somelist:
...     somelist.remove(x)
>>> somelist
[1, 3, 5, 7, 9]

>>> somelist = range(10)
>>> for x in somelist[:]:
...     somelist.remove(x)
>>> somelist
[]
Lennart Regebro
fuente
13
@Zen Porque el segundo itera sobre una copia de la lista. Entonces, cuando modifica la lista original, no modifica la copia sobre la que itera.
Lennart Regebro
3
¿Qué es mejor al hacer una lista de canciones [:] en comparación con la lista (lista de canciones)?
Mariusz Jamro
3
list(somelist)convertirá un iterable en una lista. somelist[:]realiza una copia de un objeto que admite el corte. Por lo tanto, no necesariamente hacen lo mismo. En este caso quiero hacer una copia del somelistobjeto, así que uso[:]
Lennart Regebro
33
Nota para cualquiera que lea esto, esto es MUY lento para las listas. remove()tiene que ir a TODA la lista para cada iteración, por lo que tomará una eternidad.
vitiral
77
El gran tiempo no importa cuando se trata de listas de solo una docena de artículos. A menudo, es claro y simple que los futuros programadores entiendan que es mucho más valioso que el rendimiento.
Steve
127
for i in range(len(somelist) - 1, -1, -1):
    if some_condition(somelist, i):
        del somelist[i]

Debe ir hacia atrás, de lo contrario, es un poco como cortar la rama del árbol en la que está sentado :-)

Usuarios de Python 2: reemplace rangepor xrangepara evitar crear una lista codificada

John Machin
fuente
13
En las últimas versiones de Python, puede hacerlo incluso más limpia mediante el uso de la reversed()orden interna
ncoghlan
16
reverse () no crea una nueva lista, crea un iterador inverso sobre la secuencia proporcionada. Al igual que enumerate (), debe envolverlo en list () para obtener realmente una lista. Usted puede estar pensando en ordenados (), que hace crear una nueva lista cada vez (tiene que, por lo que puede resolverlo).
ncoghlan
1
@Mauris porque enumeratedevuelve un iterador y reversedespera una secuencia. Supongo que podría hacerlo reversed(list(enumerate(somelist)))si no le importa crear una lista adicional en la memoria.
drevicko
2
Esto es O (N * M) para las matrices, es muy lento si elimina muchos elementos de una lista grande. Entonces no recomendado.
Sam Watkins
2
@SamWatkins Sí, esta respuesta es para cuando eliminas un par de elementos de una matriz muy grande. Menos uso de memoria, pero puede ser mmás lento.
Navin
52

Tutorial oficial de Python 2 4.2. "para declaraciones"

https://docs.python.org/2/tutorial/controlflow.html#for-statements

Esta parte de los documentos deja en claro que:

  • necesita hacer una copia de la lista iterada para modificarla
  • una forma de hacerlo es con la notación de corte [:]

Si necesita modificar la secuencia sobre la que está iterando mientras está dentro del bucle (por ejemplo, para duplicar elementos seleccionados), se recomienda que primero haga una copia. Iterar sobre una secuencia no hace una copia implícitamente. La notación de corte hace que esto sea especialmente conveniente:

>>> words = ['cat', 'window', 'defenestrate']
>>> for w in words[:]:  # Loop over a slice copy of the entire list.
...     if len(w) > 6:
...         words.insert(0, w)
...
>>> words
['defenestrate', 'cat', 'window', 'defenestrate']

Documentación de Python 2 7.3. "El para declaración"

https://docs.python.org/2/reference/compound_stmts.html#for

Esta parte de los documentos dice una vez más que debe hacer una copia y proporciona un ejemplo de eliminación real:

Nota: Hay una sutileza cuando la secuencia está siendo modificada por el ciclo (esto solo puede ocurrir para secuencias mutables, es decir, listas). Se utiliza un contador interno para realizar un seguimiento de qué elemento se usa a continuación, y esto se incrementa en cada iteración. Cuando este contador ha alcanzado la longitud de la secuencia, el ciclo termina. Esto significa que si el conjunto elimina el elemento actual (o anterior) de la secuencia, se omitirá el siguiente elemento (ya que obtiene el índice del elemento actual que ya ha sido tratado). Del mismo modo, si la suite inserta un elemento en la secuencia anterior al elemento actual, el elemento actual será tratado nuevamente la próxima vez a través del ciclo. Esto puede conducir a errores desagradables que se pueden evitar haciendo una copia temporal usando una porción de la secuencia completa, por ejemplo,

for x in a[:]:
    if x < 0: a.remove(x)

Sin embargo, no estoy de acuerdo con esta implementación, ya que .remove()tiene que iterar toda la lista para encontrar el valor.

Las mejores soluciones

Ya sea:

En general, solo desea ir a la .append()opción más rápida de forma predeterminada a menos que la memoria sea una gran preocupación.

¿Podría Python hacer esto mejor?

Parece que esta API de Python en particular podría mejorarse. Compárelo, por ejemplo, con:

  • Java ListIterator :: eliminar qué documentos "Esta llamada solo se puede hacer una vez por llamada a la siguiente o anterior"
  • C ++ std::vector::eraseque devuelve un interador válido al elemento después del eliminado

ambos dejan en claro que no puede modificar una lista que se está iterando, excepto con el iterador mismo, y le brinda formas eficientes de hacerlo sin copiar la lista.

Tal vez la razón subyacente es que las listas de Python se supone que son matriz dinámica respaldado, y por lo tanto cualquier tipo de eliminación serán tiempo ineficiente de todos modos, mientras que Java tiene una jerarquía interfaz mejor con ambos ArrayListy LinkedListlas implementaciones de ListIterator.

Tampoco parece haber un tipo de lista vinculada explícita en Python stdlib: Lista vinculada de Python

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
fuente
48

Su mejor enfoque para tal ejemplo sería una lista de comprensión

somelist = [tup for tup in somelist if determine(tup)]

En los casos en que está haciendo algo más complejo que llamar a una determinefunción, prefiero construir una nueva lista y simplemente agregarla a medida que avanzo. Por ejemplo

newlist = []
for tup in somelist:
    # lots of code here, possibly setting things up for calling determine
    if determine(tup):
        newlist.append(tup)
somelist = newlist

Copiar la lista usando removepuede hacer que su código se vea un poco más limpio, como se describe en una de las respuestas a continuación. Definitivamente, no debe hacer esto para listas extremadamente grandes, ya que esto implica copiar primero la lista completa y también realizar una O(n) removeoperación para cada elemento que se elimina, lo que lo convierte en un O(n^2)algoritmo.

for tup in somelist[:]:
    # lots of code here, possibly setting things up for calling determine
    if determine(tup):
        newlist.append(tup)
Eli Courtwright
fuente
37

Para aquellos que les gusta la programación funcional:

somelist[:] = filter(lambda tup: not determine(tup), somelist)

o

from itertools import ifilterfalse
somelist[:] = list(ifilterfalse(determine, somelist))
Cide
fuente
1. La comprensión de listas y las expresiones generadoras se toman prestadas de Haskell, un lenguaje funcional puro; son exactamente tan funcionales como filter, y más Pythonic. 2. Si necesita lambdausar mapo filter, la lista de comp o genexpr es siempre la mejor opción; mapy filterpuede ser un poco más rápido cuando la función transform / predicate es un Python incorporado implementado en C y el iterable no es trivialmente pequeño, pero siempre son más lentos cuando se necesita un lambdaque la listacomp / genexpr podría evitar.
ShadowRanger
13

Necesitaba hacer esto con una lista enorme, y duplicar la lista parecía costoso, especialmente porque en mi caso el número de eliminaciones sería muy reducido en comparación con los elementos restantes. Tomé este enfoque de bajo nivel.

array = [lots of stuff]
arraySize = len(array)
i = 0
while i < arraySize:
    if someTest(array[i]):
        del array[i]
        arraySize -= 1
    else:
        i += 1

Lo que no sé es qué tan eficientes son un par de eliminaciones en comparación con copiar una lista grande. Por favor comente si tiene alguna idea.

Miguel
fuente
En mi caso, necesito mover esos elementos 'no deseados' a otra lista. ¿Tienes algún comentario nuevo sobre esta solución? También creo que es mejor usar algunas eliminaciones en lugar de duplicar la lista.
gustavovelascoh
Esta es la respuesta correcta si el rendimiento es un problema (aunque igual que @Alexey). Dicho esto, la elección de listuna estructura de datos en primer lugar debe considerarse cuidadosamente, ya que la eliminación de la mitad de una lista lleva un tiempo lineal en la longitud de la lista. Si realmente no necesita acceso aleatorio al elemento secuencial k-ésimo, ¿tal vez considere OrderedDict?
máximo
@GVelascoh ¿por qué no crear newlist = [], y luego newlist.append(array[i])justo antes del array[i]?
max
2
Tenga en cuenta que es probable que esto sea ineficiente en el tiempo: si list()es una lista vinculada, el acceso aleatorio es costoso, si list()es una matriz, las eliminaciones son costosas porque requieren mover todos los elementos siguientes hacia adelante. Un iterador decente podría mejorar las cosas para la implementación de la lista vinculada. Sin embargo, esto podría ser eficiente en espacio.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
10

Puede ser inteligente también crear una nueva lista si el elemento de la lista actual cumple con los criterios deseados.

entonces:

for item in originalList:
   if (item != badValue):
        newList.append(item)

y para evitar tener que volver a codificar todo el proyecto con el nuevo nombre de la lista:

originalList[:] = newList

nota, de la documentación de Python:

copy.copy (x) Devuelve una copia superficial de x.

copy.deepcopy (x) Devuelve una copia profunda de x.

ntk4
fuente
3
Esto no agrega información nueva que no estaba en la respuesta aceptada años antes.
Mark Amery
2
Es simple y solo otra forma de ver un problema @MarkAmery. Está menos condensado para aquellas personas a las que no les gusta la sintaxis de codificación comprimida.
ntk4
9

Esta respuesta se escribió originalmente en respuesta a una pregunta que desde entonces se ha marcado como duplicada: Eliminar coordenadas de la lista en Python

Hay dos problemas en su código:

1) Cuando usa remove (), intenta eliminar enteros mientras que necesita eliminar una tupla.

2) El bucle for omitirá elementos en su lista.

Repasemos lo que sucede cuando ejecutamos su código:

>>> L1 = [(1,2), (5,6), (-1,-2), (1,-2)]
>>> for (a,b) in L1:
...   if a < 0 or b < 0:
...     L1.remove(a,b)
... 
Traceback (most recent call last):
  File "<stdin>", line 3, in <module>
TypeError: remove() takes exactly one argument (2 given)

El primer problema es que está pasando 'a' y 'b' para eliminar (), pero remove () solo acepta un único argumento. Entonces, ¿cómo podemos hacer que remove () funcione correctamente con su lista? Necesitamos descubrir cuál es cada elemento de su lista. En este caso, cada uno es una tupla. Para ver esto, accedamos a un elemento de la lista (la indexación comienza en 0):

>>> L1[1]
(5, 6)
>>> type(L1[1])
<type 'tuple'>

¡Ajá! Cada elemento de L1 es en realidad una tupla. Entonces eso es lo que debemos pasar para eliminar (). Las tuplas en python son muy fáciles, simplemente se hacen encerrando valores entre paréntesis. "a, b" no es una tupla, pero "(a, b)" es una tupla. Entonces modificamos su código y lo ejecutamos nuevamente:

# The remove line now includes an extra "()" to make a tuple out of "a,b"
L1.remove((a,b))

Este código se ejecuta sin ningún error, pero veamos la lista que muestra:

L1 is now: [(1, 2), (5, 6), (1, -2)]

¿Por qué (1, -2) todavía está en su lista? Resulta que modificar la lista mientras se usa un bucle para iterar es una muy mala idea sin un cuidado especial. La razón por la que (1, -2) permanece en la lista es que las ubicaciones de cada elemento dentro de la lista cambiaron entre iteraciones del bucle for. Veamos qué sucede si le damos al código anterior una lista más larga:

L1 = [(1,2),(5,6),(-1,-2),(1,-2),(3,4),(5,7),(-4,4),(2,1),(-3,-3),(5,-1),(0,6)]
### Outputs:
L1 is now: [(1, 2), (5, 6), (1, -2), (3, 4), (5, 7), (2, 1), (5, -1), (0, 6)]

Como puede deducir de ese resultado, cada vez que la declaración condicional se evalúa como verdadera y se elimina un elemento de la lista, la siguiente iteración del bucle omitirá la evaluación del siguiente elemento de la lista porque sus valores ahora se encuentran en diferentes índices.

La solución más intuitiva es copiar la lista, luego iterar sobre la lista original y solo modificar la copia. Puedes intentar hacerlo así:

L2 = L1
for (a,b) in L1:
    if a < 0 or b < 0 :
        L2.remove((a,b))
# Now, remove the original copy of L1 and replace with L2
print L2 is L1
del L1
L1 = L2; del L2
print ("L1 is now: ", L1)

Sin embargo, la salida será idéntica a la anterior:

'L1 is now: ', [(1, 2), (5, 6), (1, -2), (3, 4), (5, 7), (2, 1), (5, -1), (0, 6)]

Esto se debe a que cuando creamos L2, python en realidad no creó un nuevo objeto. En cambio, simplemente hizo referencia a L2 al mismo objeto que L1. Podemos verificar esto con 'is', que es diferente de simplemente "igual" (==).

>>> L2=L1
>>> L1 is L2
True

Podemos hacer una copia verdadera usando copy.copy (). Entonces todo funciona como se esperaba:

import copy
L1 = [(1,2), (5,6),(-1,-2), (1,-2),(3,4),(5,7),(-4,4),(2,1),(-3,-3),(5,-1),(0,6)]
L2 = copy.copy(L1)
for (a,b) in L1:
    if a < 0 or b < 0 :
        L2.remove((a,b))
# Now, remove the original copy of L1 and replace with L2
del L1
L1 = L2; del L2
>>> L1 is now: [(1, 2), (5, 6), (3, 4), (5, 7), (2, 1), (0, 6)]

Finalmente, hay una solución más limpia que tener que hacer una copia completamente nueva de L1. La función invertida ():

L1 = [(1,2), (5,6),(-1,-2), (1,-2),(3,4),(5,7),(-4,4),(2,1),(-3,-3),(5,-1),(0,6)]
for (a,b) in reversed(L1):
    if a < 0 or b < 0 :
        L1.remove((a,b))
print ("L1 is now: ", L1)
>>> L1 is now: [(1, 2), (5, 6), (3, 4), (5, 7), (2, 1), (0, 6)]

Desafortunadamente, no puedo describir adecuadamente cómo funciona reverse (). Devuelve un objeto 'listreverseiterator' cuando se le pasa una lista. Para fines prácticos, puede pensar que se trata de una copia inversa de su argumento. Esta es la solución que recomiendo.

Cinghiale
fuente
4

Si desea hacer algo más durante la iteración, puede ser bueno obtener tanto el índice (que garantiza que pueda hacer referencia a él, por ejemplo, si tiene una lista de dictados) como el contenido real del elemento de la lista.

inlist = [{'field1':10, 'field2':20}, {'field1':30, 'field2':15}]    
for idx, i in enumerate(inlist):
    do some stuff with i['field1']
    if somecondition:
        xlist.append(idx)
for i in reversed(xlist): del inlist[i]

enumeratele da acceso al artículo y al índice a la vez. reversedes para que los índices que vas a eliminar más tarde no cambien en ti.

fantabolous
fuente
¿Por qué obtener el índice es más relevante en el caso de que tenga una lista de dictados que en el caso de cualquier otro tipo de lista? Esto no tiene sentido por lo que puedo decir.
Mark Amery
4

Es posible que desee utilizar filter()disponible como incorporado.

Para más detalles ver aquí

Xolve
fuente
4

La mayoría de las respuestas aquí quieren que crees una copia de la lista. Tuve un caso de uso donde la lista era bastante larga (110,000 artículos) y era más inteligente seguir reduciendo la lista.

En primer lugar, deberá reemplazar el bucle foreach con el bucle while ,

i = 0
while i < len(somelist):
    if determine(somelist[i]):
         del somelist[i]
    else:
        i += 1

El valor de ino se cambia en el bloque if porque querrá obtener el valor del nuevo elemento DESDE EL MISMO ÍNDICE, una vez que se elimine el elemento anterior.

Mujeeb
fuente
3

Puede probar el bucle for en reversa, por lo que para some_list hará algo como:

list_len = len(some_list)
for i in range(list_len):
    reverse_i = list_len - 1 - i
    cur = some_list[reverse_i]

    # some logic with cur element

    if some_condition:
        some_list.pop(reverse_i)

De esta manera, el índice está alineado y no sufre las actualizaciones de la lista (independientemente de si resalta el elemento cur o no).

Queequeg
fuente
Recorrerlo reversed(list(enumerate(some_list)))sería más simple que calcular los índices usted mismo.
Mark Amery
@ MarkAmery no cree que pueda alterar la lista de esta manera.
Queequeg
3

Una posible solución, útil si desea no solo eliminar algunas cosas, sino también hacer algo con todos los elementos en un solo bucle:

alist = ['good', 'bad', 'good', 'bad', 'good']
i = 0
for x in alist[:]:
    if x == 'bad':
        alist.pop(i)
        i -= 1
    # do something cool with x or just print x
    print(x)
    i += 1
Alexey
fuente
Realmente deberías usar las comprensiones. Son mucho más fáciles de entender.
Beefster
¿Qué badsucede si quiero eliminar cosas, hacer algo con él y también hacer algo con goodcosas en un ciclo?
Alexey
1
En realidad, me di cuenta de que hay algo de inteligencia aquí en que haces una copia de la lista con una porción abierta ( alist[:]) Y como podrías estar haciendo algo elegante, en realidad tiene un caso de uso. Buena revisión es buena. Toma mi voto positivo.
Beefster
2

Necesitaba hacer algo similar y en mi caso el problema era la memoria: necesitaba fusionar múltiples objetos de conjunto de datos dentro de una lista, después de hacer algunas cosas con ellos, como un nuevo objeto, y necesitaba deshacerme de cada entrada a la que me estaba fusionando evite duplicarlos todos y explotar la memoria. En mi caso, tener los objetos en un diccionario en lugar de una lista funcionó bien:

`` `

k = range(5)
v = ['a','b','c','d','e']
d = {key:val for key,val in zip(k, v)}

print d
for i in range(5):
    print d[i]
    d.pop(i)
print d

`` `

rafa
fuente
2

TLDR:

Escribí una biblioteca que te permite hacer esto:

from fluidIter import FluidIterable
fSomeList = FluidIterable(someList)  
for tup in fSomeList:
    if determine(tup):
        # remove 'tup' without "breaking" the iteration
        fSomeList.remove(tup)
        # tup has also been removed from 'someList'
        # as well as 'fSomeList'

Si es posible, es mejor usar otro método que no requiera modificar su iterable mientras itera sobre él, pero para algunos algoritmos podría no ser tan sencillo. Entonces, si está seguro de que realmente desea el patrón de código descrito en la pregunta original, es posible.

Debería funcionar en todas las secuencias mutables, no solo en las listas.


Respuesta completa:

Editar: El último ejemplo de código en esta respuesta ofrece un caso de uso de por qué a veces es posible que desee modificar una lista en lugar de utilizar una comprensión de la lista. La primera parte de las respuestas sirve como tutorial de cómo se puede modificar una matriz en su lugar.

La solución se sigue de esto respuesta (para una pregunta relacionada) de senderle. Lo que explica cómo se actualiza el índice de matriz mientras se itera a través de una lista que se ha modificado. La solución a continuación está diseñada para rastrear correctamente el índice de matriz incluso si se modifica la lista.

Descargue fluidIter.pydesde aquí https://github.com/alanbacon/FluidIterator , es solo un archivo único, por lo que no es necesario instalar git. No hay instalador, por lo que deberá asegurarse de que el archivo esté en la ruta de Python. El código ha sido escrito para python 3 y no ha sido probado en python 2.

from fluidIter import FluidIterable
l = [0,1,2,3,4,5,6,7,8]  
fluidL = FluidIterable(l)                       
for i in fluidL:
    print('initial state of list on this iteration: ' + str(fluidL)) 
    print('current iteration value: ' + str(i))
    print('popped value: ' + str(fluidL.pop(2)))
    print(' ')

print('Final List Value: ' + str(l))

Esto producirá el siguiente resultado:

initial state of list on this iteration: [0, 1, 2, 3, 4, 5, 6, 7, 8]
current iteration value: 0
popped value: 2

initial state of list on this iteration: [0, 1, 3, 4, 5, 6, 7, 8]
current iteration value: 1
popped value: 3

initial state of list on this iteration: [0, 1, 4, 5, 6, 7, 8]
current iteration value: 4
popped value: 4

initial state of list on this iteration: [0, 1, 5, 6, 7, 8]
current iteration value: 5
popped value: 5

initial state of list on this iteration: [0, 1, 6, 7, 8]
current iteration value: 6
popped value: 6

initial state of list on this iteration: [0, 1, 7, 8]
current iteration value: 7
popped value: 7

initial state of list on this iteration: [0, 1, 8]
current iteration value: 8
popped value: 8

Final List Value: [0, 1]

Arriba hemos usado el popmétodo en el objeto de la lista de fluidos. Otros métodos iterables comunes también se implementan tales como del fluidL[i], .remove, .insert, .append, .extend. La lista también se puede modificar utilizando sectores ( sortyreverse métodos no se implementan).

La única condición es que solo debe modificar la lista en su lugar, si en algún momento fluidLo lfue reasignado a un objeto de lista diferente, el código no funcionaría. El fluidLobjeto for todavía usaría el objeto original, pero quedaría fuera de alcance para que lo modifiquemos.

es decir

fluidL[2] = 'a'   # is OK
fluidL = [0, 1, 'a', 3, 4, 5, 6, 7, 8]  # is not OK

Si queremos acceder al valor de índice actual de la lista, no podemos usar enumerar, ya que esto solo cuenta cuántas veces se ha ejecutado el bucle for. En su lugar, utilizaremos el objeto iterador directamente.

fluidArr = FluidIterable([0,1,2,3])
# get iterator first so can query the current index
fluidArrIter = fluidArr.__iter__()
for i, v in enumerate(fluidArrIter):
    print('enum: ', i)
    print('current val: ', v)
    print('current ind: ', fluidArrIter.currentIndex)
    print(fluidArr)
    fluidArr.insert(0,'a')
    print(' ')

print('Final List Value: ' + str(fluidArr))

Esto generará lo siguiente:

enum:  0
current val:  0
current ind:  0
[0, 1, 2, 3]

enum:  1
current val:  1
current ind:  2
['a', 0, 1, 2, 3]

enum:  2
current val:  2
current ind:  4
['a', 'a', 0, 1, 2, 3]

enum:  3
current val:  3
current ind:  6
['a', 'a', 'a', 0, 1, 2, 3]

Final List Value: ['a', 'a', 'a', 'a', 0, 1, 2, 3]

La FluidIterableclase solo proporciona un contenedor para el objeto de lista original. Se puede acceder al objeto original como una propiedad del objeto fluido de la siguiente manera:

originalList = fluidArr.fixedIterable

Se pueden encontrar más ejemplos / pruebas en la if __name__ is "__main__":sección al final de fluidIter.py. Vale la pena verlos porque explican lo que sucede en diversas situaciones. Tales como: Reemplazar grandes secciones de la lista usando un segmento. O usando (y modificando) el mismo iterable en bucles anidados.

Como dije para comenzar: esta es una solución complicada que perjudicará la legibilidad de su código y dificultará la depuración. Por lo tanto, otras soluciones, como las comprensiones de listas mencionadas en la respuesta de David Raznick, deben considerarse primero. Dicho esto, he encontrado momentos en los que esta clase me ha sido útil y ha sido más fácil de usar que hacer un seguimiento de los índices de elementos que deben eliminarse.


Editar: como se menciona en los comentarios, esta respuesta realmente no presenta un problema para el cual este enfoque proporciona una solución. Trataré de abordar eso aquí:

Las comprensiones de listas proporcionan una forma de generar una nueva lista, pero estos enfoques tienden a ver cada elemento de forma aislada en lugar del estado actual de la lista en su conjunto.

es decir

newList = [i for i in oldList if testFunc(i)]

Pero, ¿qué pasa si el resultado testFuncdepende de los elementos que ya se han agregado newList? ¿O los elementos que todavía están en oldListeso podrían agregarse a continuación? Todavía puede haber una manera de utilizar una comprensión de la lista, pero comenzará a perder su elegancia, y para mí es más fácil modificar una lista en su lugar.

El siguiente código es un ejemplo de un algoritmo que sufre el problema anterior. El algoritmo reducirá una lista para que ningún elemento sea múltiplo de ningún otro elemento.

randInts = [70, 20, 61, 80, 54, 18, 7, 18, 55, 9]
fRandInts = FluidIterable(randInts)
fRandIntsIter = fRandInts.__iter__()
# for each value in the list (outer loop)
# test against every other value in the list (inner loop)
for i in fRandIntsIter:
    print(' ')
    print('outer val: ', i)
    innerIntsIter = fRandInts.__iter__()
    for j in innerIntsIter:
        innerIndex = innerIntsIter.currentIndex
        # skip the element that the outloop is currently on
        # because we don't want to test a value against itself
        if not innerIndex == fRandIntsIter.currentIndex:
            # if the test element, j, is a multiple 
            # of the reference element, i, then remove 'j'
            if j%i == 0:
                print('remove val: ', j)
                # remove element in place, without breaking the
                # iteration of either loop
                del fRandInts[innerIndex]
            # end if multiple, then remove
        # end if not the same value as outer loop
    # end inner loop
# end outerloop

print('')
print('final list: ', randInts)

La salida y la lista reducida final se muestran a continuación

outer val:  70

outer val:  20
remove val:  80

outer val:  61

outer val:  54

outer val:  18
remove val:  54
remove val:  18

outer val:  7
remove val:  70

outer val:  55

outer val:  9
remove val:  18

final list:  [20, 61, 7, 55, 9]
Resonancia
fuente
Es difícil saber si esto está sobre diseñado porque no está claro qué problema está tratando de resolver; ¿Qué logra eliminar elementos utilizando este enfoque que some_list[:] = [x for x in some_list if not some_condition(x)]no logra? Sin una respuesta a eso, ¿por qué debería alguien creer que descargar y usar su biblioteca de 600 líneas completa con errores tipográficos y código comentado es una mejor solución a su problema que el de una sola línea? -1.
Mark Amery
@MarkAmery. El caso de uso principal para cuando esto es cuando se trata de determinar si un elemento debe eliminarse (o agregarse o moverse) basándose no solo en el elemento en sí, sino en el estado de otro elemento en la lista o el estado de la lista como todo. Por ejemplo, con las comprensiones de listas no es posible escribir algo como de some_list[:] = [x for x in some_list if not some_condition(y)]dónde yes un elemento de lista diferente x. Tampoco sería posible escribir some_list[:] = [x for x in some_list if not some_condition(intermediateStateOf_some_list)].
Resonancia
2

El método más eficaz es la lista por comprensión, muchas personas muestran su caso, por supuesto, también es una buena manera de obtener una iteratora través filter.

Filterrecibe una función y una secuencia. Filteraplica la función pasada a cada elemento por turno y luego decide si desea retener o descartar el elemento dependiendo de si el valor de retorno de la función es Trueo False.

Hay un ejemplo (obtenga las probabilidades en la tupla):

list(filter(lambda x:x%2==1, (1, 2, 4, 5, 6, 9, 10, 15)))  
# result: [1, 5, 9, 15]

Precaución: tampoco puede manejar iteradores. Los iteradores son a veces mejores que las secuencias.

chseng
fuente
2

El bucle for se repetirá a través del índice.

considera que tienes una lista,

[5, 7, 13, 29, 65, 91]

tienes usando la variable de lista llamada lis. y usas lo mismo para eliminar ..

tu variable

lis = [5, 7, 13, 29, 35, 65, 91]
       0  1   2   3   4   5   6

durante la 5ta iteración,

su número 35 no era primo, por lo que lo eliminó de una lista.

lis.remove(y)

y luego el siguiente valor (65) pasa al índice anterior.

lis = [5, 7, 13, 29, 65, 91]
       0  1   2   3   4   5

así que el puntero de la cuarta iteración se movió al quinto.

Es por eso que su ciclo no cubre 65 ya que se movió al índice anterior.

por lo que no debe hacer referencia a la lista en otra variable que todavía haga referencia al original en lugar de la copia.

ite = lis #dont do it will reference instead copy

entonces copia de la lista usando list[::]

ahora te dará,

[5, 7, 13, 29]

El problema es que eliminó un valor de una lista durante la iteración, luego el índice de su lista colapsará.

para que puedas probar la comprensión en su lugar.

que admite todos los iterables como, list, tuple, dict, string, etc.

Mohideen bin Mohammed
fuente
Esto me ayudó a entender por qué fallaba mi código.
Wahid Sadik
2

Si desea eliminar elementos de una lista mientras itera, use un ciclo while para que pueda alterar el índice actual y el índice final después de cada eliminación.

Ejemplo:

i = 0
length = len(list1)

while i < length:
    if condition:
        list1.remove(list1[i])
        i -= 1
        length -= 1

    i += 1
Sin nombre
fuente
1

Las otras respuestas son correctas, por lo general es una mala idea eliminar de una lista que está iterando. La iteración inversa evita las trampas, pero es mucho más difícil seguir el código que hace eso, por lo que generalmente es mejor usar una comprensión de lista o filter.

Sin embargo, hay un caso en el que es seguro eliminar elementos de una secuencia que está iterando: si solo está eliminando un elemento mientras está iterando. Esto se puede garantizar con a returno a break. Por ejemplo:

for i, item in enumerate(lst):
    if item % 4 == 0:
        foo(item)
        del lst[i]
        break

Esto a menudo es más fácil de entender que una comprensión de la lista cuando realiza algunas operaciones con efectos secundarios en el primer elemento de una lista que cumple alguna condición y luego elimina ese elemento de la lista inmediatamente después.

Carne de res
fuente
1

Se me ocurren tres enfoques para resolver su problema. Como ejemplo, crearé una lista aleatoria de tuplas somelist = [(1,2,3), (4,5,6), (3,6,6), (7,8,9), (15,0,0), (10,11,12)]. La condición que elijo es sum of elements of a tuple = 15. En la lista final solo tendremos esas tuplas cuya suma no es igual a 15.

Lo que he elegido es un ejemplo elegido al azar. Siéntase libre de cambiar la lista de tuplas y la condición que he elegido.

Método 1.> Use el marco que había sugerido (donde se completa un código dentro de un bucle for). Utilizo un código pequeño con delpara eliminar una tupla que cumple con dicha condición. Sin embargo, este método perderá una tupla (que satisface dicha condición) si dos tuplas colocadas consecutivamente cumplen con la condición dada.

for tup in somelist:
    if ( sum(tup)==15 ): 
        del somelist[somelist.index(tup)]

print somelist
>>> [(1, 2, 3), (3, 6, 6), (7, 8, 9), (10, 11, 12)]

Método 2.> Construya una nueva lista que contenga elementos (tuplas) donde no se cumpla la condición dada (esto es lo mismo que eliminar elementos de la lista donde se cumple la condición dada). El siguiente es el código para eso:

newlist1 = [somelist[tup] for tup in range(len(somelist)) if(sum(somelist[tup])!=15)]

print newlist1
>>>[(1, 2, 3), (7, 8, 9), (10, 11, 12)]

Método 3.> Encuentre índices donde se cumpla la condición dada y luego use elementos de eliminación (tuplas) correspondientes a esos índices. El siguiente es el código para eso.

indices = [i for i in range(len(somelist)) if(sum(somelist[i])==15)]
newlist2 = [tup for j, tup in enumerate(somelist) if j not in indices]

print newlist2
>>>[(1, 2, 3), (7, 8, 9), (10, 11, 12)]

El método 1 y el método 2 son más rápidos que el método 3 . Método2 y método3 son más eficientes que método1. Yo prefiero metodo2 . Para el ejemplo mencionado anteriormente,time(method1) : time(method2) : time(method3) = 1 : 1 : 1.7

Siddharth Satpathy
fuente
0

Para cualquier cosa que tenga el potencial de ser realmente grande, utilizo lo siguiente.

import numpy as np

orig_list = np.array([1, 2, 3, 4, 5, 100, 8, 13])

remove_me = [100, 1]

cleaned = np.delete(orig_list, remove_me)
print(cleaned)

Eso debería ser significativamente más rápido que cualquier otra cosa.

CENTURION
fuente
Por lo que medí, NumPy comienza a ser más rápido para listas de más de 20 elementos, y alcanza un filtrado> 12 veces más rápido para listas grandes de 1000 elementos y más.
Georgy
0

En algunas situaciones, donde está haciendo más que simplemente filtrar una lista de un elemento a la vez, desea que su iteración cambie mientras itera.

Aquí hay un ejemplo donde copiar la lista de antemano es incorrecto, la iteración inversa es imposible y la comprensión de la lista tampoco es una opción.

""" Sieve of Eratosthenes """

def generate_primes(n):
    """ Generates all primes less than n. """
    primes = list(range(2,n))
    idx = 0
    while idx < len(primes):
        p = primes[idx]
        for multiple in range(p+p, n, p):
            try:
                primes.remove(multiple)
            except ValueError:
                pass #EAFP
        idx += 1
        yield p
CodeKid
fuente
0

Si usará la nueva lista más adelante, simplemente puede establecer el elemento en Ninguno y luego juzgarlo en el ciclo posterior, de esta manera

for i in li:
    i = None

for elem in li:
    if elem is None:
        continue

De esta manera, no necesita copiar la lista y es más fácil de entender.

John Zhang
fuente
-1

suponga una lista de números y desea eliminar todos los no que sean divisibles por 3,

list_number =[i for i in range(100)]

usando list comprehension, esto creará una nueva lista y creará un nuevo espacio de memoria

new_list =[i for i in list_number if i%3!=0]

usando la lambda filterfunción, esto creará una nueva lista resultante y consumirá espacio de memoria

new_list = list(filter(lambda x:x%3!=0, list_number))

sin consumir espacio de memoria para la nueva lista y modificar la lista existente

for index, value in enumerate(list_number):
    if list_number[index]%3==0:
        list_number.remove(value)
sahasrara62
fuente