2009-08-05 20 views
30

Si tengo una lista de diccionarios, por ejemplo:Python: eliminar el diccionario de la lista

[{'id': 1, 'name': 'paul'}, 
{'id': 2, 'name': 'john'}] 

y me gustaría quitar el diccionario con id de 2 (o el nombre de John), ¿cuál es la forma más eficiente ir sobre esto programáticamente (es decir, no sé el índice de la entrada en la lista, así que no puede simplemente aparecer).

Respuesta

61
thelist[:] = [d for d in thelist if d.get('id') != 2] 

Editar: como algunas dudas se han expresado en un comentario acerca de la ejecución de este código (algunos basada en un malentendido características de rendimiento de Python, algunos en el supuesto más allá de las especificaciones, dado que hay exactamente un dict en el lista con un valor de 2 para la clave 'id'), deseo ofrecerle tranquilidad en este punto.

En un sistema Linux de edad, la medición de este código:

$ python -mtimeit -s"lod=[{'id':i, 'name':'nam%s'%i} for i in range(99)]; import random" "thelist=list(lod); random.shuffle(thelist); thelist[:] = [d for d in thelist if d.get('id') != 2]" 
10000 loops, best of 3: 82.3 usec per loop 

de los cuales unos 57 microsegundos para la random.shuffle (necesario para asegurar que el elemento a eliminar no está siempre en el mismo lugar ;-) y 0.65 microsegundos para la copia inicial (quienquiera que se preocupe por el impacto en el rendimiento de las copias superficiales de las listas de Python es obviamente fuera del almuerzo ;-), necesario para evitar alterar la lista original en el ciclo (para que cada tramo del ciclo tenga algo que borrar;-).

Cuando se sabe que hay exactamente un elemento para eliminarlo, es posible localizar y eliminar aún más expedita:

$ python -mtimeit -s"lod=[{'id':i, 'name':'nam%s'%i} for i in range(99)]; import random" "thelist=list(lod); random.shuffle(thelist); where=(i for i,d in enumerate(thelist) if d.get('id')==2).next(); del thelist[where]" 
10000 loops, best of 3: 72.8 usec per loop 

(utilice el next incorporado en lugar del método .next si estás en Python 2.6 o superior, por supuesto), pero este código se rompe si la cantidad de dictados que satisfacen la condición de eliminación no es exactamente uno. Generalizar esto, tenemos:

$ python -mtimeit -s"lod=[{'id':i, 'name':'nam%s'%i} for i in range(33)]*3; import random" "thelist=list(lod); where=[i for i,d in enumerate(thelist) if d.get('id')==2]; where.reverse()" "for i in where: del thelist[i]" 
10000 loops, best of 3: 23.7 usec per loop 

donde el barajado se puede quitar porque ya hay tres predice equidistantes a eliminar, tal como la conocemos. Y el listcomp, sin cambios, tarifas así:

$ python -mtimeit -s"lod=[{'id':i, 'name':'nam%s'%i} for i in range(33)]*3; import random" "thelist=list(lod); thelist[:] = [d for d in thelist if d.get('id') != 2]" 
10000 loops, best of 3: 23.8 usec per loop 

totalmente el cuello y el cuello, incluso con sólo 3 elementos de la 99 a ser eliminado. Con las listas más largas y más repeticiones, esto es aún más claro:

$ python -mtimeit -s"lod=[{'id':i, 'name':'nam%s'%i} for i in range(33)]*133; import random" "thelist=list(lod); where=[i for i,d in enumerate(thelist) if d.get('id')==2]; where.reverse()" "for i in where: del thelist[i]" 
1000 loops, best of 3: 1.11 msec per loop 
$ python -mtimeit -s"lod=[{'id':i, 'name':'nam%s'%i} for i in range(33)]*133; import random" "thelist=list(lod); thelist[:] = [d for d in thelist if d.get('id') != 2]" 
1000 loops, best of 3: 998 usec per loop 

Con todo, no es evidente que debemos desplegar la sutileza de decisiones y la inversión de la lista de índices de remover, frente a la lista muy simple y obvia comprensión, para ganar posiblemente 100 nanosegundos en un caso pequeño y perder 113 microsegundos en uno más grande ;-). Evitar o criticar soluciones simples, sencillas y perfectamente adecuadas para el desempeño (como listas de comprensión para esta clase general de problemas de "eliminar algunos elementos de una lista") es un ejemplo particularmente desagradable de la conocida tesis de Knuth y Hoare de que "la optimización prematura es la raíz de todos los males en la programación "-)

+1

Dos razones por las que esto es malo: copia la lista completa, y recorre toda la lista incluso si el diccionario que contiene la identificación 2 es el primer elemento. – Imagist

+12

@imagist, es sin embargo el más rápido - MEDIR, por el amor de Dios, no solo ASUMIR sabes de lo que estás hablando, esp. cuando obviamente no lo haces ;-), * ESPECIALMENTE * cuando el elemento a eliminar es el primero (evita mover cualquier otro elemento). Y no hay ninguna indicación en la pregunta original de que cada dict en la lista DEBE tener siempre un valor diferente que corresponda a 'id'. –

+0

Hmmmm. No está mal. Hay dos enfoques: hacer una nueva lista con algunos elementos filtrados o modificar la lista existente para eliminar algunos elementos. Este es solo el primer enfoque. Y en la medida de lo posible, no hay nada que decir que un diccionario con id = 2 no aparecerá más de una vez en la lista. Es una lista; no hay garantía de exclusividad. Y el OP no sugirió esta limitación. – hughdbrown

0

puede intentar lo siguiente:!

a = [{'id': 1, 'name': 'paul'}, 
    {'id': 2, 'name': 'john'}] 

for e in range(len(a) - 1, -1, -1): 
    if a[e]['id'] == 2: 
     a.pop(e) 

Si no puede aparecer desde el principio - pop desde el extremo, no va a arruinar la de lazo.

+0

Quiere decir "rango (len (a) - 1, -1, -1)", no "rango (len (a) - 1, 0, -1)". Esto no incluye el primer elemento de la lista. Escuché que hoy en día se prefiere invertir(). Ver mi código a continuación. – hughdbrown

+0

Así es lo que estaba recibiendo en: >>> a = lista (rango (5)) >>> a [, 4 0, 1, 2, 3] >>> gama (len (a) - 1, -1, -1) [4, 3, 2, 1, 0] >>> rango (len (a) - 1, 0, -1) [4, 3, 2, 1] Solo espere a que se destruyan los comentarios ... – hughdbrown

4

Aquí está una manera de hacerlo con una lista por comprensión (suponiendo que el nombre 'foo' su lista):

[x for x in foo if not (2 == x.get('id'))] 

Sustituto 'john' == x.get('name') o lo que sea, según corresponda.

filter también funciona:

foo.filter(lambda x: x.get('id')!=2, foo)

Y si quieres un generador se puede utilizar itertools:

itertools.ifilter(lambda x: x.get('id')!=2, foo)

Sin embargo, a partir de Python 3, filter volverá un iterador de todos modos , por lo que la comprensión de la lista es realmente la mejor opción, como sugirió Alex.

+0

también, .get es mejor que [] aquí, ya que no se rompe si algún dict en la lista NO tiene una entrada para la clave 'id'. –

+0

Buena sugerencia - respuesta editada en consecuencia. –

0

Usted podría intentar algo a lo largo de las siguientes líneas:

def destructively_remove_if(predicate, list): 
     for k in xrange(len(list)): 
      if predicate(list[k]): 
       del list[k] 
       break 
     return list 

    list = [ 
     { 'id': 1, 'name': 'John' }, 
     { 'id': 2, 'name': 'Karl' }, 
     { 'id': 3, 'name': 'Desdemona' } 
    ] 

    print "Before:", list 
    destructively_remove_if(lambda p: p["id"] == 2, list) 
    print "After:", list 

A menos que se construye algo parecido a un índice sobre sus datos, que no creo que se puede hacer mejor que no hacer una fuerza bruta " tabla escanear "en toda la lista. Si sus datos están ordenados por la clave que está usando , es posible que pueda emplear el módulo bisect al encuentre el objeto que busca con mayor rapidez.

6

Esto no es propiamente correcto (ya que creo que ya tiene algunos bastante buenos), pero ... ¿ha considerado tener un diccionario de <id>:<name> en lugar de una lista de diccionarios?

+1

+1: "Si es difícil, lo estás haciendo mal". Si desea eliminar elementos mediante un atributo, use un diccionario, marcado por el atributo. Mucho más simple –

+1

... siempre y cuando no le importe en absoluto conservar el orden de los artículos, nunca quiera quitar cosas por un atributo diferente, esté feliz de no permitir ningún duplicado con respecto a ese atributo, etc., etc., demasiado muchas restricciones más allá de cualquier especificación expresada por el PO, para hacer que esta sugerencia sea razonable ;-). –

+0

Si hubiera tenido que dar todas esas especificaciones por sentado, hubiera dicho "use una base de datos" xD – fortran

2
# assume ls contains your list 
for i in range(len(ls)): 
    if ls[i]['id'] == 2: 
     del ls[i] 
     break 

será probablemente más rápido que los métodos de la lista de comprensión en promedio, ya que no atraviesa toda la lista si encuentra el elemento en cuestión desde el principio.

+0

levantará 'KeyError' si dict no tiene' id'. y eso no es lo que OP pidió. – SilentGhost

+0

@Imagist +1 Esto era exactamente lo que estaba buscando. Nota para @SilentGhost: Solo podría usar una clave diferente, que no sea 'id', si desea apuntar a otro valor, es decir:' si ls [i] ['name'] == 'john': 'coincidiría y eliminar ese diccionario – natureminded

Cuestiones relacionadas