Manipulación de listas en Python con pop ()

En resumen, necesito eliminar varios elementos de una lista de acuerdo con sus índices. Sin embargo, no puedo usar el pop porque cambia los índices (sin algún sistema de compensación torpe). ¿Hay una manera de eliminar varios elementos simultáneamente?

Tengo un algoritmo que recorre una lista y, si las condiciones son adecuadas, elimina ese elemento mediante el método emergente. Surge un problema ya que todo esto se hace en un bucle. Una vez que se hace el pop, la lista se reduce en uno, desplazando todos los valores en uno. Así que el bucle saldrá de rango. ¿Es posible eliminar varios elementos simultáneamente u otra solución?

Un ejemplo de mi problema:

L = ['a', 'b', 'c', 'd'] for i in range(len(L)): print L if L[i] == 'a' or L[i] == 'c': L.pop(i) 

¿Sus listas son grandes? Si es así, use ifilter de itertools para filtrar los elementos que no quiere perezosamente (sin costo inicial).

¿Las listas no son tan grandes? Solo usa una lista de comprensión:

  newlist = [x for x in oldlist if x not in ['a', 'c'] ] 

Esto creará una nueva copia de la lista. Esto generalmente no es un problema para la eficiencia a menos que realmente te importe el consumo de memoria.

Como un medio feliz de conveniencia de syntax y holgazanería (= eficiencia para listas grandes), puede construir un generador en lugar de una lista usando ( ) lugar de [ ] :

 interestingelts = (x for x in oldlist if x not in ['a', 'c']) 

Después de esto, puedes recorrer iteraciones interestingelts , pero no puedes indexarlo:

  for y in interestingelts: # ok print y print interestingelts[0] # not ok: generator allows sequential access only 

Quieres una lista de comprensión:

 L = [c for c in L if c not in ['a', 'c']] 

O, si realmente no quieres crear una copia, ve hacia atrás:

 for i in reversed(range(len(L))): if L[i] in ['a', 'c']: L.pop(i) # del L[i] is more efficient 

Gracias a ncoghlan para reversed() y phooji por las sugerencias del L[i] . (Decidí dejarlo como L.pop(i) , ya que así se formuló inicialmente la pregunta).

Además, como JS Sebastian señala correctamente, ir hacia atrás es eficiente en términos de espacio pero ineficiente; la mayoría de las veces, lo mejor es una lista de comprensión o generador ( L = (...) lugar de L = [...] ).

Editar:

Bien, ya que la gente parece querer algo menos ridículamente lento que el método revertido anterior (no puedo imaginar por qué … 🙂 aquí hay un filtro en el lugar para preservar los pedidos que debe diferir en velocidad de una lista de comprensión solo por una constante. (Esto es similar a lo que haría si quisiera filtrar una cadena en c.)

 write_i = 0 for read_i in range(len(L)): L[write_i] = L[read_i] if L[read_i] not in ['a', 'c']: write_i += 1 del L[write_i:] print L # output: ['b', 'd'] 

Resumen

  • use la comprensión de lista (o genexpr) para eliminar varios elementos de una lista
  • Si su entrada es una cadena grande de bytes, use str.translate() para eliminar caracteres
  • eliminar un elemento a la vez, la del L[i] es lenta para listas grandes

Si los elementos son bytes como en su ejemplo, podría usar str.translate() :

 def remove_bytes(bytestr, delbytes): """ >>> remove_bytes(b'abcd', b'ac') == b'bd' True """ return bytestr.translate(None, delbytes) 

En general, varios elementos podrían eliminarse utilizando el corte:

 def remove_inplace_without_order(L, delitems): """Remove all items from `L` that are in `delitems` (not preserving order). >>> L = list(range(4)); remove_inplace_without_order(L, [0,2]); L [3, 1] """ idel = len(L) # items idel.. to be removed for i in reversed(range(len(L))): if L[i] in delitems: idel -= 1 L[i] = L[idel] # save `idel`-th item del L[idel:] # remove items all at once #NOTE: the function returns `None` (it means it modifies `L` inplace) 

Como @phooji y @senderle ya mencionamos, en su caso es preferible la comprensión de lista (o expresión del generador):

 def remove_listcomp(L, delitems): return [x for x in L if x not in delitems] 

Aquí hay una comparación de rendimiento para L=list("abcd"*10**5); delitems="ac" L=list("abcd"*10**5); delitems="ac" :

 | function | time, msec | ratio | |------------------------------+------------+--------| | list | 4.42 | 0.9 | | remove_bytes | 4.88 | 1.0 | | remove | 27.3 | 5.6 | | remove_listcomp | 36.8 | 7.5 | | remove_inplace_without_order | 71.2 | 14.6 | | remove_inplace_senderle2 | 83.8 | 17.2 | | remove_inplace_senderle | 15000 | 3073.8 | #+TBLFM: $3=$2/@3$2;%.1f 

Dónde

 try: from itertools import ifilterfalse as filterfalse except ImportError: from itertools import filterfalse # py3k def remove(L, delitems): return filterfalse(delitems.__contains__, L) def remove_inplace_senderle(L, delitems): for i in reversed(range(len(L))): if L[i] in delitems: del L[i] def remove_inplace_senderle2(L, delitems): write_i = 0 for read_i in range(len(L)): L[write_i] = L[read_i] if L[read_i] not in delitems: write_i += 1 del L[write_i:] 

remove_inplace_senderle() es lento debido a que utiliza el algoritmo O(N**2) . Cada del L[i] puede hacer que todos los elementos a la derecha se muevan a la izquierda para cerrar la brecha.

La columna de tiempo en la tabla anterior incluye el tiempo que lleva crear una nueva lista de entrada (la primera fila) debido a que algunos algoritmos modifican una entrada en el lugar.

Aquí hay tiempos para la misma entrada pero sin crear una nueva lista en cada iteración:

  | function | time, msec | ratio | |-----------------+------------+-------| | remove_bytes | 0.391 | 1 | | remove | 24.3 | 62 | | remove_listcomp | 33.4 | 85 | #+TBLFM: $3=$2/@2$2;%d 

La tabla muestra que itertools.ifilterfalse() no proporciona una mejora significativa con respecto a listcomp.

En general, no vale la pena ni es perjudicial pensar en el rendimiento de dichas tareas a menos que un generador de perfiles haya demostrado que este código es un cuello de botella y que es importante para su progtwig. Pero podría ser útil conocer enfoques alternativos que podrían proporcionar más de un orden de magnitud en la mejora de la velocidad.