diff --git a/04_iteration_review_and_exercises.ipynb b/04_iteration_review_and_exercises.ipynb new file mode 100644 index 0000000..0e937b7 --- /dev/null +++ b/04_iteration_review_and_exercises.ipynb @@ -0,0 +1,941 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "# Chapter 4: Iteration" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Content Review" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "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**?" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Q2**: Solving a problem with a **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 **backwards** fashion!" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Q3**: Explain what **duck typing** means! Why can it cause problems? Why it is [not a bug but a feature](https://www.urbandictionary.com/define.php?term=It%27s%20not%20a%20bug%2C%20it%27s%20a%20feature)?" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Q4**: What is **syntactic sugar**?" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**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." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Q12**: Whereas a **recursion** may accidently result in a **never ending** program, `while`-loops and `for`-loops are guaranteed to **terminate**." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Q13**: Before writing **any** kind of **loop**, we **always** need to think about a **stopping criterion** ahead of time." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Q14**: **Container** types such as `list` objects are characterized by their **support** for **being looped over**, for example as in:" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```python\n", + "for element in container:\n", + " # do something for every element\n", + " ...\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Coding Exercises" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Towers of Hanoi" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "A popular example for a problem that is solved with a recursion art the **[Towers of Hanoi](https://en.wikipedia.org/wiki/Tower_of_Hanoi)**.\n", + "\n", + "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$." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "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", + "\n", + "The [MIT Blossoms Initative](https://blossoms.mit.edu/) is primarily aimed at high school students and does not have any prerequisites.\n", + "\n", + "The video consists of three segments the last of which is *not* necessary to have watched in order to solve the tasks below. So, watch the video until 37:55." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from IPython.display import YouTubeVideo\n", + "YouTubeVideo(\"UuIneNBbscc\", width=\"60%\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Video Review Questions" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**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**?" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Naive Translation to Python" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "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 progam's output: For example, an online version of the game can be found **[here](https://www.mathsisfun.com/games/towerofhanoi.html)**." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's first **generalize** the mathematical relationship from above.\n", + "\n", + "While the first number of $Sol(\\cdot)$ is the number of `disks` $n$, the second and third \"numbers\" are actually 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", + "\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)$" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In words, this means that in order to move a tower consisting of $n$ disks from an `origin` $o$ to a `destination` $d$, we three steps must be executed:\n", + "\n", + "1. Move the top most $n - 1$ disks of the tower temporarily from $o$ to $i$ (= sub-problem 1)\n", + "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", + "\n", + "Assume that all arguments to `sol()` will be `int` objects!\n", + "\n", + "Once completed, `sol()` should print out all the moves in the correct order. With **printing a move**, we simply mean a line like \"1 -> 3\", short for \"Move the top-most disk from spot 1 to spot 3\".\n", + "\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!" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "sol(1, 1, 3)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "sol(2, 1, 3)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "sol(3, 1, 3)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "sol(4, 1, 3)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Pythonic Re-Factoring" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The previous `sol()` implementation does the job but the conditional statement needed in unnecessarily tedious. \n", + "\n", + "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", + "\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." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def hanoi(disks, *, origin=\"left\", intermediate=\"center\", destination=\"right\"):\n", + "\n", + " # answer to Q15.9\n", + " # ...\n", + "\n", + " # answer to Q15.10\n", + " # ...\n", + " # ..." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Q15.9**: Copy the base case from `sol()`." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**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", + "\n", + "Also, write a `print()` statement analogous to the one in `sol()` in between the two recursive function calls. Is it ok to just copy and paste it?" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Q15.11**: Execute the code cells below and confirm that the printed moves are correct!" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "hanoi(1)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "hanoi(2)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "hanoi(3)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "hanoi(4)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We could of course also use **numeric labels** for the three steps like so." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "hanoi(3, origin=1, intermediate=2, destination=3)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Passing a Value \"up\" the Recursion Tree" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's say, we did not know about the **analytical formula** for the number of **minimal moves** given $n$.\n", + "\n", + "In such cases, we could modify a recursive function to return a count value to be passed up the recursion tree.\n", + "\n", + "In fact, 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", + "\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", + "\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." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def hanoi_moves(disks, *, origin=\"left\", intermediate=\"center\", destination=\"right\"):\n", + "\n", + " # answer to Q15.12\n", + " # ...\n", + "\n", + " moves = ... # <- answer to Q15.13\n", + " 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", + "\n", + "With `disks` in the range from `24` through `26` the computation time roughly doubles for each increase of `disks` by 1.\n", + "\n", + "**Q15.16**: Execute the code cells below and see for yourself!" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%timeit -n 1 -r 1\n", + "print(\"Number of moves:\", hanoi_moves(24))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%timeit -n 1 -r 1\n", + "print(\"Number of moves:\", hanoi_moves(25))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%timeit -n 1 -r 1\n", + "print(\"Number of moves:\", hanoi_moves(26))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Passing a Value \"down\" the Recursion Tree (Advanced)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "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 build 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", + "\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." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def hanoi_ordered(disks, *, origin=\"left\", intermediate=\"center\", destination=\"right\", offset=None):\n", + "\n", + " # answer to Q15.17\n", + " # ...\n", + "\n", + " total = (2 ** disks - 1)\n", + " half = (2 ** (disks - 1) - 1)\n", + " count = total - half\n", + "\n", + " if offset is not None:\n", + " count += offset\n", + "\n", + " 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!" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "hanoi_ordered(1)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "hanoi_ordered(2)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "hanoi_ordered(3)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "hanoi_ordered(4)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "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." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Open Question" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**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?" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " " + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.3" + }, + "toc": { + "base_numbering": 1, + "nav_menu": {}, + "number_sections": false, + "sideBar": true, + "skip_h1_title": true, + "title_cell": "Table of Contents", + "title_sidebar": "Contents", + "toc_cell": false, + "toc_position": {}, + "toc_section_display": false, + "toc_window_display": false + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/static/towers_of_hanoi.gif b/static/towers_of_hanoi.gif new file mode 100644 index 0000000..7ab13d4 Binary files /dev/null and b/static/towers_of_hanoi.gif differ