Come filtrare un dizionario in base a una funzione di condizione arbitraria?


212

Ho un dizionario di punti, ad esempio:

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

Voglio creare un nuovo dizionario con tutti i punti il ​​cui valore xey è inferiore a 5, ovvero i punti 'a', 'b' e 'd'.

Secondo il libro , ogni dizionario ha la items()funzione, che restituisce un elenco di (key, pair) tuple:

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

Quindi ho scritto questo:

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

C'è un modo più elegante? Mi aspettavo che Python avesse una funzione super fantastica dictionary.filter(f)...


Risposte:


427

Al giorno d'oggi, in Python 2.7 e versioni successive, è possibile utilizzare una comprensione dict:

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

E in Python 3:

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

15
Upvote! Questo è più di due volte più veloce dell'approccio più generale di Martellis. Nota che puoi usare anche le viste (come gli iteitem, NON sono una copia degli elementi dict): {k: v per k, v in points.viewitems () se v [0] <5 e v [1] < 5}
dorvak,

5
Ed ecco una buona spiegazione del perché la funzione call dict () è più lenta della sintassi del costruttore / letterale {} doughellmann.com/2012/11/…
dorvak

1
Tieni presente che è iteritemsstato rimosso in Python 3. Ma puoi usare itemsinvece. Si comporta come iteritemsfunziona nelle versioni precedenti.
Elias Zamaria,

1
@Datanovice Sono sicuro che si potrebbe. Si potrebbe anche aprire una nuova domanda con dettagli sufficienti per ottenere una risposta più utile;)
Thomas,

1
Uno ha aperto una domanda con risposte limitate, quindi ha fatto ricorso alla lettura di quante più domande è possibile per ottenere una migliore comprensione. Una sega una più consapevole e quindi, ha continuato a scegliere quelli cervelli;) la mia D: stackoverflow.com/questions/50104127/...
Datanovice

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

Puoi scegliere di chiamare .iteritems()invece .items()che in Python 2 e pointspotresti avere molte voci.

all(x < 5 for x in v)può essere eccessivo se sai per certo che ogni punto sarà sempre solo 2D (in tal caso potresti esprimere lo stesso vincolo con un and) ma funzionerà bene ;-).


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

1
In Python 2 usa iteritems () invece di items ()
Regisz

2
In python 3.5, questo restituisce un errore: points_small = dict (filtro (lambda (a, (b, c)): b <5 ec <5, points.items ())) ^ Sintassi
Errore

Penso che non sia supportato in 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
grande ! vale la pena ricordare che si tratta di Py3, poiché la lambda non può più decomprimere l'argomento tupla (vedi PEP 3113 )
Ciprian Tomoiagă

Confronti le tuple lessicograficamente, il che non è quello richiesto dall'OP. Nel tuo caso, il punto (3, 10)supererà il test: (3, 10) < (5, 5)è vero, ma è sbagliato ( ydovrebbe essere inferiore anche a 5).
dmitry_romanov,

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

7

Penso che la risposta di Alex Martelli sia sicuramente il modo più elegante per farlo, ma volevo solo aggiungere un modo per soddisfare il tuo desiderio di un dictionary.filter(f)metodo super fantastico in un modo Pythonic:

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)

Fondamentalmente creiamo una classe che eredita da dict, ma aggiunge il metodo di filtro. Dobbiamo utilizzare .items()per il filtro, poiché l'utilizzo .iteritems()durante l'iterazione distruttiva genererà un'eccezione.


+1 Grazie, codice elegante. Penso davvero che dovrebbe far parte del dizionario standard.
Adam Matan,

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.