#!/usr/bin/env python
# coding: utf-8
# # Week 4 Day 16, Core: Memoization
# In[ ]:
def check_test(actual, expected):
if expected != actual:
print(f"Function should return the value {expected}, it is returning the value {actual}")
else:
print(f"Congratulations, the test case passed!")
# ## 0. Climbing Stairs
# You are climbing a staircase. It takes `n` steps to reach the top. Each time you can either climb `1` or `2` steps. In how many distinct ways can you climb to the top?
# ### 0.1
# Write a recursive function `climbing_stairs(n)` that takes an integer `n` and returns the number of distinct ways you can climb to the top.
#
#
# Give me a Hint (click)
# If you know in how many ways you can climb n-2 steps, and in how many ways you can climb n-1 steps, how does that help you in computing in how many ways you can climb n steps?
#
# In[ ]:
def climbing_stairs(n):
# Your code here
pass
# In[ ]:
# Test your code
print(climbing_stairs(10)) # This should give 89
print(climbing_stairs(35)) # This should give 14930352
# You might notice that the computation of `climbing_stairs(35)` takes a few seconds.
# ### 0.2
# Add `print("Calculating value for " + str(n))` at the beginning of your function (make it the first line), run it, and run the cell below :
# In[ ]:
climbing_stairs(10)
# How many times does it print `3` ?
# Answer here:
# What did you learn from the number?
# Answer here:
# ## Check your answer here
# In[ ]:
def check_test(actual, expected):
if expected != actual:
print(f"Function should return the value {expected}, it is returning the value {actual}")
else:
print(f"Congratulations, the test case passed!")
check_test(climbing_stairs(2), 2)
check_test(climbing_stairs(5), 8)
check_test(climbing_stairs(6), 13)
# ## 1. Climbing Stairs - Memoized
# We are going to store things we have computed before in a list `mem` such that
# $$
# mem[i] = \begin{cases}-1 \text{ if we haven't computed } climbing\_stairs_i \text{ before }\\
# climbing\_stairs_i \text{ if we have already computed it}
# \end{cases}
# $$
#
# ### 1.1
# Write a function `create_memory(n)` that takes an integer `n` and returns a list L of length `n` that contains the integer `-1` repeated `n` times.
#
#
# In[ ]:
def create_memory(n):
# Your code here
#
# ### 1.2
# Write a function `check(i, mem)` that takes an index `i` and a list `mem` and returns `True` if `mem[i]` is not equal to `-1`:
# In[ ]:
def check(i, mem):
# Your code here
# ### 1.3
# Write a function `add(mem, i, value)` that takes a list `mem`, an index `i` and an integer `value` and that stores the integer `value` in the list `mem` at index `i` :
# In[ ]:
def add(mem, i, value):
# Your code here
# ### 1.4
# Write a recursive function `memo_climbing_stairs(n, mem)` that takes an integer `n` and a list `mem`.
# Here is how it should work :
#
# - If
mem[n]
is not equal to -1
, it should return mem[n]
# - If
mem[n]
is equal to -1
, it should compute the value recursively and store it in mem[n]
before returning it.
#
# In[ ]:
def memo_climbing_stairs(n, mem):
# Your code here
# ### 1.5
# Write a function `climbing_stairs(n)` that takes an integer `n`. It should use `create_memory(n)` and `memo_climbing_stairs(n, mem)`.
*Hint: Remember that we are storing values from 0 to n*
# In[ ]:
def climbing_stairs(n):
# Your code here
# Check your answer here
check_test(climbing_stairs(2), 2)
check_test(climbing_stairs(5), 8)
check_test(climbing_stairs(6), 13)
check_test(climbing_stairs(35), 14930352)
# Do you notice any difference in how long it takes to compute `climbing_stairs(35)`?
# **your answer here**
# ## 2. Fibonnacci Sequence
#
# The fibonnacci sequence is defined as below :
#
# $$
# \Large
# f_n = \begin{cases}n \text{ if } \leq 1\\
# f_{n-1} + f_{n-2} \text{ if } n > 1
# \end{cases}
# $$
#
# For example, the first eight Fibonnacci numbers are:
#
# 0,1,1,2,3,5,8,13 . . .
#
# ### 2.1
#
# Write a recursive function `fib(n)` that takes an integer `n` and returns $f_n$.
# In[ ]:
def fib(n):
# Your code here
# ### 2.2
#
# Write a memoized version of the above function.
#
#
# Give me a Hint (click)
# This should work the same way as 2.4.
#
# In[ ]:
def memo_fib(n, mem):
# Your code here
# ### 2.3
#
# Write a function `fib(n)` that takes an integer `n`. It should use `create_memory(n)` and `memo_fib(n, mem)`.
*Hint: Remember that we are storing fibs from 0 to n*
# In[ ]:
def fib(n):
# Your code here
# ## Check your answer here
# In[ ]:
check_test(fib(7), 13)
check_test(fib(0), 0)
check_test(fib(1), 1)
# ## Q:
# Do you think `fibonnacci sequence` and `climbing stairs` are related? If so, how?
# YOUR ANSWER GOES HERE
# ## 3. Making Basketball Teams
#
# You have `n` classmates. You want to create a team of `k` players. How many different teams can you create ?
#
# You may have seen in math class that the number we are looking for is :
# $$
# \Large
# \binom{n}{k}
# $$
#
# To choose your team, you can either pick the tallest classmate and then pick the remaining `k-1` players, or you can decide to leave out your tallest classmate and pick a team among `n-1` classmates.
#
# This yields the recursive formula :
# $$
# \binom{n}{k} = \binom{n-1}{k-1} + \binom{n-1}{k}
# $$
#
#
# ### 3.1
# Write a recursive function `binom(n, k)` that takes two integers `n` and `k` and returns $\binom{n}{k}$ :
#
# *Hint : Think of your base case carefully. What is $\binom{n}{0}$ ? What is $\binom{0}{k}$ if $k \neq 0$ ?*
#
# In[ ]:
# ### 3.2
# We're going to create a list of lists called `mem` such that :
# $$
# mem[i][j] =
# \begin{cases}
# -1 \text{ if }\binom{i}{j} \text{ has not been computed before}\\
# \binom{i}{j} \text{ if it has}
# \end{cases}
# $$
#
# Write a function `create_memory(r, c)` that takes two integers `r` and `c` and returns a list of lists with `r` rows and `c` colums where **all entries are -1**.
#
#
# Give me a Hint (click)
# Make sure the rows are referencing different lists!
#
# For example, the code below **does NOT work**:
# ```python
# lst = [ [-1] * 4 ] * 3
# ```
#
# If you now modify an entry, for example `list[0][0] = 5`, the whole first column will change to 5, because all rows point to the same list. If you have internet, you can use `pythontutor.com` to visualize this!
#
# Instead, use a list comprehension or for loop to create the rows.
#
# In[ ]:
def create_memory(r, c):
# Your code here
# Check your answer here
matrix = create_memory(3, 2)
check_test(matrix, [[-1, -1], [-1, -1], [-1, -1]])
matrix[0][0] = 5 # Make sure only one entry changes
check_test(matrix, [[5, -1], [-1, -1], [-1, -1]])
# ### 3.3
# Write a function `memo_binom(n, k, mem)` that takes two integers `n`, `k` and a list of lists `mem` and that returns $\binom{n}{k}$.
#
# As before, this function should **check** in `mem` if $\binom{n}{k}$ has been computed before, and if not, compute it recursively and add the answer to `mem`.
# In[ ]:
# ### 3.4
# Write a function `binom(n, k)` that returns $\binom{n}{k}$. It should use `create_memory(r, c)` and `memo_binom(n, k, mem)`.
# In[ ]:
# ## 4. Moving in a grid
#
#
# We want to find the number of paths that go from `A` to `Z` in the grid below :
#
#
#
#
#
# Two examples paths are drawn below in red:
#
#
#
# |
# |
#
#
# We number the points in the grid like so :
#
#
#
#
# Let $T(a,b)$ denote the number of ways of getting from (0,0) to (a, b). For example $T(0,1) = 1$ and $T(1,1) = 3$
#
# ##### Recursive formula :
#
# If you know $T(a-1, b)$, $T(a, b-1)$ and $T(a-1, b-1)$, how can you find $T(a,b)$ ?
#
# **Your answer here**
#
# ### 4.1
# Write a recursive function `T(a,b)` that takes two integers `a` and `b` and returns the number of ways of getting from (0,0) to (a,b):
#
#
#
# Give me a Hint (click)
# For the base case, how many different paths are there from $(0, 0)$ to $(a, 0)$? How many different paths are there from $(0, 0)$ to $(0, b)$?
#
# In[ ]:
# ### 4.2
# How many ways are there of getting from `A` to `Z` ?
#
#
#
# In[ ]:
# Answer here
# ### 4.3
# Following the previous exercise's procedure, write a function `memo_T(a,b)` that takes integers `a` and `b` and returns $T(a,b)$:
# In[ ]:
# ### 4.4
# Write a function `T(a, b)` that takes two integers `a` and `b` and returns the number of ways of getting from (0,0) to (a,b). It should use `memo_T(a, b)` :
# In[ ]:
# ## 5. Min Number of Ways to Make Change
#
# Suppose you live in a country whose coin denominations are in the list ```L```. For example, in Ethiopia we would have ```L = [1,5,10,50,100,200]```, but other countries have different coin systems. Implement a function ```makeChange(n, L)``` which returns the minimum number of coins needed to make change for ```n``` cents using the coins in ```L```.
#
# *For example*, ```makeChange(14, [1,5,10,50,100,200])``` should return ```5```, since you can make change for ```14``` cents by giving one ```10``` cent piece and four ```1``` cent pieces, for a total of ```5``` coins (you could also give two ```5``` cent pieces and four ```1``` cent pieces, or ```14``` ```1``` cent pieces, but those options would each require more coins). If it is impossible to make change for ```n``` cents using ```L```, you should return ```-1```. For example, ```makeChange(3, [2, 5])``` should return ```-1``` since there is no way to make change for ```3``` cents using ```2``` cent and ```5``` cent pieces.
#
# ### 5.1
# First implement a function ```makeChange``` that takes as input an integer $n$ and a list $L$ (with coin values sorted in increasing order) and returns the minimum number of ways to make change using plain recursion.
# In[ ]:
def make_change(n, L):
# Your code here
pass
check_test(make_change(14, [1,5,10,25,50,100]), 5)
check_test(make_change(12,[1,4,6,8,10,12]), 1)
check_test(make_change(11,[4,6,8]), -1)
# ### 5.2
# Implement a faster version or your solution using memoization in `makeChange_memo(n, L)`.
# In[ ]:
def make_change_memo(n, L):
# Your code here
pass
check_test(make_change_memo(14, [1,5,10,25,50,100]), 5)
check_test(make_change_memo(50,[1,4,6,8,10,12]), 5)
check_test(make_change_memo(11,[4,6,8]), -1)
# ### 5.3 (Challenge)
#
# Can you modify your code so that it returns the list of coins that sum up to the amount you want (or `None` if no solution exists)?
#
# Hint: In your memoization table, store lists of coins rather than the amount of coins.
# In[ ]: