Làm thế nào để lọc một từ điển theo một hàm điều kiện tùy ý?


212

Tôi có một từ điển các điểm, nói:

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

Tôi muốn tạo một từ điển mới với tất cả các điểm có giá trị x và y nhỏ hơn 5, tức là các điểm 'a', 'b' và 'd'.

Theo cuốn sách , mỗi từ điển có items()chức năng, trả về một danh sách các (key, pair) tuple:

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

Vì vậy, tôi đã viết này:

>>> 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ó cách nào thanh lịch hơn? Tôi đã mong đợi Python có một số dictionary.filter(f)chức năng siêu tuyệt vời ...


Câu trả lời:


427

Ngày nay, trong Python 2.7 trở lên, bạn có thể sử dụng cách hiểu chính tả:

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

Và trong Python 3:

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

15
Upvote! Điều này nhanh hơn gấp hai lần so với phương pháp tổng quát hơn của Martellis. Lưu ý rằng bạn cũng có thể sử dụng các chế độ xem (như iteitems, chúng KHÔNG phải là bản sao của các mục chính tả): {k: v cho k, v in points.viewitems () nếu v [0] <5 và v [1] < 5}
dorvak

5
Và đây là một lời giải thích tốt tại sao hàm gọi dict () chậm hơn hàm tạo / cú pháp bằng chữ {} doughellmann.com/2012/11/ Kẻ
dorvak

1
Hãy nhớ rằng iteritemsđã bị xóa trong Python 3. Nhưng itemsthay vào đó bạn có thể sử dụng . Nó hành xử theo cách iteritemslàm việc trong các phiên bản cũ hơn.
Elias Zamaria

1
@Datanovice Tôi chắc chắn một người có thể. Người ta cũng có thể mở một câu hỏi mới với đủ chi tiết để có câu trả lời hữu ích hơn;)
Thomas

1
Người ta đã mở một câu hỏi với những câu trả lời hạn chế, do đó người ta đã phải đọc càng nhiều câu hỏi càng tốt để hiểu rõ hơn. Người ta đã thấy một người hiểu biết hơn và do đó, tiếp tục chọn những bộ não;) Q: stackoverflow.com/questions/50104127/
Datanovice

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

Bạn có thể chọn gọi .iteritems()thay vì .items()nếu bạn ở trong Python 2 và pointscó thể có nhiều mục.

all(x < 5 for x in v)có thể là quá mức nếu bạn biết chắc chắn mỗi điểm sẽ luôn chỉ là 2D (trong trường hợp đó bạn có thể biểu thị cùng một ràng buộc với một and) nhưng nó sẽ hoạt động tốt ;-).


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

1
Trong Python 2, sử dụng iteritems () thay vì các mặt hàng ()
Regisz

2
Trong python 3.5, điều này trả về một lỗi: points_small = dict (bộ lọc (lambda (a, (b, c)): b <5 và c <5, points.items ())) ^ SyntaxError: cú pháp không hợp lệ `
Mevin Babu

Tôi nghĩ rằng nó không được hỗ trợ trong 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
tuyệt quá ! đáng nói rằng đây là Py3, vì lambda không còn có thể giải nén đối số tuple (xem PEP 3113 )
Ciprian Tomoiagă

Bạn so sánh các bộ dữ liệu từ vựng, đó không phải là những gì OP yêu cầu. Trong trường hợp của bạn, điểm (3, 10)sẽ vượt qua bài kiểm tra: (3, 10) < (5, 5)là Đúng, nhưng nó sai ( ycũng nên nhỏ hơn 5).
dmitry_romanov

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

7

Tôi nghĩ rằng câu trả lời của Alex Martelli chắc chắn là cách thanh lịch nhất để làm điều này, nhưng chỉ muốn thêm một cách để thỏa mãn mong muốn của bạn về một dictionary.filter(f)phương pháp siêu tuyệt vời theo cách của 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)

Về cơ bản chúng ta tạo một lớp kế thừa từ dict, nhưng thêm phương thức lọc. Chúng ta cần phải sử dụng .items()cho bộ lọc, vì sử dụng .iteritems()trong khi lặp đi lặp lại một cách triệt để sẽ tạo ra ngoại lệ.


+1 Cảm ơn, mã thanh lịch. Tôi thực sự nghĩ rằng nó nên là một phần của từ điển tiêu chuẩn.
Adam Matan

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.