5.3. Dictionary and Set Comprehensions¶
We have seen how list comprehensions can used to describe and construct lists with an expression. Python has similar syntax for constructing dictionaries and sets using comprehensions.
5.3.1. The Dictionary Comprehension¶
The dictionary comprehension uses the following syntax.
dict_comp = {KEY:VAL for VAR in SEQ if COND}
Notice that the dictionary comprehension is similar to a list
comprehension in that it has a for
sequence expression and an optional
if
filter. This comprehension is delimited by braces ({}
) and
distinguished by the KEY:VAL
key-value pair in the operation expression.
For example, we could construct a dictionary that maps integers to their square root as follows.
In [1]: sqrts = {n:n**0.5 for n in range(5)}
In [2]: sqrts
Out[2]: {0: 0.0, 1: 1.0, 2: 1.4142135623730951, 3: 1.7320508075688772, 4: 2.0}
This approach is useful for storing the output for expensive operations, allowing the values to be looked up quickly without recomputing values.
Note
We expand on this approach to saving expensive operations when discussing
memiozation
.
5.3.2. The Set Comprehension¶
The set comprehension looks very similar to a dictionary comprehension, but is distinguished by only having a value in the operation expression.
set_comp = {VAL for VAR in SEQ if COND}
This comprehension is useful for creating a set of unique values from some sequence of values.
Check your understanding
In [3]: from random import randint
In [4]: die_rolls = lambda n: [randint(1,6) for i in range(n)]
In [5]: unique_rolls = {roll for roll in die_rolls(5)}
In [6]: unique_rolls
Out[6]: {1, 3, 4, 5}
- (A) Set comprehension
- A set comprehension needs one value in the operation expression, not a key-value pair.
- (B) List comprehension
- A list comprehension is delimited by brackets ('[',']') not braces ('{', '}').
- (C) Dictionary comprehension
- The dictionary comprehension can be identified by the surrounding braces and the key-value pair in the operation expression.
Q-1: Which type of comprehension is given below?
{w:i for i, w in enumerate(words)}
- (A) Set comprehension
- The set comprehension can be identified by the surrounding braces and single expression in the operation expression.
- (B) List comprehension
- The dictionary comprehension needs a key-value pair in the operation expression.
- (C) Dictionary comprehension
- A list comprehension is delimited by brackets ('[',']') not braces ('{', '}').
Q-2: Which type of comprehension is given below?
{w for i, w in enumerate(words)}
- (A) 5
- The word "fear" will only be counted once and all words containing a 't' will be excluded.
- (B) 6
- The word "fear" will only be counted once. A value is either in a set or not in a set, there is no repetition of values.
- (C) 9
- All words containing a 't' will be excluded.
Q-3: What value will the following code return?
fear = "the only thing we have to fear is fear itself"
len({w for w in fear.split() if 't' not in w})
- (A) 4
- This dictionary maps each word to twice the length of the word.
- (B) 8
- This dictionary maps each word to twice the length of the word.
- (C) Error: You can't have an expression in the value position of the key-value pair.
- Any expression can be used in the value position. Expressions can also be used in the key position, but they must evaluate to an immutable hashable object.
Q-4: What value will the following code return?
fear = "the only thing we have to fear is fear itself"
d = {w:2*len(w) for w in fear.split()}
get('fear', d)
5.3.3. Merging Dictionaries¶
One application of set and dictionary comprehensions is the process of merging
two dictionaries. Let’s create a function that called merge
that takes two
dictionaries d1
and d2
as arguments and returns a new dictionary that
merges the values from both. As it is possible that both dictionaries contain
the same keys, we will use the following rules to deal with these key
collisions.
- Use the key-value pair from
d2
, if present. - Use the key-value pair from
d1
for allkey not in d2
.
To make this process easier, we construct a helper function for selecting the value from the correct dictionary.
In [7]: from toolz import get
In [8]: get_value = lambda key, d1, d2: get(key, d2) if key in d2 else get(key, d1)
In [9]: d1, d2 = {'a':1, 'b':2}, {'a':10, 'c':12}
In [10]: assert get_value('a', d1, d2) == get('a', d2)
In [11]: assert get_value('b', d1, d2) == get('b', d1)
Note that we use two assert
statements to determine if our helper function
is pulling values from the correct dictionary. The fact that nothing happened
when we ran these lines indicates that the assertions held.
Note
An assert statement will do nothing when the condition is true, but will throw an exception if it fails. This is a useful technique for testing functions, which will be discussed on more detail in a future chapter.
The second helper function will take the two dictionaries as arguments and return a set of the combined keys.
In [12]: all_keys = lambda d1, d2: set(d1.keys()) | set(d2.keys())
In [13]: assert all_keys(d1, d2) == set(['a', 'b', 'c'])
Now we can used a dictionary comprehension to construct the merged dictionaries.
In [14]: my_merge = lambda d1, d2: {key:get_value(key, d1, d2) for key in all_keys(d1, d2)}
In [15]: my_merge(d1, d2)
Out[15]: {'a': 10, 'b': 2, 'c': 12}
In a pattern that should have become familiar, the toolz
package already
provides and implementation of merge
that behaves in the same way.
In [16]: from toolz import merge
In [17]: merge(d1, d2)
Out[17]: {'a': 10, 'b': 2, 'c': 12}
5.3.4. The levels of abstraction for a dictionary¶
The reader should take a moment to consider the levels of abstraction for a dictionary.
The levels of abstraction for a dictionary
A dictionary consists of keys and values.
Our rule for writing clean code is to write functions that only work at one level of abstraction. If we are going to adhere to this rule when working with dictionaries, we should write functions for working with the keys and values, and then apply these functions to the dictionary or dictionaries.
Writing clean code for dictionaries
When processing dictionaries, do the following to ensure clean code:
- Write a function or functions to process keys.
- Write a function or functions to process the values.
- Write a function that uses a comprehension and the functions from 1 and 2 to process the dictionary.
The last example illustrated this pattern by first constructing functions that worked on keys (made the collection of all keys) and values (pulled the value from the correct dictionary). These functions were then applied to the dictionaries using a comprehension.
Note
We will develop a generalization of merge
called merge_with
when
developing higher-order functions for dictionaries.