Python: eliminando el elemento de la lista mientras se itera sobre la lista [duplicado]

Estoy iterando sobre una lista de elementos en Python, hago alguna acción sobre ella y luego los elimino si cumplen con ciertos criterios.

for element in somelist:
    do_action(element)
    if check(element):
        remove_element_from_list

¿Qué debo usar en lugar de remove_element? He visto preguntas similares, pero noto la presencia de la parte do_action que se ejecutará para todos los elementos y, por lo tanto, elimina la solución de usar filtros.

preguntado el 16 de mayo de 11 a las 20:05

¿No se puede dividir en dos pasos, es decir, para que todos los elementos realicen la acción y luego eliminen los elementos? -

Debes NUNCA eliminar un elemento de una lista mientras se itera sobre él en un bucle for. En su lugar, podría usar un bucle while. O bien, registre los índices de todos los elementos que desea eliminar y luego elimínelos después de que se complete la iteración -

Para todos: necesito modificar la lista en su lugar, no hay copias. En realidad, la lista se pasa a una función y esa función debe modificar la lista. -

@Scrontch, todavía es posible (y mejor) recorrer la lista y luego reemplazar su contenido como una segunda pasada. Por eso utilizo [:] en mi respuesta

@ inspectorG4dget: Su primera oración es insostenible, vea mi respuesta. -

9 Respuestas

Siempre puedes iterar sobre una copia de la lista, dejándote libre de modificar el original:

for item in list(somelist):
  ...
  somelist.remove(item)

Respondido el 16 de junio de 13 a las 20:06

@Scrontch Dados los criterios adicionales de "modificar la lista en su lugar", esta parece la solución más limpia. Como algunos han mencionado, no desea iterar sobre la misma lista que está modificando. - John Gaines Jr.

Ok, pero parece que no funciona bien. ¿No será esto O (n ^ 2)? (Y eso sin contar la copia de la lista inicial). Eliminar el elemento sobre la marcha sería O (n). - Scrontch

usar mapas (o listas de comprensión) para efectos secundarios y desechar el resultado no es muy pitónico - John La Rooy

La implementación alternativa es incorrecta. Deberá invertir 'toremove' antes de la función de mapa. De lo contrario, los índices posteriores apuntan a objetos incorrectos. - Akkishore

Esto parece bastante roto list.remove elimina la primera aparición de un valor por igualdad, si estaba intentando eliminar todos los valores flotantes de [0, 1, 1.0, 0] solo haciendo .remove(0.0) etc., terminarías con [1.0, 0.0] que es definitivamente no es el resultado con todo el flotador eliminado. - Tadhg McDonald-Jensen

Para cumplir con estos criterios: modificar la lista original in situ, sin copias de la lista, solo una pasada, funciona, una solución tradicional es iterar hacia atrás:

for i in xrange(len(somelist) - 1, -1, -1):
    element = somelist[i]
    do_action(element)
    if check(element):
        del somelist[i]

Bono: no funciona len(somelist) en cada iteración. Funciona en cualquier versión de Python (al menos hasta 1.5.2) ... s / xrange / range / para 3.X.

Actualización: si desea iterar hacia adelante, es posible, solo que más complicado y más feo:

i = 0
n = len(somelist)
while i < n:
    element = somelist[i]
    do_action(element)
    if check(element):
        del somelist[i]
        n = n - 1
    else:
        i = i + 1

contestado el 17 de mayo de 11 a las 03:05

También podrías usar reversed(range(len(somelist))) para que se vea un poco mejor - Ben Ruijl

es un algoritmo cuadráticoO(n**2)). Aquí está un solución lineal si hay muchos elementos para eliminar y no quieres usar lista de comprensión por alguna razon. - jfs

¿Es este comportamiento documentado / definido, o es un comportamiento indefinido que funciona (por ahora)? - Baruch

Lista de comp:

results = [x for x in (do_action(element) for element in somelist) if check(element)]

contestado el 17 de mayo de 11 a las 01:05

pero el OP quiere llamar a la función "comprobar" Si lo envía después llamando a do_action - Riccardo Galli

for element in somelist:
    do_action(element)
somelist[:] = (x for x in somelist if not check(x))

Si realmente necesita hacerlo de una pasada sin copiar la lista

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

contestado el 17 de mayo de 11 a las 03:05

También puede usar una expresión generadora, luego no se crea una lista temporal: somelist[:] = (x for x in somelist if not check(element)) - pillmuncher

-1 La revisión del código elemental dice "No funciona". - John Machin

@John, bueno, la idea estaba ahí. Eso es lo que obtengo por escribir código no probado a las 6:30 antes de mi café. John La Rooy

Todavía puede usar el filtro, moviendo a una función externa la modificación del elemento (iterando solo una vez)

def do_the_magic(x):
    do_action(x)
    return check(x)

# you can get a different filtered list
filter(do_the_magic,yourList)

# or have it modified in place (as suggested by Steven Rumbalski, see comment)
yourList[:] = itertools.ifilter(do_the_magic, yourList)

Respondido 13 Feb 12, 16:02

Tus argumentos para filter están en el orden incorrecto. Además, quiere que la lista se modifique en su lugar, así que use itertools.ifilter y asignar a un segmento: yourList[:] = itertools.ifilter(do_the_magic, yourList) - Steven Rumbalski

gracias, arreglado el pedido. No noté el requisito de "en su lugar": Riccardo Galli

Otra forma de hacerlo es:

while i<len(your_list):
    if #condition :
        del your_list[i]
    else:
        i+=1

Entonces, borra los elementos uno al lado del otro mientras verifica

Respondido el 12 de junio de 13 a las 11:06

Puede hacer un generador que devuelva todo lo que no se elimina:

def newlist(somelist):
    for element in somelist:
        do_action(element)
        if not check(element):
            yield element

contestado el 17 de mayo de 11 a las 00:05

Excepto que el OP quiere que la lista se modifique en su lugar, presumiblemente hay otras referencias a ella, por lo que crear una copia filtrada no resuelve todo el problema. - PaulMcG

@Paul, es un poco injusto juzgar por criterios que no formaban parte de la pregunta original. El requisito estricto de realizar la actualización en el lugar llegó en un comentario después de que di esta respuesta. - Mark Ransom

es cierto, veo esto todo el tiempo con las preguntas de análisis. Hay un proceso de descubrimiento, ya que la pregunta original generalmente omite 1 o 12 (¡o 12!) Bits de información vitales. - PaulMcG

Para hacer que esto se asigne en el lugar a un segmento: somelist[:] = (x for x in newlist(someList)) - Steven Rumbalski

¿Por qué no reescribirlo para que sea

for element in somelist: 
   do_action(element)  

if check(element): 
    remove_element_from_list

Consulte esta pregunta para saber cómo eliminar de la lista, aunque parece que ya la ha visto. Eliminar elementos de una lista mientras se itera

Otra opción es hacer esto si realmente desea mantenerlo igual

newlist = [] 
for element in somelist: 
   do_action(element)  

   if not check(element): 
      newlst.append(element)

contestado el 23 de mayo de 17 a las 15:05

No exactamente en el lugar, pero alguna idea para hacerlo:

a = ['a', 'b']

def inplace(a):
    c = []
    while len(a) > 0:
        e = a.pop(0)
        if e == 'b':
            c.append(e)
    a.extend(c)

Puede extender la función para llamar a su filtro en la condición.

Respondido el 12 de junio de 13 a las 14:06

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.