Jak filtrować słownik według dowolnej funkcji warunku?


212

Mam słownik punktów, powiedz:

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

Chcę utworzyć nowy słownik ze wszystkimi punktami, których wartość xiy jest mniejsza niż 5, tj. Punkty „a”, „b” i „d”.

Zgodnie z książką , każdy słownik ma items()funkcję, która zwraca listę (key, pair) krotek:

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

Więc napisałem to:

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

Czy istnieje bardziej elegancki sposób? Spodziewałem się, że Python będzie miał niesamowitą dictionary.filter(f)funkcję ...


Odpowiedzi:


427

Obecnie w Pythonie 2.7 i nowszych można używać rozumienia dykt:

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

A w Python 3:

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

15
Głosuj! Jest to ponad dwa razy szybsze niż bardziej ogólne podejście Martellisa. Pamiętaj, że możesz również używać widoków (podobnie jak iteitems, NIE są one kopią elementów dict): {k: v dla k, v w points.viewitems () jeśli v [0] <5 i v [1] < 5}
dorvak,

5
A oto dobre wyjaśnienie, dlaczego wywołanie funkcji dict () jest wolniejsze niż składnia konstruktora / dosłowności {} doughellmann.com/2012/11/…
dorvak

1
Pamiętaj, że iteritemszostał usunięty w Pythonie 3. Ale możesz użyć itemszamiast tego. Zachowuje się tak, jak iteritemsdziała w starszych wersjach.
Elias Zamaria 27.04.16

1
@Datanovice Jestem pewien, że można. Można też otworzyć nowe pytanie z wystarczającymi szczegółami, aby uzyskać bardziej przydatną odpowiedź;)
Thomas

1
Otworzono pytanie z ograniczonymi odpowiedziami, dlatego uciekł się do przeczytania jak największej liczby pytań, aby uzyskać lepsze zrozumienie. Widziałem bardziej kompetentnego i dlatego nadal
wybieraliśmy sobie

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

Możesz zadzwonić .iteritems()zamiast, .items()jeśli jesteś w Pythonie 2 i pointsmoże mieć wiele wpisów.

all(x < 5 for x in v)może być przesada, jeśli na pewno każdy punkt będzie zawsze tylko 2D (w takim przypadku możesz wyrazić to samo ograniczenie za pomocą an and), ale będzie działał dobrze ;-).


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

1
W Pythonie 2 użyj iteritems () zamiast items ()
Regisz

2
W Pythonie 3.5 zwraca błąd: points_small = dict (filter (lambda (a, (b, c)): b <5 and c <5, points.items ())) ^
Błąd

Myślę, że nie jest obsługiwany w Pythonie 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
świetny ! warto wspomnieć, że jest to Py3, ponieważ lambda nie może już rozpakować argumentu krotki (patrz PEP 3113 )
Ciprian Tomoiagă

Porównywasz krotki leksykograficznie, co nie jest tym, czego wymaga OP. W twoim przypadku punkt (3, 10)przejdzie test: (3, 10) < (5, 5)jest Prawdą, ale jest błędny ( ypowinien być również mniejszy niż 5).
dmitry_romanov

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

7

Myślę, że odpowiedź Alexa Martellego jest zdecydowanie najbardziej eleganckim sposobem na zrobienie tego, ale po prostu chciałem dodać sposób, aby zaspokoić twoje pragnienie super niesamowitej dictionary.filter(f)metody w pythoniczny sposób:

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)

Zasadniczo tworzymy klasę, która dziedziczy po dict, ale dodaje metodę filter. Musimy użyć .items()do filtrowania, ponieważ użycie .iteritems()podczas destrukcyjnego iteracji spowoduje wyjątek.


+1 Dzięki, elegancki kod. Naprawdę uważam, że powinien on być częścią standardowego słownika.
Adam Matan

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.