"Read [Chapter 4](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/04_iteration.ipynb) of the book. Then work through the fourteen review questions."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Essay Questions "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Answer the following questions briefly with *at most* 300 characters per question!"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q1**: What is so \"special\" about the number **7919**?"
"**Q2**: Solving a problem by **recursion** is not only popular in computer science and math. Name some examples from the fields of business or economics where problems are also solved in a **backward** fashion!"
"**Q3**: Explain what **duck typing** means! Why can it cause problems? Why is it [not a bug but a feature](https://www.urbandictionary.com/define.php?term=It%27s%20not%20a%20bug%2C%20it%27s%20a%20feature)?"
"**Q5**: Describe in your own words why the **recursive** version of `fibonacci()`, the \"Easy at first Glance\" example in the chapter, is computationally **inefficient**! Why does the **iterative** version of `fibonacci()`, the \"Hard at first Glance\" example, run so much faster?"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
" "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q6**: What is the conceptual difference between a **container** and a **list**?"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
" "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q7**: What is a good use case for the `for`-loop's optional `else`-clause?"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
" "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### True / False Questions"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Motivate your answer with *one short* sentence!"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q8**: When a **recursion** does **not** reach the base case, this is an example of the **early exit** strategy."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
" "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q9**: Any programming language **without** looping constructs like the `for` or `while` statements is **not** Turing complete."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
" "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q10**: A **recursive** formulation is the same as a **circular** one: The terms are **synonyms**."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
" "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q11**: Formulating a computational problem as a **recursion** results in an **efficient** implementation."
"**Q12**: Whereas a **recursion** may accidentally result in a **never-ending** program, `while`-loops and `for`-loops are guaranteed to **terminate**."
"In its basic version, a tower consisting of, for example, four disks with increasing radii, is placed on the left-most of **three** adjacent spots. In the following, we refer to the number of disks as $n$, so here $n = 4$.\n",
"\n",
"The task is to move the entire tower to the right-most spot whereby **two rules** must be obeyed:\n",
"\n",
"1. Disks can only be moved individually, and\n",
"2. a disk with a larger radius must *never* be placed on a disk with a smaller one.\n",
"\n",
"Although the **[Towers of Hanoi](https://en.wikipedia.org/wiki/Tower_of_Hanoi)** are a **classic** example, introduced by the mathematician [Édouard Lucas](https://en.wikipedia.org/wiki/%C3%89douard_Lucas) already in 1883, it is still **actively** researched as this scholarly [article](https://www.worldscientific.com/doi/abs/10.1142/S1793830919300017?journalCode=dmaa&) published in January 2019 shows.\n",
"\n",
"Despite being so easy to formulate, the game is quite hard to solve.\n",
"\n",
"Below is an interactive illustration of the solution with the minimal number of moves for $n = 4$."
"Watch the following video by [MIT](https://www.mit.edu/)'s professor [Richard Larson](https://idss.mit.edu/staff/richard-larson/) for a comprehensive introduction.\n",
"The video consists of three segments, the last of which is *not* necessary to have watched to solve the tasks below. So, watch the video until 37:55."
"**Q15.1**: Explain for the $n = 3$ case why it can be solved as a **recursion**!"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
" "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q15.2**: How does the number of minimal moves needed to solve a problem with three spots and $n$ disks grow as a function of $n$? How does this relate to the answer to **Q15.1**?"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
" "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q15.3**: The **[Towers of Hanoi](https://en.wikipedia.org/wiki/Tower_of_Hanoi)** problem is of **exponential growth**. What does that mean? What does that imply for large $n$?"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
" "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q15.4**: The video introduces the recursive relationship $Sol(4, 1, 3) = Sol(3, 1, 2) ~ \\bigoplus ~ Sol(1, 1, 3) ~ \\bigoplus ~ Sol(3, 2, 3)$. The $\\bigoplus$ is to be interpreted as some sort of \"plus\" operation. How does this \"plus\" operation work? How does this way of expressing the problem relate to the answer to **Q15.1**?"
"As most likely the first couple of tries will result in *semantic* errors, it is advisable to have some sort of **visualization tool** for the program's output: For example, an online version of the game can be found **[here](https://www.mathsisfun.com/games/towerofhanoi.html)**."
"While the first number of $Sol(\\cdot)$ is the number of `disks` $n$, the second and third \"numbers\" are the **labels** for the three spots. Instead of spots `1`, `2`, and `3`, we could also call them `\"left\"`, `\"center\"`, and `\"right\"` in our Python implementation. When \"passed\" to the $Sol(\\cdot)$ \"function\" they take on the role of an `origin` (= $o$) and `destination` (= $d$) pair.\n",
"So, the expression $Sol(4, 1, 3)$ is the same as $Sol(4, \\text{\"left\"}, \\text{\"right\"})$ and describes the problem of moving a tower consisting of $n = 4$ disks from `origin` `1` / `\"left\"` to `destination` `3` / `right`. As we have seen in the video, we need some `intermediate` (= $i$) spot."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"In summary, the generalized functional relationship can be expressed as:\n",
"\n",
"$Sol(n, o, d) = Sol(n-1, o, i) ~ \\bigoplus ~ Sol(1, o, d) ~ \\bigoplus ~ Sol(n-1, i, d)$"
"2. Move the remaining and largest disk from $o$ to $d$\n",
"3. Move the the $n - 1$ disks from the temporary spot $i$ to $d$ (= sub-problem 2)\n",
"\n",
"The two sub-problems can be solved via the same recursive logic."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"$Sol(\\cdot)$ can be written in Python as a function `sol()` that takes three arguments `disks`, `origin`, and `destination` that mirror $n$, $o$, and $d$.\n",
"Once completed, `sol()` should print out all the moves in the correct order. With **printing a move**, we mean a line like \"1 -> 3\", short for \"Move the top-most disk from spot 1 to spot 3\".\n",
"Write your answers to **Q15.5** to **Q15.7** into the subsequent code cell and finalize `sol()`! No need to write a docstring or validate the input here."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def sol(disks, origin, destination):\n",
"\n",
" # answer to Q15.5\n",
" # ...\n",
"\n",
" # answer to Q15.6\n",
" # ...\n",
" # ...\n",
" # ...\n",
" # ...\n",
" # ...\n",
" # ...\n",
" # ...\n",
" # ...\n",
" # ...\n",
" # ...\n",
" # ...\n",
"\n",
" # answer to Q15.7\n",
" # ...\n",
" # ..."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q15.5**: What is the `disks` argument when the function reaches its **base case**? Check for the base case with a simple `if` statement and return from the function using the **early exit** pattern!"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q15.6**: If not in the base case, `sol()` needs to determine the `intermediate` spot given concrete `origin` and `destination` arguments. For example, if called with `origin=1` and `destination=2`, `intermediate` must be `3`.\n",
"\n",
"Add **one** compound `if` statement to `sol()` that has a branch for **every** possible `origin`-`destination` pair that sets a variable `intermediate` to the correct temporary spot. **How many** branches will there be?"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q15.7**: `sol()` needs to call itself **two more times** with the correct 2-pairs chosen from the three available spots `origin`, `intermediate`, and `destination`.\n",
"\n",
"In between the two recursive function calls, write a `print()` statement that prints out from where to where the \"remaining and largest\" disk has to be moved!"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q15.8**: Execute the code cells below and confirm that the printed moves are correct!"
"Let's create a more concise `hanoi()` function that, in addition to a positional `disks` argument, takes three keyword-only arguments `origin`, `intermediate`, and `destination` with default values `\"left\"`, `\"center\"`, and `\"right\"`.\n",
"Write your answers to **Q15.9** and **Q15.10** into the subsequent code cell and finalize `hanoi()`! No need to write a docstring or validate the input here."
"**Q15.10**: Instead of conditional logic, `hanoi()` calls itself **two times** with the **three** arguments `origin`, `intermediate`, and `destination` passed on in a **different** order.\n",
"\n",
"Figure out how the arguments are passed on in the two recursive `hanoi()` calls!\n",
"This is similar to what we do in the recursive versions of `factorial()` and `fibonacci()` in [Chapter 4](https://github.com/webartifex/intro-to-python/blob/master/04_iteration.ipynb), where we pass up an intermediate result.\n",
"Let's create a `hanoi_moves()` function that follows the same internal logic as `hanoi()`, but, instead of printing out the moves, returns the number of steps done so far in the recursion.\n",
"Write your answers to **Q15.12** to **Q15.14** into the subsequent code cell and finalize `hanoi_moves()`! No need to write a docstring or validate the input here."
" moves += hanoi_moves(...) # <- answer to Q15.14 between the ()\n",
" moves += hanoi_moves(...) # <- answer to Q15.14 between the ()\n",
"\n",
" return moves"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q15.12**: Copy the base case from `hanoi()`! What count should be returned when it is reached?"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q15.13**: Initialize the variable `moves` with an appropriate count! This is the number of moves that corresponds to **one** recursive function call."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q15.14**: `moves` is updated with the counts passed up from the two recursive calls.\n",
"\n",
"Complete the two recursive function calls with the same arguments as in `hanoi()`!"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q15.15**: Write a `for`-loop that prints out the **minimum number** of moves needed to solve Towers of Hanoi for any number of `disks` from `1` through `20` to confirm your answer to **Q15.2**."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"##### Time Complexity"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Observe how quickly the `hanoi_moves()` function slows down for increasing `disks` arguments.\n",
"The above `hanoi()` prints the optimal solution's moves in the correct order but fails to label each move with an order number. This can be built in by passing on one more argument `offset` down the recursion tree. As the logic gets a bit \"involved,\" `hanoi_ordered()` below is almost finished.\n",
"Write your answers to **Q15.17** and **Q15.18** into the subsequent code cell and finalize `hanoi_ordered()`! No need to write a docstring or validate the input here."
" hanoi_ordered(..., offset=offset) # <- answer to Q15.18 between the ()\n",
" # answer to Q15.18\n",
" hanoi_ordered(..., offset=count) # <- answer to Q15.18 between the ()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q15.17**: Copy the base case from the original `hanoi()`!"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q15.18**: Complete the two recursive function calls with the same arguments as in `hanoi()` or `hanoi_moves()`! Do not change the already filled in `offset` arguments!\n",
"\n",
"Then, copy the `print()` statement from `hanoi()` and adjust it to print out `count` as well!"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q15.19**: Execute the code cells below and confirm that the order numbers are correct!"
"Lastly, it is to be mentioned that for problem instances with a small `disks` argument, it is easier to collect all the moves first in a list and then add the order number with the [enumerate()](https://docs.python.org/3/library/functions.html#enumerate) built-in."
"**Q15.20**: Conducting your own research on the internet (max. 15 minutes), what can you say about generalizing the **[Towers of Hanoi](https://en.wikipedia.org/wiki/Tower_of_Hanoi)** problem to a setting with **more than three** landing spots?"