# 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]
```

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

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]]
[[7, 8], [6, 5]]
>>> m1
[[6, 6], [3, 1]]
>>> m2
[[1, 2], [3, 4]]
[[3]]
>>> m1 = [[1, 2, 3], [4, 5, 6]]
>>> m2 = [[-1, -2, -3], [-4, -5, -6]]
[[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)]
```