List Comprehensions

Loops Review

If we have a list:

>>> names = ['Nellie', 'Ronald', 'Judith', 'Lavonda']

How could we print out each of these names?

>>> for i in range(len(names)):
...     print(names[i])
...

Don’t do this. There’s no need to get the indexes for names. Python doesn’t have for loops the same way C does... we have for-each loops which call for-in loops or just for loops.

>>> for name in names:
...     print(name)
...

How did Python know that name was the singular version of names?

Because we told it! We can name that variable anything we want:

>>> for x in names:
...     print(x)
...

What if we really needed the indexes for looping... say we want to loop over two lists at once:

>>> colors = ["red", "green", "blue", "purple"]
>>> ratios = [0.2, 0.3, 0.1, 0.4]
>>> for i, color in enumerate(colors):
...     print(f"{ratios[i] * 100}% {color}")

We could use enumerate to get the index.

Do we really need the index though? We’re only using it to look up the corresponding item in our second.

Is there a better way to do this?

>>> colors = ["red", "green", "blue", "purple"]
>>> ratios = [0.2, 0.3, 0.1, 0.4]
>>> for color, ratio in zip(colors, ratios):
...     print(f"{ratio * 100}% {color}")

Yes: use zip! The zip function is meant for looping over multiple things at the same time.

How do you loop over a dictionary in Python?

>>> animals = {'birds': 3, 'cats': 2, 'dogs': 1}
>>> for animal in animals:
...     print(f"I have {animals[animal]} {animal}")
...

Is there a better way?

>>> animals = {'birds': 3, 'cats': 2, 'dogs': 1}
>>> for item in animals.items():
...     print(f"I have {item[1]} {item[0]}")
...

Is that good enough?

>>> animals = {'birds': 3, 'cats': 2, 'dogs': 1}
>>> for animal, count in animals.items():
...     print(f"I have {count} {animal}")
...

That’s called tuple unpacking, iterable unpacking, or multiple assignment and it’s one of the overlooked features in Python.

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.

We can put any expression that makes a new object inside of the first part of a comprehension.

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)]

Let’s call a string methods within a comprehension:

>>> caps = [color.upper() for color in colors]
>>> caps
['RED', 'GREEN', 'BLUE', 'YELLOW']

We could also use comprehensions to get specific digits in a string:

number = 4321
digits = [int(d) for d in str(number)]
print(digits  # prints [4, 3, 2, 1])

We can nest list comprehensions to make more complicated lists. Let’s create a matrix, then create the transpose of the matrix:

>>> matrix = [[r* 3+i for i in range(1, 4)] for r in range(4)]
>>> matrix
[[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 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:

>>> [n**3 for n in range(101) if sqrt(n).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 = [n for n in nums if n > 0]
>>> new_nums
[4, 7, 9, 34, 3]

Readability

To make your comprehensions more readable, I recommend always breaking them over multiple lines of code.

I also recommend that you write comprehensions by writing a for loop first and then copy-pasting your way from a loop to a comprehension.

Let’s copy-paste our way from a loop to a comprehension:

pet_counts = {'cats' : 6, 'dogs' : 4, 'hamsters' : 7, 'birds' : 3}
too_many = []
for pet, num in num_pets.items():
    if num > 4:
        too_many.append(pet)

Let’s copy-paste our way into a comprehension:

too_many = [
    pet
    for pet, num in num_pets.items()
    if num > 4
]

Comprehensions can always be written over multiple lines and doing so often improves readability.

Comprehension Exercises

These exercises are all in the lists.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 get_vowel_names

Tip

You should use at least one list comprehension in each of these exercises!

Starting with a vowel

Edit the get_vowel_names function so that it accepts a list of names and returns a new list containing all names that start with a vowel. It should work like this:

>>> from lists import get_vowel_names
>>> names = ["Alice", "Bob", "Christy", "Jules"]
>>> get_vowel_names(names)
['Alice']
>>> names = ["scott", "arthur", "jan", "elizabeth"]
>>> get_vowel_names(names)
['arthur', 'elizabeth']

Power List By Index

Edit the power_list function so that it 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:

>>> from lists import power_list
>>> 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]

Flatten a Matrix

Edit the flatten function to that it will take a matrix (a list of lists) and return a flattened version of the matrix.

>>> from lists import flatten
>>> 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]

Reverse Difference

Edit the function reverse_difference so that it accepts a list of numbers and returns a new copy of the list with the reverse of the list subtracted.

Example usage:

>>> from lists import reverse_difference
>>> reverse_difference([9, 8, 7, 6])
[3, 1, -1, -3]
>>> reverse_difference([1, 2, 3, 4, 5])
[-4, -2, 0, 2, 4]
>>> reverse_difference([3, 2, 1, 0])
[3, 1, -1, -3]
>>> reverse_difference([0, 0])
[0, 0]

Matrix Addition

Edit the function matrix_add so that it takes two matrices (lists of lists of numbers) and returns a new matrix (list of lists of numbers) with the corresponding numbers added together.

Example usage:

>>> from lists import matrix_add
>>> m1 = [[6, 6], [3, 1]]
>>> m2 = [[1, 2], [3, 4]]
>>> matrix_add(m1, m2)
[[7, 8], [6, 5]]
>>> m1
[[6, 6], [3, 1]]
>>> m2
[[1, 2], [3, 4]]
>>> matrix_add([[5]], [[-2]])
[[3]]
>>> m1 = [[1, 2, 3], [4, 5, 6]]
>>> m2 = [[-1, -2, -3], [-4, -5, -6]]
>>> matrix_add(m1, m2)
[[0, 0, 0], [0, 0, 0]]

Transpose

File: Edit the transpose function in the lists.py file.

Test: Run python test.py transpose in your exercises directory.

Exercise: Make a function transpose that accepts a list of lists and returns the transpose of the list of lists.

Example usage:

>>> from zip import transpose
>>> transpose([[1, 2], [3, 4]])
[[1, 3], [2, 4]]
>>> matrix = [['a','b','c'],['d','e','f'],['g','h','i']]
>>> transpose(matrix)
[['a', 'd', 'g'], ['b', 'e', 'h'], ['c', 'f', 'i']]

Factors

File: Edit the get_factors function in the lists.py file.

Test: Run python test.py get_factors in your exercises directory.

Exercise: The function get_factors returns the factors of a given number.

Example:

>>> from lists import get_factors
>>> get_factors(2)
[1, 2]
>>> get_factors(6)
[1, 2, 3, 6]
>>> get_factors(100)
[1, 2, 4, 5, 10, 20, 25, 50, 100]

Pythagorean Triples

Edit the triples function so that it takes a number and returns a list of tuples of 3 integers where each tuple is a Pythagorean triple, and the integers are all less then the input number.

A Pythagorean triple is a group of 3 integers a, b, and c, such that they satisfy the formula a**2 + b**2 = c**2

>>> from lists import triples
>>> triples(15)
[(3, 4, 5), (5, 12, 13), (6, 8, 10)]
>>> triples(30)
[(3, 4, 5), (5, 12, 13), (6, 8, 10), (7, 24, 25), (8, 15, 17), (9, 12, 15), (10, 24, 26), (12, 16, 20), (15, 20, 25), (20, 21, 29)]