# Lab 6: Recursion and Midterm Review

*Due at 11:59:59 pm on 10/22/2019.*

## Starter Files

Download lab06.zip. Inside the archive, you will find starter files for the questions in this lab, along with a copy of the OK autograder.

## Submission

By the end of this lab, you should have submitted the lab with `python3 ok --submit`

. You may submit more than once before the deadline; only the final submission will be graded. Check that you have successfully submitted your code on okpy.org. See this article for more instructions on okpy and submitting assignments.

When you are ready to submit, run `ok`

with the `--submit`

option:

`python3 ok --submit`

After submitting, `ok`

will display a submission URL, with which you can view your submission on okpy.org.

## Recursion

A recursive function is a function that calls itself in its body, either directly or indirectly. Recursive functions have three important components:

- Base case(s), the simplest possible form of the problem you're trying to solve.
- Consider a recursive call on a smaller argument.
- Recursive case(s), where the function calls itself with a
*simpler argument*as part of the computation.

Let's look at the canonical example, `factorial`

:

```
def factorial(n):
if n == 0:
return 1
return n * factorial(n - 1)
```

We know by its definition that `0!`

is `1`

. So we choose `n = 0`

as our
base case. The recursive step also follows from the definition of
factorial, i.e. `n! = n * (n-1)!`

.

The next few questions in lab will have you writing recursive functions. Here are some general tips:

- Consider how you can solve the current problem using the solution to
a simpler version of the problem. Remember to
*trust the recursion*: assume that your solution to the simpler problem works correctly without worrying about how. - Think about what the answer would be in the simplest possible case(s). These will be your base cases - the stopping points for your recursive calls. Make sure to consider the possibility that you're missing base cases (this is a common way recursive solutions fail).
- It may help to write the iterative version first.

## Required Questions

### Question 1: A Bad List Flattener

There is something wrong with the following code! Figure out what is wrong with it, and then fix it to pass the doc string.

Hint, your fix should be very minor! Do not rewrite the whole program.

```
def bad_list_flattener(lst1, lst2):
"""
Flattens both lst1 and lst2, and returns the
concatenation of the two flattened lists. Flattening
a list means to collapse the list into one
dimension (like np.flatten).
>>> girls = [['Rachel', 'Green'], ['Phoebe', 'Buffay']]
>>> boys = [['Ross', 'Geller'], ['Chandler', 'Bing']]
>>> bad_list_flattener(girls, boys)
['Rachel', 'Green', 'Phoebe', 'Buffay', 'Ross', 'Geller', 'Chandler', 'Bing']
>>> cats = [['Persian'], ['British', 'Shorthair']]
>>> dogs = [['Golden', 'Retriever']]
>>> bad_list_flattener(dogs, cats)
['Golden', 'Retriever', 'Persian', 'British', 'Shorthair']
"""
newlist1 = []
newlist2 = []
for inner_lst in lst1:
for item in inner_lst:
newlist1 += item
for inner_lst in lst2:
for item in inner_lst:
newlist2 += item
return newlist1 + newlist2
```

```
def bad_list_flattener(lst1, lst2):
"""
Flattens both lst1 and lst2, and returns the
concatenation of the two flattened lists. Flattening
a list means to collapse the list into one
dimension (like np.flatten).
>>> girls = [['Rachel', 'Green'], ['Phoebe', 'Buffay']]
>>> boys = [['Ross', 'Geller'], ['Chandler', 'Bing']]
>>> bad_list_flattener(girls, boys)
['Rachel', 'Green', 'Phoebe', 'Buffay', 'Ross', 'Geller', 'Chandler', 'Bing']
>>> cats = [['Persian'], ['British', 'Shorthair']]
>>> dogs = [['Golden', 'Retriever']]
>>> bad_list_flattener(dogs, cats)
['Golden', 'Retriever', 'Persian', 'British', 'Shorthair']
"""
newlist1 = []
newlist2 = []
for inner_lst in lst1:
for item in inner_lst:
newlist1 += item
for inner_lst in lst2:
for item in inner_lst:
newlist2 += item
return newlist1 + newlist2
```

Use OK to test your code:

`python3 ok -q bad_list_flattener`

### Question 2: is_palindrome

Write a recursive solution for the function `is_palindrome`

such that it takes in any list and returns True if that list is the same way going forwards and backwards.

```
def is_palindrome(lst):
""" Returns True if the list is a palindrome. A palindrome is a list
that reads the same forwards as backwards
>>> is_palindrome([1, 2, 3, 4, 5])
False
>>> is_palindrome(["p", "a", "l", "i", "n", "d", "r", "o", "m", "e"])
False
>>> is_palindrome([True, False, True])
True
>>> is_palindrome([])
True
>>> is_palindrome(["a", "v", "a"])
True
>>> is_palindrome(["racecar", "racecar"])
True
>>> is_palindrome(["r", "a", "c", "e", "c", "a", "r"])
True
"""
"*** YOUR CODE HERE ***"
if len(lst) == 0 or len(lst) == 1:
return True
if lst[0] != lst[-1]:
return False
return is_palindrome(lst[1:-1])
```

Use OK to test your code:

`python3 ok -q is_palindrome`

### Question 3: AB Plus C

Implement `ab_plus_c`

, a function that takes arguments `a`

, `b`

, and
`c`

and computes `a * b + c`

. You can assume a and b are both nonnegative
integers. However, you can't use the `*`

operator. Try recursion!

```
def ab_plus_c(a, b, c):
"""Computes a * b + c.
>>> ab_plus_c(2, 4, 3) # 2 * 4 + 3
11
>>> ab_plus_c(0, 3, 2) # 0 * 3 + 2
2
>>> ab_plus_c(3, 0, 2) # 3 * 0 + 2
2
"""
"*** YOUR CODE HERE ***"
if b == 0:
return c
return a + ab_plus_c(a, b - 1, c)
```

Use OK to test your code:

`python3 ok -q ab_plus_c`

### Question 4: GCD

The greatest common divisor of two positive integers `a`

and `b`

is the
largest integer which evenly divides both numbers (with no remainder).
Euclid, a Greek mathematician in 300 B.C., realized that the greatest
common divisor of `a`

and `b`

is one of the following:

- the smaller value if it evenly divides the larger value, OR
- the greatest common divisor of the smaller value and the remainder of the larger value divided by the smaller value

In other words, if `a`

is greater than `b`

and `a`

is not divisible by
`b`

, then

`gcd(a, b) == gcd(b, a % b)`

Write the `gcd`

function recursively using Euclid's algorithm.

```
def gcd(a, b):
"""Returns the greatest common divisor of a and b.
Should be implemented using recursion.
>>> gcd(34, 19)
1
>>> gcd(39, 91)
13
>>> gcd(20, 30)
10
>>> gcd(40, 40)
40
"""
"*** YOUR CODE HERE ***"
a, b = max(a, b), min(a, b)
if a % b == 0:
return b
else:
return gcd(b, a % b)
```

Use OK to test your code:

`python3 ok -q gcd`

This is an example of the elegance that recursive solutions permit. Here for comparison is an iterative solution.

```
# Iterative solution, if you're curious
def gcd_iter(a, b):
"""Returns the greatest common divisor of a and b, using iteration.
>>> gcd_iter(34, 19)
1
>>> gcd_iter(39, 91)
13
>>> gcd_iter(20, 30)
10
>>> gcd_iter(40, 40)
40
"""
while a % b != 0:
a, b = b, a % b
return b
```

### Question 5: Adding matrices

To practice, write a function that adds two matrices together using list comprehensions. The function should take in two 2D lists of the same dimensions. Try to implement this in one line!

```
def add_matrices(x, y):
"""
>>> matrix1 = [[1, 3],
... [2, 0]]
>>> matrix2 = [[-3, 0],
... [1, 2]]
>>> add_matrices(matrix1, matrix2)
[[-2, 3], [3, 2]]
"""
"*** YOUR CODE HERE ***"
return [[x[i][j] + y[i][j] for j in range(len(x[0]))]
for i in range(len(x))]
```

Use OK to test your code:

`python3 ok -q add_matrices`

### Question 6: Mul_by_num

Using higher order functions, complete the `mul_by_num`

function. This
function should take an argument and return a one argument function
that multiplies any value passed to it by the original number.

```
def mul_by_num(num):
"""
Returns a function that takes one argument and returns num
times that argument.
>>> x = mul_by_num(5)
>>> y = mul_by_num(2)
>>> x(3)
15
>>> y(-4)
-8
"""
"*** YOUR CODE HERE ***"
def f(x):
return num*x
return f
```

Use OK to test your code:

`python3 ok -q mul_by_num`

### Question 7: Skip Add

Write a function `skip_add`

that takes a single argument `n`

and computes the sum of every other integer between 0 and `n`

starting from n. Assume `n`

is non-negative.

```
def skip_add(n):
""" Takes a number x and returns x + x-2 + x-4 + x-6 + ... + 0.
>>> skip_add(5) # 5 + 3 + 1 + 0
9
>>> skip_add(10) # 10 + 8 + 6 + 4 + 2 + 0
30
"""
"*** YOUR CODE HERE ***"
if n <= 0:
return 0
return n + skip_add(n - 2)
```

Use OK to test your code:

`python3 ok -q skip_add`

## Extra Questions

Extra questions are not worth extra credit and are entirely optional. They are designed to challenge you to think creatively!

### Question 8: Towers of Hanoi

A classic puzzle called the Towers of Hanoi is a game that consists of three
rods, and a number of disks of different sizes which can slide onto any rod.
The puzzle starts with `n`

disks in a neat stack in ascending order of size on
a `start`

rod, the smallest at the top, forming a conical shape.

The objective of the puzzle is to move the entire stack to an `end`

rod,
obeying the following rules:

- Only one disk may be moved at a time.
- Each move consists of taking the top (smallest) disk from one of the rods and sliding it onto another rod, on top of the other disks that may already be present on that rod.
- No disk may be placed on top of a smaller disk.

Complete the definition of `move_stack`

, which prints out the steps required to
move `n`

disks from the `start`

rod to the `end`

rod without violating the
rules.

```
def print_move(origin, destination):
"""Print instructions to move a disk."""
print("Move the top disk from rod", origin, "to rod", destination)
def move_stack(n, start, end):
"""Print the moves required to move n disks on the start pole to the end
pole without violating the rules of Towers of Hanoi.
n -- number of disks
start -- a pole position, either 1, 2, or 3
end -- a pole position, either 1, 2, or 3
There are exactly three poles, and start and end must be different. Assume
that the start pole has at least n disks of increasing size, and the end
pole is either empty or has a top disk larger than the top n start disks.
>>> move_stack(1, 1, 3)
Move the top disk from rod 1 to rod 3
>>> move_stack(2, 1, 3)
Move the top disk from rod 1 to rod 2
Move the top disk from rod 1 to rod 3
Move the top disk from rod 2 to rod 3
>>> move_stack(3, 1, 3)
Move the top disk from rod 1 to rod 3
Move the top disk from rod 1 to rod 2
Move the top disk from rod 3 to rod 2
Move the top disk from rod 1 to rod 3
Move the top disk from rod 2 to rod 1
Move the top disk from rod 2 to rod 3
Move the top disk from rod 1 to rod 3
"""
assert 1 <= start <= 3 and 1 <= end <= 3 and start != end, "Bad start/end"
"*** YOUR CODE HERE ***"
if n == 1:
print_move(start, end)
else:
other = 6 - start - end
move_stack(n-1, start, other)
print_move(start, end)
move_stack(n-1, other, end)
```

Use OK to test your code:

`python3 ok -q move_stack`