una tuplas por palabra listas lista elementos diccionario comprensión colecciones buscar agregar python optimization set series

tuplas - listas en python



¿Manera elegante de eliminar elementos de la secuencia en Python? (14)

Ambas soluciones, filtro y comprensión requieren construir una nueva lista. No conozco las partes internas de Python para estar seguro, pero creo que un enfoque más tradicional (pero menos elegante) podría ser más eficiente:

names = [''Jones'', ''Vai'', ''Smith'', ''Perez''] item = 0 while item <> len(names): name = names [item] if name==''Smith'': names.remove(name) else: item += 1 print names

De todos modos, para listas cortas, me quedo con cualquiera de las dos soluciones propuestas anteriormente.

Esta pregunta ya tiene una respuesta aquí:

Cuando estoy escribiendo código en Python, a menudo necesito eliminar elementos de una lista u otro tipo de secuencia en función de algunos criterios. No he encontrado una solución que sea elegante y eficiente, ya que eliminar elementos de una lista que está iterando actualmente es malo. Por ejemplo, no puedes hacer esto:

for name in names: if name[-5:] == ''Smith'': names.remove(name)

Suelo terminar haciendo algo como esto:

toremove = [] for name in names: if name[-5:] == ''Smith'': toremove.append(name) for name in toremove: names.remove(name) del toremove

Esto es ineficiente, bastante feo y posiblemente con errores (¿cómo maneja múltiples entradas de ''John Smith''?). ¿Alguien tiene una solución más elegante, o al menos una más eficiente?

¿Qué tal uno que funciona con diccionarios?


Aquí está mi implementación filter_inplace que se puede usar para filtrar elementos de una lista en el lugar, se me ocurrió esto por mi cuenta de forma independiente antes de encontrar esta página. Es el mismo algoritmo que publicó PabloG, que acaba de ser más genérico para que pueda usarlo para filtrar las listas en su lugar, también puede eliminarlo de la lista en función de la comparisonFunc Funk si se invierte se establece en True ; una especie de filtro invertido si se quiere.

def filter_inplace(conditionFunc, list, reversed=False): index = 0 while index < len(list): item = list[index] shouldRemove = not conditionFunc(item) if reversed: shouldRemove = not shouldRemove if shouldRemove: list.remove(item) else: index += 1


Bueno, esto es claramente un problema con la estructura de datos que estás usando. Use una tabla hash por ejemplo. Algunas implementaciones admiten entradas múltiples por clave, por lo que uno puede mostrar el elemento más nuevo o eliminarlos todos.

Pero esto es, y lo que vas a encontrar la solución es elegancia a través de una estructura de datos diferente, no de algoritmo. Tal vez puedas mejorar si está ordenado, o algo así, pero la iteración en una lista es tu único método aquí.

editar: uno se da cuenta de que pidió "eficiencia" ... todos estos métodos sugeridos simplemente iteran sobre la lista, que es lo mismo que sugirió.


Dos formas sencillas de lograr solo el filtrado son:

  1. Usando filter :

    names = filter(lambda name: name[-5:] != "Smith", names)

  2. Usando listas de comprensión:

    names = [name for name in names if name[-5:] != "Smith"]

Tenga en cuenta que ambos casos mantienen los valores para los que la función de predicado se evalúa como True , por lo que debe invertir la lógica (es decir, diga "mantener a las personas que no tienen el apellido Smith" en lugar de "eliminar a las personas que tienen el último nombre Smith ").

Editar divertido ... dos personas publicaron individualmente las dos respuestas que sugerí cuando estaba publicando la mía.


El filtro y las listas de comprensión están bien para su ejemplo, pero tienen un par de problemas:

  • Hacen una copia de su lista y devuelven la nueva, y eso será ineficiente cuando la lista original sea realmente grande
  • Pueden ser muy engorrosos cuando el criterio para elegir elementos (en su caso, si el nombre es [-5:] == ''Smith'') es más complicado o tiene varias condiciones.

Su solución original es en realidad más eficiente para listas muy grandes, incluso si podemos aceptar que es más fea. Pero si te preocupa que puedas tener varios ''John Smith'', se puede solucionar borrando en función de la posición y no del valor:

names = [''Jones'', ''Vai'', ''Smith'', ''Perez'', ''Smith''] toremove = [] for pos, name in enumerate(names): if name[-5:] == ''Smith'': toremove.append(pos) for pos in sorted(toremove, reverse=True): del(names[pos]) print names

No podemos elegir una solución sin considerar el tamaño de la lista, pero para listas grandes preferiría su solución de 2 pasos en lugar del filtro o lista de comprensiones


En el caso de un conjunto.

toRemove = set([]) for item in mySet: if item is unwelcome: toRemove.add(item) mySets = mySet - toRemove


Hay ocasiones en que el filtrado (ya sea mediante el uso de filtro o una lista de comprensión) no funciona. Esto sucede cuando otro objeto contiene una referencia a la lista que está modificando y necesita modificar la lista en su lugar.

for name in names[:]: if name[-5:] == ''Smith'': names.remove(name)

La única diferencia con el código original es el uso de names[:] lugar de names en el ciclo for. De esta forma, el código itera sobre una copia (poco profunda) de la lista y las eliminaciones funcionan como se espera. Como la copia de listas es superficial, es bastante rápida.


La respuesta obvia es la que dieron Juan y otras personas, a saber:

>>> names = [name for name in names if name[-5:] != "Smith"] # <-- slower

Pero eso tiene la desventaja de que crea un nuevo objeto de lista, en lugar de reutilizar el objeto original. Hice algunos perfiles y la experimentación, y el método más eficiente que se me ocurrió es:

>>> names[:] = (name for name in names if name[-5:] != "Smith") # <-- faster

Asignar a "nombres [:]" significa básicamente "reemplazar el contenido de la lista de nombres con el siguiente valor". Es diferente de solo asignarle nombres, ya que no crea un nuevo objeto de lista. El lado derecho de la tarea es una expresión generadora (tenga en cuenta el uso de paréntesis en lugar de corchetes). Esto hará que Python itere en la lista.

Algunos perfiles rápidos sugieren que esto es aproximadamente un 30% más rápido que el enfoque de la lista de comprensión, y aproximadamente un 40% más rápido que el enfoque de filtro.

Advertencia : si bien esta solución es más rápida que la solución obvia, es más oscura y se basa en técnicas de Python más avanzadas. Si lo usa, le recomiendo que lo acompañe con un comentario. Probablemente solo valga la pena usarlo en casos en los que realmente se preocupe por el rendimiento de esta operación en particular (lo cual es bastante rápido sin importar qué). (En el caso en que utilicé esto, estaba haciendo una búsqueda de haz A * y lo usé para eliminar los puntos de búsqueda del haz de búsqueda.)


Para responder a su pregunta sobre el trabajo con diccionarios, debe tener en cuenta que Python 3.0 incluirá las siguientes definiciones :

>>> {i : chr(65+i) for i in range(4)}

Mientras tanto, puedes hacer una comprensión cuasídica de esta manera:

>>> dict([(i, chr(65+i)) for i in range(4)])

O como una respuesta más directa:

dict([(key, name) for key, name in some_dictionary.iteritems if name[-5:] != ''Smith''])


Si la lista debe filtrarse in situ y el tamaño de la lista es bastante grande, los algoritmos mencionados en las respuestas anteriores, que se basan en list.remove (), pueden ser inadecuados, porque su complejidad computacional es O (n ^ 2) . En este caso, puede usar la siguiente función no pitonica:

def filter_inplace(func, original_list): """ Filters the original_list in-place. Removes elements from the original_list for which func() returns False. Algrithm''s computational complexity is O(N), where N is the size of the original_list. """ # Compact the list in-place. new_list_size = 0 for item in original_list: if func(item): original_list[new_list_size] = item new_list_size += 1 # Remove trailing items from the list. tail_size = len(original_list) - new_list_size while tail_size: original_list.pop() tail_size -= 1 a = [1, 2, 3, 4, 5, 6, 7] # Remove even numbers from a in-place. filter_inplace(lambda x: x & 1, a) # Prints [1, 3, 5, 7] print a

Editar: en realidad, la solución en https://.com/a/4639748/274937 es superior a la solución de la mina. Es más pitónico y funciona más rápido. Entonces, aquí hay una nueva implementación de filter_inplace ():

def filter_inplace(func, original_list): """ Filters the original_list inplace. Removes elements from the original_list for which function returns False. Algrithm''s computational complexity is O(N), where N is the size of the original_list. """ original_list[:] = [item for item in original_list if func(item)]


También puede iterar hacia atrás sobre la lista:

for name in reversed(names): if name[-5:] == ''Smith'': names.remove(name)

Esto tiene la ventaja de que no crea una nueva lista (como filter o una lista de comprensión) y utiliza un iterador en lugar de una copia de lista (como [:] ).

Tenga en cuenta que aunque eliminar elementos mientras se itera hacia atrás es seguro, insertarlos es un poco más complicado.



filtro sería increíble para esto. Ejemplo simple:

names = [''mike'', ''dave'', ''jim''] filter(lambda x: x != ''mike'', names) [''dave'', ''jim'']

Editar: la comprensión de la lista de Corey es increíble también.


names = filter(lambda x: x[-5:] != "Smith", names);