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}

    Q-1: Which type of comprehension is given below?

    {w:i for i, w in enumerate(words)}
    
  • (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-2: Which type of comprehension is given below?

    {w 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-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) 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-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)
    
  • (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.

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.

  1. Use the key-value pair from d2, if present.
  2. Use the key-value pair from d1 for all key 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.

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:

  1. Write a function or functions to process keys.
  2. Write a function or functions to process the values.
  3. 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.

my_merge example of level of abstraction for a dictionary
In the my_merge example, two functions were written to process the keys and values and then these functions were used in a comprehension to merge the dictionaries.

Note

We will develop a generalization of merge called merge_with when developing higher-order functions for dictionaries.

Next Section - 5.4. Computational Complexity