Set Comprehensions & Dictionary Comprehensions

Set Comprehensions

So we’ve learned about list comprehensions and generator expressions. What if we want to use a comprehension but create a set?

We could do this:

>>> words = ["apple", "orange", "lime", "lemon"]
>>> first_letters = set(w[0] for w in words)
>>> first_letters
set(['o', 'a', 'l'])

Here we’ve used a generator expression and the set constructor to make a set.

But Python actually has a special syntax just for making set comprehensions:

>>> first_letters = {w[0] for w in words}
>>> first_letters
set(['o', 'a', 'l'])

We use curly braces when making a set comprehension because we use curly braces when making a set.

Remembering our code for printing reversible words from a dictionary, there’s one more improvement we can make with what we just learned. We can turn the reversed_words set into a set comprehension.

Here’s where we left off:

with open('dictionary.txt') as dictionary_file:
    words = (
        line.rstrip()
        for line in dictionary_file
    )
    words_over_five_letters = [
        word
        for word in words
        if len(word) > 5
    ]

# Store the reverse of all long words
reversed_words = set()
for word in words_over_five_letters:
    reversed_words.add(word[::-1])

reversible_words = [
    word
    for word in words_over_five_letters
    if word in reversed_words
]

for word in reversible_words:
    print(word)

We can take the for loop that adds to the reversed_words set and turn into a set comprehension:

reversed_words = {
    word[::-1]
    for word in words_over_five_letters
}

Here’s our final result:

with open('dictionary.txt') as dictionary_file:
    words = (
        line.rstrip()
        for line in dictionary_file
    )
    words_over_five_letters = [
        word
        for word in words
        if len(word) > 5
    ]

reversed_words = {
    word[::-1]
    for word in words_over_five_letters
}

reversible_words = [
    word
    for word in words_over_five_letters
    if word in reversed_words
]

for word in reversible_words:
    print(word)

Great! We’ve taken all but one for loop we started with and turned them into list comprehensions. Then we took two and turned them into space- and time-efficient generators.

Dict Comprehensions

What about making a dictionary from a generator expression?

Let’s say we have a dictionary where the values are numbers and we want to make a new dictionary which has only values with two digit numbers.

>>> favorite_numbers = {'rebecca': 293, 'ronald': 76, 'dorothy': 62, 'harold': 36, 'matt': 314}
>>> dict((k, v) for k, v in favorite_numbers.items() if v < 100)
{'ronald': 76, 'harold': 36, 'dorothy': 62}

Here we’re using a generator expression and a dict constructor to make a dictionary.

Python has a special syntax for creating dictionary comprehensions that we should use instead:

>>> {k: v for k, v in favorite_numbers.items() if v < 100}
{'ronald': 76, 'harold': 36, 'dorothy': 62}

Notice the k: v syntax which is very similar to the key-value syntax used for defining dictionaries.

Let’s create a dictionary where the keys are letters and the values are the indexes of those letters (where a is 1 and z is 26):

>>> from string import ascii_lowercase
>>> letters = {letter: n + 1 for n, letter in enumerate(ascii_lowercase)}
>>> letters['t']
20
>>> word = "Trey"
>>> encoded_word = [letters[x] for x in word.lower()]
>>> encoded_word
[20, 18, 5, 25]

More Comprehension Exercises

These exercises are all in the more.py file in the exercises directory. Edit the file to add the functions or fix the error(s) in the existing function(s). To run the test: from the exercises folder, type python test.py <function_name>, like this:

$ python test.py flip_dict

Flipped Dictionary

Edit the function flip_dict, that flips dictionary keys and values.

Example usage:

>>> from more import flip_dict
>>> flip_dict({'Python': "2015-09-15", 'Java': "2015-09-14", 'C': "2015-09-13"})
{'2015-09-13': 'C', '2015-09-15': 'Python', '2015-09-14': 'Java'}

ASCII Strings

Edit the function get_ascii_codes so that it accepts a list of strings and returns a dictionary containing the strings as keys and a list of corresponding ASCII character codes as values.

>>> from more import get_ascii_codes
>>> words = ["hello", "bye", "yes", "no", "python"]
>>> get_ascii_codes(words)
{'yes': [121, 101, 115], 'hello': [104, 101, 108, 108, 111], 'python': [112, 121, 116, 104, 111, 110], 'no': [110, 111], 'bye': [98, 121, 101]}

Double-valued Dictionary

Edit the function dict_from_truple so that it accepts a list of three-item tuples and returns a dictionary where the keys are the first item of each tuple and the values are a two-tuple of the remaining two items of each input tuple.

Example usage:

>>> from more import dict_from_truple
>>> dict_from_truple([(1, 2, 3), (4, 5, 6), (7, 8, 9)])
{1: (2, 3), 4: (5, 6), 7: (8, 9)}

Multi-valued Dictionary

Edit the function dict_from_tuple by starting with the code from your dict_from_truple function, above, and modify it to accept a list of tuples of any length and return a dictionary which uses the first item of each tuple as keys and all subsequent items as values.

Example usage:

>>> from more import dict_from_tuple
>>> dict_from_tuple([(1, 2, 3, 4), (5, 6, 7, 8)])
{1: (2, 3, 4), 5: (6, 7, 8)}
>>> dict_from_tuple([(1, 2, 3), (4, 5, 6), (7, 8, 9)])
{1: (2, 3), 4: (5, 6), 7: (8, 9)}

Factors

Edit the function get_all_factors so that it takes a set of numbers and makes a dictionary containing the numbers as keys and a list of factors as values.

>>> from more import get_all_factors
>>> get_all_factors({1, 2, 3, 4})
{1: [1], 2: [1, 2], 3: [1, 3], 4: [1, 2, 4]}
>>> get_all_factors({62, 293, 314})
{314: [1, 2, 157, 314], 293: [1, 293], 62: [1, 2, 31, 62]}

Hint

You can use this function to find the factors of any number:

def get_factors(number):
    """Get factors of the given number."""
    return [
        n
        for n in range(1, number + 1)
        if number % n == 0
    ]