List Comprehensions¶
Note
Be sure to activate your virtual environment in your command/terminal window before starting the Python REPL.
Basic Comprehensions¶
Let’s say we have a list of numbers and we want to double each number. With what we have learned so far, our code would look something like this:
>>> my_favorite_numbers = [1, 1, 2, 3, 5, 8, 13]
>>> doubled_numbers = []
>>> for n in my_favorite_numbers:
... doubled_numbers.append(n * 2)
...
>>> doubled_numbers
[2, 2, 4, 6, 10, 16, 26]
In Python there is a shorter syntax for this. We can write the code to create our doubled_numbers list in only one line:
>>> doubled_numbers = [n * 2 for n in my_favorite_numbers]
>>> doubled_numbers
[2, 2, 4, 6, 10, 16, 26]
This is called a list comprehension. List comprehensions provide convenient shorthand for creating lists from other lists or iterables. A simple list comprehension is handy when we want to initialize a list:
>>> [0 for i in range(10)]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
Let’s create a list of number tuples where the second item of the tuple is the square of the first:
>>> squares = [(x, x ** 2) for x in range(1, 11)]
>>> squares
[(1, 1), (2, 4), (3, 9), (4, 16), (5, 25), (6, 36), (7, 49), (8, 64), (9, 81), (10, 100)]
Now let’s combine iterations of two lists of strings to create a list of strings of every combination of the lists:
>>> colors = ["red", "green", "blue", "yellow"]
>>> things = ["ball", "car", "house"]
>>> combos = [" ".join([color, item]) for color in colors for item in things]
>>> combos
['red ball', 'red car', 'red house', 'green ball', 'green car', 'green house', 'blue ball', 'blue car', 'blue house', 'yellow ball', 'yellow car', 'yellow house']
We can call functions or methods within the list comprehension:
>>> caps = [color.upper() for color in colors]
>>> caps
['RED', 'GREEN', 'BLUE', 'YELLOW']
List comprehensions can perform their operations on any iterable. For example we could loop over a tuple of numbers and double each:
>>> numbers = (12, 4, 56, 5, -1, 38)
>>> doubles = [x * 2 for x in numbers]
>>> doubles
[24, 8, 112, 10, -2, 76]
>>> tuple(doubles)
(24, 8, 112, 10, -2, 76)
We could also loop over all items in a set:
>>> colors = {"purple", "aqua", "blue"}
>>> stretched_colors = set(["_".join(word) for x in colors])
>>> stretched_colors
{'b_l_u_e', 'a_q_u_a', 'p_u_r_p_l_e'}
Of course, when we use an unordered collection type for the iterable, there is no guarantee about the order of the items returned, but you will always get all the items.
We can nest list comprehensions to make more complicated lists. Let’s create a matrix, then create the transpose of the matrix:
>>> matrix = [[row * 3 + incr for incr in range(1, 4)] for row in range(4)]
>>> matrix
[[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]]
>>> transpose = [[row[i] for row in matrix] for i in range(3)]
>>> transpose
[[1, 4, 7, 10], [2, 5, 8, 11], [3, 6, 9, 12]]
In the real world, built-in functions are preferred, so, as we saw previously, we can transpose the matrix using zip:
>>> transpose = list(zip(*matrix))
>>> transpose
[(1, 4, 7, 10), (2, 5, 8, 11), (3, 6, 9, 12)]
But zip gives you immutable tuples for rows, instead of lists. What if you need to be able to modify the matrix?
You can use a list comprehension on the transposed matrix:
>>> transpose = [list(row) for row in transpose]
>>> transpose
[[1, 4, 7, 10], [2, 5, 8, 11], [3, 6, 9, 12]]
Or you can do it all in one command at the same time as the transpose:
>>> transpose = [list(row) for row in list(zip(*matrix))]
>>> transpose
[[1, 4, 7, 10], [2, 5, 8, 11], [3, 6, 9, 12]]
Conditional Filters¶
A powerful feature of list comprehensions is the ability to use a conditional if clause to add a filter to the iterable. Say we want a list of cubes of all perfect squares up through 100:
>>> [x ** 3 for x in range(101) if (x ** 0.5).is_integer()]
[0, 1, 64, 729, 4096, 15625, 46656, 117649, 262144, 531441, 1000000]
Let’s make a list comprehension that gets all numbers from a list that are greater than zero:
>>> nums = [4, -1, 7, 9, 34, 0, -4, 3]
>>> new_nums = [x for x in nums if x > 0]
>>> new_nums
[4, 7, 9, 34, 3]
We could also check which pets are too numerous, given a dictionary of pets and quantities:
>>> num_pets = {'cats' : 6, 'dogs' : 4, 'hamsters' : 7, 'birds' : 3}
>>> too_many = [pets for pets, num in num_pets.items() if num > 4]
>>> too_many
['cats', 'hamsters']
When do we use them?¶
List comprehensions are a tool. Like all tools, you need to be able to identify opportunities to use them.
You can use list comprehensions whenever you see a “for loop” that loops over an iterable, transforming each item and adding it to a list.
Take this function:
>>> def square_all(numbers):
... squared_numbers = []
... for n in numbers:
... squared_numbers.append(n * n)
... return squared_numbers
...
>>> square_all([1, 2, 3, 4])
[1, 4, 9, 16]
>>> square_all((0, 1, 2))
[0, 1, 4]
You can see there is a “for loop” making one list (or any other iterable) into a new list.
We can rewrite this as a list comprehension:
>>> def square_all(numbers):
... return [n * n for n in numbers]
...
>>> square_all([1, 2, 3, 4])
[1, 4, 9, 16]
>>> square_all((0, 1, 2))
[0, 1, 4]
We can also use list comprehensions whenever we see a “for loop” that also excludes some values, using an “if statement” to filter out values that don’t meet a condition.
Take this function:
>>> def only_truthy(things):
... truthy_things = []
... for x in things:
... if x:
... truthy_things.append(x)
... return truthy_things
...
>>> only_truthy([1, 0, 2, False, "hello", True, ""])
[1, 2, 'hello', True]
That “if statement” can be transformed into the condition statement in a list comprehension.
We can rewrite this as a list comprehension like this:
>>> def only_truthy(things):
... return [x for x in things if x]
...
>>> only_truthy([1, 0, 2, False, "hello", True, ""])
[1, 2, 'hello', True]
That looks a little strange with all those “x” variables in there. We have “x for x” because we’re not actually modifying each item in this list. We’re just filtering them out.
We can also modify the values at the same time:
>>> nums = [4, -1, 7, 9, 34, 0, -4, 3]
>>> new_nums = [x * 3 for x in nums if x > 0]
>>> new_nums
[12, 21, 27, 102, 9]
Comprehension Exercises¶
Tip
You should use at least one list comprehension in each of these exercises!
Starting with a vowel¶
Make a function that accepts a list of names and returns a new list containing all names that start with a vowel. It should work like this:
>>> names = ["Alice", "Bob", "Christy", "Jules"]
>>> vowel_names(names)
['Alice']
>>> names = ["Scott", "Arthur", "Jan", "Elizabeth"]
>>> vowel_names(names)
['Arthur', 'Elizabeth']
Power List By Index¶
Make a function, power_list, that accepts a list of numbers and returns a new list that contains each number raised to the i-th power where i is the index of that number in the given list. For example:
>>> power_list([3, 2, 5])
[1, 2, 25]
>>> numbers = [78, 700, 82, 16, 2, 3, 9.5]
>>> power_list(numbers)
[1, 700, 6724, 4096, 16, 243, 735091.890625]
Divisible¶
Make a list of all numbers between 100 and 300 that are divisible by both 6 and 10.
Flatten a Matrix¶
Create a function flatten, that will take a matrix (a list of lists) and return a flattened version of the matrix.
>>> matrix = [[row * 3 + incr for incr in range(1, 4)] for row in range(4)]
>>> matrix
[[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]]
>>> flatten(matrix)
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
Secret Message¶
- Create a string containing the
declaration of independence - Get a list of all uppercase letters in the declaration of independence
- Find a secret 4 letter word in this uppercase letter list. The word starts at index 374 ends at index 382, and skips every other letter