320

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...

1

7 Answers 7

584

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}
Sign up to request clarification or add additional context in comments.

4 Comments

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}
And here is a good explanation why the function call dict() is slower than the constructor/literal syntax {} doughellmann.com/2012/11/…
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.
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}
121
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;-).

Comments

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

3 Comments

In Python 2 use iteritems() instead of items()
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 `
I think it's not supported in python 3
26
>>> 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)}

2 Comments

great ! worth mentioning that this is Py3, as the lambda can no longer unpack the tuple argument (see PEP 3113)
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).
10
dict((k, v) for (k, v) in points.iteritems() if v[0] < 5 and v[1] < 5)

Comments

10

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.

Comments

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

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.