¿Cómo filtrar un diccionario de acuerdo con una función de condición arbitraria?


212

Tengo un diccionario de puntos, digamos:

>>> points={'a':(3,4), 'b':(1,2), 'c':(5,5), 'd':(3,3)}

Quiero crear un nuevo diccionario con todos los puntos cuyo valor x e y sea menor que 5, es decir, puntos 'a', 'b' y 'd'.

Según el libro , cada diccionario tiene la items()función, que devuelve una lista de (key, pair) tuplas:

>>> points.items()
[('a', (3, 4)), ('c', (5, 5)), ('b', (1, 2)), ('d', (3, 3))]

Entonces he escrito esto:

>>> for item in [i for i in points.items() if i[1][0]<5 and i[1][1]<5]:
...     points_small[item[0]]=item[1]
...
>>> points_small
{'a': (3, 4), 'b': (1, 2), 'd': (3, 3)}

¿Hay alguna forma más elegante? Esperaba que Python tuviera una función súper increíble dictionary.filter(f)...


Respuestas:


427

Hoy en día, en Python 2.7 y versiones posteriores, puede usar una comprensión dict:

{k: v for k, v in points.iteritems() if v[0] < 5 and v[1] < 5}

Y en Python 3:

{k: v for k, v in points.items() if v[0] < 5 and v[1] < 5}

15
¡Votación a favor! Esto es más de dos veces más rápido que el enfoque más general de Martellis. Tenga en cuenta que también puede usar vistas (como iteitems, NO son una copia de los elementos dict): {k: v para k, v en puntos.viewitems () si v [0] <5 y v [1] < 5}
dorvak

55
Y aquí hay una buena explicación de por qué la función call dict () es más lenta que la sintaxis del constructor / literal {} doughellmann.com/2012/11/…
dorvak

1
Tenga en cuenta que iteritemsse eliminó en Python 3. Pero puede usar itemsen su lugar. Se comporta como iteritemsfunciona en versiones anteriores.
Elias Zamaria

1
@Datanovice Estoy seguro de que uno podría. También se podría abrir una nueva pregunta con suficiente detalle para obtener una respuesta más útil;)
Thomas

1
Uno ha abierto una pregunta con respuestas limitadas, por lo tanto, ha recurrido a leer tantas preguntas como sea posible para obtener una mejor comprensión. Uno vio uno más conocedor y, por lo tanto, continuó recogiendo los cerebros;) mi Q: stackoverflow.com/questions/50104127/…
Datanovice

110
dict((k, v) for k, v in points.items() if all(x < 5 for x in v))

Puede elegir llamar en .iteritems()lugar de .items()si está en Python 2 y pointspuede tener muchas entradas.

all(x < 5 for x in v)puede ser excesivo si sabe con certeza que cada punto siempre será 2D solamente (en ese caso, puede expresar la misma restricción con un and) pero funcionará bien ;-).


21
points_small = dict(filter(lambda (a,(b,c)): b<5 and c < 5, points.items()))

1
En Python 2 use iteritems () en lugar de items ()
Regisz

2
En python 3.5, esto devuelve un error: points_small = dict (filter (lambda (a, (b, c)): b <5 y c <5, points.items ())) ^ SyntaxError: sintaxis no válida `
Mevin Babu

Creo que no es compatible con Python 3
matanster

15
>>> points = {'a': (3, 4), 'c': (5, 5), 'b': (1, 2), 'd': (3, 3)}
>>> dict(filter(lambda x: (x[1][0], x[1][1]) < (5, 5), points.items()))

{'a': (3, 4), 'b': (1, 2), 'd': (3, 3)}

3
estupendo ! Vale la pena mencionar que este es Py3, ya que la lambda ya no puede desempaquetar el argumento de la tupla (ver PEP 3113 )
Ciprian Tomoiagă

Compara las tuplas lexicográficamente, que no es lo que requiere OP. En su caso, el punto (3, 10)pasará la prueba: (3, 10) < (5, 5)es verdadero, pero está mal (también ydebería ser menor que 5).
dmitry_romanov

9
dict((k, v) for (k, v) in points.iteritems() if v[0] < 5 and v[1] < 5)

7

Creo que la respuesta de Alex Martelli es definitivamente la forma más elegante de hacer esto, pero solo quería agregar una forma de satisfacer su deseo de un dictionary.filter(f)método súper increíble de una manera pitónica:

class FilterDict(dict):
    def __init__(self, input_dict):
        for key, value in input_dict.iteritems():
            self[key] = value
    def filter(self, criteria):
        for key, value in self.items():
            if (criteria(value)):
                self.pop(key)

my_dict = FilterDict( {'a':(3,4), 'b':(1,2), 'c':(5,5), 'd':(3,3)} )
my_dict.filter(lambda x: x[0] < 5 and x[1] < 5)

Básicamente creamos una clase que hereda de dict, pero agrega el método de filtro. Necesitamos usar .items()para el filtrado, ya que usarlo .iteritems()mientras itera destructivamente generará una excepción.


+1 Gracias, código elegante. Realmente creo que debería ser parte del diccionario estándar.
Adam Matan

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.