วิธีกรองพจนานุกรมตามฟังก์ชั่นเงื่อนไขตามอำเภอใจ?


212

ฉันมีพจนานุกรมของจุดพูดว่า:

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

ฉันต้องการสร้างพจนานุกรมใหม่ด้วยคะแนนทั้งหมดที่ค่า x และ y น้อยกว่า 5 นั่นคือคะแนน 'a', 'b' และ 'd'

ตามพจนานุกรมหนังสือแต่ละเล่มมีitems()ฟังก์ชั่นซึ่งจะคืนค่ารายการของ(key, pair) tuple:

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

ดังนั้นฉันจึงเขียนสิ่งนี้:

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

มีวิธีที่สง่างามกว่านี้ไหม? ฉันคาดหวังว่า Python จะมีdictionary.filter(f)ฟังก์ชั่นสุดยอดเยี่ยม...


คำตอบ:


427

ทุกวันนี้ใน Python 2.7 ขึ้นไปคุณสามารถใช้ dict comprehension

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

และใน Python 3:

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

15
Upvote! นี่เร็วกว่าวิธีทั่วไปมากกว่าสองเท่าของ Martellis โปรดทราบว่าคุณสามารถใช้มุมมองได้เช่นกัน (เช่น iteitems ไม่ใช่สำเนาของ dict): {k: v สำหรับ k, v ใน points.viewitems () ถ้า v [0] <5 และ v [1] < 5}
dorvak

5
และนี่คือคำอธิบายที่ดีว่าทำไม Dict ฟังก์ชั่นการโทร () จะช้ากว่าคอนสตรัค / อักษรไวยากรณ์ {} doughellmann.com/2012/11/...
dorvak

1
โปรดจำไว้ว่าiteritemsถูกลบใน Python 3 แต่คุณสามารถใช้itemsแทนได้ มันทำงานในลักษณะที่iteritemsทำงานในรุ่นที่เก่ากว่า
อีเลียสซามาเรี

1
@Datanovice ฉันแน่ใจว่าจะทำได้ เราสามารถเปิดคำถามใหม่พร้อมรายละเอียดที่เพียงพอเพื่อให้ได้คำตอบที่มีประโยชน์มากขึ้น)
Thomas

1
หนึ่งได้เปิดคำถามที่มีการตอบสนอง จำกัด ดังนั้นหนึ่งจึงหันไปอ่านคำถามให้มากที่สุดเท่าที่จะทำได้เพื่อทำความเข้าใจที่ดีขึ้น หนึ่งเห็นหนึ่งที่มีความรู้มากขึ้นและดังนั้นจึงยังคงเลือกสมอง;) คำถามของฉัน: stackoverflow.com/questions/50104127/ …
Datanovice

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

คุณสามารถเลือกที่จะโทร.iteritems()แทนที่จะเป็น.items()ถ้าคุณอยู่ใน Python 2 และpointsอาจมีหลายรายการ

all(x < 5 for x in v)อาจ overkill ถ้าคุณรู้แน่นอนว่าแต่ละจุดจะเป็นแบบ 2 มิติเท่านั้น (ในกรณีนั้นคุณอาจแสดงข้อ จำกัด เดียวกันกับand) แต่มันจะทำงานได้ดี ;-)


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

1
ใน Python 2 ให้ใช้ iteritems () แทน item ()
Regisz

2
ใน python 3.5 สิ่งนี้จะส่งคืนข้อผิดพลาด: points_small = dict (ตัวกรอง (lambda (a, (b, c))): b <5 และ c <5, points.items ())) ^ SyntaxError: ไวยากรณ์ที่ไม่ถูกต้อง `
Mevin Babu

ฉันคิดว่ามันไม่รองรับ 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
ดีมาก! ควรค่าแก่การกล่าวถึงว่านี่คือ Py3 เนื่องจากแลมบ์ดาไม่สามารถแกะอาร์กิวเมนต์ tuple ได้อีกต่อไป (ดูPEP 3113 )
Ciprian Tomoiagă

คุณเปรียบเทียบสิ่งอันดับพจนานุกรมซึ่งไม่ใช่สิ่งที่ OP ต้องการ ในกรณีของคุณคะแนน(3, 10)จะผ่านการทดสอบ: (3, 10) < (5, 5)เป็นจริง แต่มันผิด ( yควรน้อยกว่า 5 เช่นกัน)
dmitry_romanov


7

ฉันคิดว่าคำตอบของ Alex Martelli เป็นวิธีที่ยอดเยี่ยมที่สุดในการทำสิ่งนี้ แต่เพียงต้องการเพิ่มวิธีเพื่อตอบสนองความต้องการของคุณสำหรับdictionary.filter(f)วิธีการที่ยอดเยี่ยมในแบบของ 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)

โดยพื้นฐานแล้วเราสร้างคลาสที่สืบทอดมาdictแต่เพิ่มวิธีการกรอง เราจำเป็นต้องใช้.items()สำหรับการกรองเนื่องจากการใช้.iteritems()ในขณะที่วนซ้ำแบบทำลายล้างจะทำให้เกิดข้อยกเว้น


+1 ขอบคุณรหัสที่สง่างาม ฉันคิดว่ามันควรจะเป็นส่วนหนึ่งของพจนานุกรมมาตรฐาน
Adam Matan

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.