Bagaimana cara memfilter kamus sesuai dengan fungsi kondisi arbitrer?


212

Saya memiliki kamus poin, katakan:

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

Saya ingin membuat kamus baru dengan semua titik yang nilainya x dan y lebih kecil dari 5, yaitu titik 'a', 'b' dan 'd'.

Menurut buku itu , setiap kamus memiliki items()fungsi, yang mengembalikan daftar (key, pair) tuple:

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

Jadi saya telah menulis ini:

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

Apakah ada cara yang lebih elegan? Saya mengharapkan Python untuk memiliki beberapa dictionary.filter(f)fungsi yang luar biasa ...


Jawaban:


427

Saat ini, dalam Python 2.7 dan lebih tinggi, Anda dapat menggunakan pemahaman dict:

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

Dan dengan Python 3:

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

15
Suara positif! Ini lebih dari dua kali lebih cepat daripada Martellis pendekatan yang lebih umum. Perhatikan bahwa Anda juga dapat menggunakan tampilan (seperti item, itu BUKAN salinan item dict): {k: v untuk k, v dalam points.viewitems () jika v [0] <5 dan v [1] < 5}
dorvak

5
Dan di sini adalah penjelasan yang baik mengapa fungsi panggilan dict () lebih lambat daripada konstruktor / sintaks literal {} doughellmann.com/2012/11/…
dorvak

1
Perlu diingat bahwa iteritemsitu dihapus dalam Python 3. Tapi Anda bisa menggunakannya itemssebagai gantinya. Itu berperilaku cara iteritemsbekerja di versi yang lebih lama.
Elias Zamaria

1
@ Devovice saya yakin orang bisa. Orang juga dapat membuka pertanyaan baru dengan detail yang cukup untuk mendapatkan jawaban yang lebih berguna;)
Thomas

1
Seseorang telah membuka sebuah pertanyaan dengan tanggapan terbatas, sehingga ia terpaksa membaca sebanyak mungkin pertanyaan untuk memperoleh pemahaman yang lebih baik. Seseorang melihat yang lebih luas dan dengan demikian, terus memilih otaknya;) T saya: stackoverflow.com/questions/50104127/…
Datanovice

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

Anda dapat memilih untuk menelepon .iteritems()daripada .items()menggunakan Python 2 dan pointsmungkin memiliki banyak entri.

all(x < 5 for x in v)mungkin berlebihan jika Anda tahu pasti setiap titik akan selalu menjadi 2D saja (dalam hal ini Anda mungkin mengekspresikan kendala yang sama dengan a and) tetapi itu akan berfungsi dengan baik ;-).


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

1
Dalam Python 2 gunakan iteritems () bukan item ()
Regisz

2
Dalam python 3.5, ini mengembalikan kesalahan: points_small = dict (filter (lambda (a, (b, c)): b <5 dan c <5, points.items ())) ^ SyntaxError: sintaks tidak valid `
Mevin Babu

Saya pikir itu tidak didukung dalam 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
Bagus ! layak disebutkan bahwa ini adalah Py3, karena lambda tidak dapat lagi membongkar argumen tuple (lihat PEP 3113 )
Ciprian Tomoiagă

Anda membandingkan tupel secara leksikografis, yang bukan merupakan persyaratan OP. Dalam kasus Anda, titik (3, 10)akan lulus tes: (3, 10) < (5, 5)Benar, tetapi itu salah ( yharus lebih kecil dari 5 juga).
dmitry_romanov

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

7

Saya pikir jawaban Alex Martelli jelas merupakan cara paling elegan untuk melakukan ini, tetapi hanya ingin menambahkan cara untuk memuaskan keinginan Anda akan dictionary.filter(f)metode super luar biasa dengan cara 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)

Pada dasarnya kami membuat kelas yang mewarisi dari dict, tetapi menambahkan metode filter. Kita perlu menggunakan .items()untuk penyaringan, karena menggunakan .iteritems()iterasi sementara secara destruktif akan menimbulkan pengecualian.


+1 Terima kasih, kode elegan. Saya benar-benar berpikir itu harus menjadi bagian dari kamus standar.
Adam Matan

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.