Pregunta

Esta pregunta ya tiene una respuesta aquí:

Cuando estoy escribiendo el código en Python, a menudo es necesario para eliminar elementos de una lista o de otro tipo de secuencia basada en algunos criterios.No he encontrado una solución que es elegante y eficiente, como la eliminación de elementos de una lista que se está actualmente iteración es malo.Por ejemplo, usted no puede hacer esto:

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

Yo por lo general terminan 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

Este es innefficient, bastante feo y posiblemente buggy (¿cómo se manejan múltiples 'John Smith' entradas?).¿Alguien tiene una solución más elegante, o al menos una más eficaz?

¿Y uno que trabaja con los diccionarios?

¿Fue útil?

Solución

Dos maneras fáciles de lograr solo el filtrado son:

  1. El uso de filter:

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

  2. El uso de la lista de comprensión:

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

Tenga en cuenta que ambos casos, mantener los valores para los cuales la función de predicado se evalúa a True, entonces , tienes que invertir la lógica (es decir,usted dice "mantener a la gente que no tiene el apellido "Smith" en lugar de "eliminar a las personas que tienen el apellido "Smith").

Editar Divertido...dos personas de forma individual publicado tanto de las respuestas que me sugirió como me fue la publicación de la mina.

Otros consejos

También se puede recorrer 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 crear una nueva lista (como filter o una lista de comprensión), y usa un iterador en lugar de una lista de copia (como [:]).

Tenga en cuenta que aunque la eliminación de los elementos, mientras que recorrer hacia atrás es seguro, la inserción de ellos es un poco más complicado.

La respuesta obvia es la que John y un par de otras personas dio, a saber:

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

Pero que tiene la desventaja de que se crea un nuevo objeto de la 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

La asignación a los "nombres[:]" significa, básicamente, "reemplazar el contenido de la lista de nombres con el siguiente valor".Es diferente de la de asignar a los nombres, en la que no crea un nuevo objeto de la lista.El lado derecho de la asignación es un generador de expresión (nota: el uso de paréntesis en lugar de corchetes).Esto hará que Python para recorrer toda la lista.

Algunas rápida de perfiles sugiere que esto es alrededor de 30% más rápido que la lista de comprensión de enfoque, y alrededor de un 40% más rápido que el filtro de enfoque.

Advertencia:mientras que esta solución es más rápida que la solución obvia, es más oscura, y se basa en la más avanzada de Python técnicas.Si usted lo utiliza, recomiendo acompañarlo con un comentario.Probablemente es sólo vale la pena utilizar en casos en los que realmente se preocupan por el rendimiento de esta operación en particular (que es bastante rápido, no importa qué).(En el caso de que yo este, yo estaba haciendo Un* * haz la búsqueda, y se utiliza para eliminar la búsqueda de puntos a partir de la búsqueda de la viga.)

El uso de una lista de comprensión

list = [x for x in list if x[-5:] != "smith"]

Hay veces cuando el filtrado (ya sea a través del filtro o una lista de comprensión) no funciona.Esto sucede cuando algún otro objeto es la celebración de una referencia a la lista que se está modificando y que usted necesita para modificar la lista en el lugar.

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

La única diferencia con el original del código es el uso de names[:] en lugar de names en el bucle for.De esa manera el código recorre más de un (poco profundas) copia de la lista y las suspensiones funcionan como se esperaba.Desde la lista de copia es poco, es bastante rápido.

el filtro sería impresionante para este.Ejemplo sencillo:

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

Editar: Corey comprensión de lista es impresionante también.

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

Ambas soluciones, filtro y la comprensión de la requiere la construcción de una nueva lista.No sé lo suficiente de la Python interna para estar seguro, pero me creo que una más tradicional (pero menos elegante) enfoque 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 la lista corta, me quedo con cualquiera de las dos soluciones propuestas anteriormente.

Para responder a su pregunta acerca de cómo trabajar con diccionarios, debe tener en cuenta que Python 3.0 incluyen dict comprensiones de:

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

En el tiempo medio, se puede hacer un cuasi-dict la comprensión de esta manera:

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

O como una respuesta directa:

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

Si la lista debe ser filtrada en el lugar y el tamaño de la lista es bastante grande, entonces los algoritmos mencionados en las respuestas anteriores, las cuales están basadas en la lista.remove(), puede ser inadecuado, debido a su complejidad computacional es O(n^2).En este caso se puede utilizar la siguiente no-tan python función:

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://stackoverflow.com/a/4639748/274937 es superior a la mía solución.Es más python y funciona más rápido.Así que, aquí está un nuevo filter_inplace() aplicación:

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)]

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

  • Ellos hacen una copia de su lista y devuelva una nueva, y que será ineficaz cuando la lista original es muy grande
  • Puede ser realmente incómodo cuando los criterios para seleccionar elementos (en su caso, si el nombre de[-5:] == 'Smith') es más complicado, o tiene varias condiciones.

Su solución original es realmente más eficaz para muy grandes listas, incluso si estamos de acuerdo en que es más feo.Pero si le preocupa que usted puede tener varios 'John Smith', puede ser corregido mediante la eliminación basada en la posición y no en el 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 escoger una solución sin tener en cuenta el tamaño de la lista, pero para grandes listas prefiero tu 2-pase solución en lugar del filtro o listas de comprensión

En el caso de un conjunto.

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

Aquí está mi filter_inplace la aplicación que se puede utilizar para filtrar los elementos de una lista en el lugar, se me ocurrió esto en mi propia forma independiente antes de encontrar esta página.Es el mismo algoritmo que lo PabloG publicado, sólo se hizo más genérico, así que usted puede utilizar para filtrar las listas en el lugar, también es capaz de eliminar de la lista basada en el comparisonFunc si está invertido se establece 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á utilizando.El uso de una tabla hash, por ejemplo.Algunas implementaciones soportan múltiples entradas por clave, de modo que uno pueda pop el elemento más nuevo, o quite todos ellos.

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

editar: uno no se dan cuenta de que él pidió la "eficiencia"...todos estos métodos sugeridos sólo iterar sobre la lista, que es el mismo como lo sugirió.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top