How to filter a dictionary according to an arbitrary condition function?
Asked Answered
K

7

305

I have a dictionary of points, say:

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

I want to create a new dictionary with all the points whose x and y value is smaller than 5, i.e. points 'a', 'b' and 'd'.

According to the the book, each dictionary has the items() function, which returns a list of (key, pair) tuple:

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

So I have written this:

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

Is there a more elegant way? I was expecting Python to have some super-awesome dictionary.filter(f) function...

Karyosome answered 16/5, 2010 at 16:30 Comment(1)
#3420622Ballroom
D
569

You can use a dict comprehension:

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

And in Python 2, starting from 2.7:

{k: v for k, v in points.iteritems() if v[0] < 5 and v[1] < 5}
Disposed answered 16/5, 2013 at 13:57 Comment(7)
Upvote! This is more than two times faster than Martellis more general approach. Note that you can use views as well (like iteitems, they are NOT a copy of the dict items): {k: v for k, v in points.viewitems() if v[0] < 5 and v[1] < 5}Unknit
And here is a good explanation why the function call dict() is slower than the constructor/literal syntax {} doughellmann.com/2012/11/…Unknit
Keep in mind that iteritems was removed in Python 3. But you can use items instead. It behaves the way iteritems works in older versions.Collings
Hey buddy, I know this is old, but could one use dict comprehension to run partial string matches in 'column x' against dict keys?Carboxylate
@Datanovice I'm sure one could. One could also open a new question with sufficient detail to get a more useful answer ;)Disposed
One has opened a question with limited responses, thus one has resorted to reading as many questions as one can to gain a better understanding. One saw a more knowledgeable one and thus, continued to pick ones brains ;) my Q : #50104627Carboxylate
As a side note, if someone wanted to determine what was == to, it would look like this for the example: {k: v for k, v in points.items() if v[0] == 5}Phratry
A
120
dict((k, v) for k, v in points.items() if all(x < 5 for x in v))

You could choose to call .iteritems() instead of .items() if you're in Python 2 and points may have a lot of entries.

all(x < 5 for x in v) may be overkill if you know for sure each point will always be 2D only (in that case you might express the same constraint with an and) but it will work fine;-).

Asbury answered 16/5, 2010 at 16:37 Comment(0)
G
27
points_small = dict(filter(lambda (a,(b,c)): b<5 and c < 5, points.items()))
Gant answered 16/5, 2010 at 16:35 Comment(3)
In Python 2 use iteritems() instead of items()Globeflower
In python 3.5, this returns an error: points_small = dict(filter(lambda (a,(b,c)): b<5 and c < 5, points.items())) ^ SyntaxError: invalid syntax `Chromo
I think it's not supported in python 3Photoelectric
H
25
>>> 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)}
Hudgens answered 23/2, 2015 at 10:49 Comment(2)
great ! worth mentioning that this is Py3, as the lambda can no longer unpack the tuple argument (see PEP 3113)Gratianna
You compare tuples lexicographically, which is not what OP required. In your case, point (3, 10) will pass the test: (3, 10) < (5, 5) is True, but it's wrong (y should be smaller than 5 as well).Roble
B
10
dict((k, v) for (k, v) in points.iteritems() if v[0] < 5 and v[1] < 5)
Bethink answered 16/5, 2010 at 16:35 Comment(0)
I
9

I think that Alex Martelli's answer is definitely the most elegant way to do this, but just wanted to add a way to satisfy your want for a super awesome dictionary.filter(f) method in a Pythonic sort of way:

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)

Basically we create a class that inherits from dict, but adds the filter method. We do need to use .items() for the the filtering, since using .iteritems() while destructively iterating will raise exception.

Incisive answered 16/5, 2013 at 14:48 Comment(1)
+1 Thanks, elegant code. I really think it should be a part of the standard dictionary.Karyosome
E
6
dict((k, v) for (k, v) in points.iteritems() if v[0] < 5 and v[1] < 5)
Erechtheum answered 16/5, 2010 at 16:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.