From 0fe9cca303634a63a6e8309e9587cc1189ccd4a9 Mon Sep 17 00:00:00 2001 From: Alexander Hess Date: Thu, 22 Oct 2020 18:31:16 +0200 Subject: [PATCH] Add initial version of chapter 09, part 2 --- 04_iteration/00_content.ipynb | 2 +- 09_mappings/02_content.ipynb | 1201 +++++++++++++++++++ 09_mappings/static/fibonacci_call_graph.png | Bin 0 -> 32706 bytes CONTENTS.md | 4 + 4 files changed, 1206 insertions(+), 1 deletion(-) create mode 100644 09_mappings/02_content.ipynb create mode 100644 09_mappings/static/fibonacci_call_graph.png diff --git a/04_iteration/00_content.ipynb b/04_iteration/00_content.ipynb index f7d1e41..0171c6c 100644 --- a/04_iteration/00_content.ipynb +++ b/04_iteration/00_content.ipynb @@ -874,7 +874,7 @@ "\n", "To understand this in detail, we have to study algorithms and data structures (e.g., with [this book](https://www.amazon.de/Introduction-Algorithms-Press-Thomas-Cormen/dp/0262033844/ref=sr_1_1?__mk_de_DE=%C3%85M%C3%85%C5%BD%C3%95%C3%91&crid=1JNE8U0VZGU0O&qid=1569837169&s=gateway&sprefix=algorithms+an%2Caps%2C180&sr=8-1)), a discipline within computer science, and dive into the analysis of **[time complexity of algorithms ](https://en.wikipedia.org/wiki/Time_complexity)**.\n", "\n", - "Luckily, in the Fibonacci case, the inefficiency can be resolved with a **caching** (i.e., \"reuse\") strategy from the field of **[dynamic programming ](https://en.wikipedia.org/wiki/Dynamic_programming)**, namely **[memoization ](https://en.wikipedia.org/wiki/Memoization)**. We do so in [Chapter 9 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/develop/09_mappings/00_content.ipynb#Memoization), after introducing the `dict` data type.\n", + "Luckily, in the Fibonacci case, the inefficiency can be resolved with a **caching** (i.e., \"reuse\") strategy from the field of **[dynamic programming ](https://en.wikipedia.org/wiki/Dynamic_programming)**, namely **[memoization ](https://en.wikipedia.org/wiki/Memoization)**. We do so in [Chapter 9 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/develop/09_mappings/02_content.ipynb#Memoization), after introducing the `dict` data type.\n", "\n", "Let's measure the average run times for `fibonacci()` and varying `i` arguments with the `%%timeit` [cell magic](https://ipython.readthedocs.io/en/stable/interactive/magics.html#magic-timeit) that comes with Jupyter." ] diff --git a/09_mappings/02_content.ipynb b/09_mappings/02_content.ipynb new file mode 100644 index 0000000..ae2c380 --- /dev/null +++ b/09_mappings/02_content.ipynb @@ -0,0 +1,1201 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "**Note**: Click on \"*Kernel*\" > \"*Restart Kernel and Clear All Outputs*\" in [JupyterLab](https://jupyterlab.readthedocs.io/en/stable/) *before* reading this notebook to reset its output. If you cannot run this file on your machine, you may want to open it [in the cloud ](https://mybinder.org/v2/gh/webartifex/intro-to-python/develop?urlpath=lab/tree/09_mappings/02_content.ipynb)." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "# Chapter 9: Mappings & Sets (continued)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "After introducing the `dict` type in the [first part ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/develop/09_mappings/00_content.ipynb) of this chapter, we first look at an extension of the packing and unpacking syntax that involves `dict` objects. Then, we see how mappings can help us write computationally more efficient implementations to recursive solutions of problems as introduced in [Chapter 4 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/develop/04_iteration/00_content.ipynb#Recursion). In a way, this second part of the chapter \"finishes\" Chapter 4." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "## Packing & Unpacking (continued)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Just as a single `*` symbol is used for packing and unpacking iterables in [Chapter 7 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/develop/07_sequences/03_content.ipynb#Packing-&-Unpacking), a double `**` symbol implements packing and unpacking for mappings.\n", + "\n", + "Let's say we have `to_words` and `more_words` as below and want to merge the items together into a *new* `dict` object." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "to_words = {\n", + " 0: \"zero\",\n", + " 1: \"one\",\n", + " 2: \"two\",\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "more_words = {\n", + " 2: \"TWO\", # to illustrate a point\n", + " 3: \"three\",\n", + " 4: \"four\",\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "By *unpacking* the items with `**`, the newly created `dict` object is first filled with the items from `to_words` and then from `more_words`. The item with the key `2` from `more_words` overwrites its counterpart from `to_words` as it is mentioned last." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{0: 'zero', 1: 'one', 2: 'TWO', 3: 'three', 4: 'four'}" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "{**to_words, **more_words}" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "### Function Definitions & Calls (continued)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Both, `*` and `**` may be used within the header line of a function definition, for example, as in `print_args1()` below. Here, *positional* arguments not captured by positional parameters are *packed* into the `tuple` object `args`, and *keyword* arguments not captured by keyword parameters are *packed* into the `dict` object `kwargs`.\n", + "\n", + "For `print_args1()`, all arguments are optional, and ..." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "def print_args1(*args, **kwargs):\n", + " \"\"\"Print out all arguments passed in.\"\"\"\n", + " for index, arg in enumerate(args):\n", + " print(\"position\", index, arg)\n", + "\n", + " for key, value in kwargs.items():\n", + " print(\"keyword\", key, value)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "... we may pass whatever we want to it, or nothing at all." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "print_args1()" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "position 0 a\n", + "position 1 b\n", + "position 2 c\n" + ] + } + ], + "source": [ + "print_args1(\"a\", \"b\", \"c\")" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "keyword first 1\n", + "keyword second 2\n", + "keyword third 3\n" + ] + } + ], + "source": [ + "print_args1(first=1, second=2, third=3)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "position 0 x\n", + "position 1 y\n", + "keyword flag True\n" + ] + } + ], + "source": [ + "print_args1(\"x\", \"y\", flag=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "We may even unpack `dict` and `list` objects." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "flags = {\"flag\": True, \"another_flag\": False}" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "keyword flag True\n", + "keyword another_flag False\n" + ] + } + ], + "source": [ + "print_args1(**flags)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "position 0 42\n", + "position 1 87\n", + "keyword flag True\n", + "keyword another_flag False\n" + ] + } + ], + "source": [ + "print_args1(*[42, 87], **flags)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "The next example, `print_args2()`, requires the caller to pass one positional argument, captured in the `positional` parameter, and one keyword argument, captured in `keyword`. Further, an optional keyword argument `default` may be passed in. Any other positional or keyword arguments are packed into either `args` or `kwargs`." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "def print_args2(positional, *args, keyword, default=True, **kwargs):\n", + " \"\"\"Print out all arguments passed in.\"\"\"\n", + " print(\"required positional\", positional)\n", + "\n", + " for index, arg in enumerate(args):\n", + " print(\"optional positional\", index, arg)\n", + "\n", + " print(\"required keyword\", keyword)\n", + " print(\"default keyword\", default)\n", + "\n", + " for key, value in kwargs.items():\n", + " print(\"optional keyword\", key, value)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "If the caller does not respect that, a `TypeError` is raised." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "ename": "TypeError", + "evalue": "print_args2() missing 1 required positional argument: 'positional'", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mprint_args2\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;31mTypeError\u001b[0m: print_args2() missing 1 required positional argument: 'positional'" + ] + } + ], + "source": [ + "print_args2()" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "ename": "TypeError", + "evalue": "print_args2() missing 1 required keyword-only argument: 'keyword'", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mprint_args2\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"p\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;31mTypeError\u001b[0m: print_args2() missing 1 required keyword-only argument: 'keyword'" + ] + } + ], + "source": [ + "print_args2(\"p\")" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "required positional p\n", + "required keyword k\n", + "default keyword True\n" + ] + } + ], + "source": [ + "print_args2(\"p\", keyword=\"k\")" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "required positional p\n", + "required keyword k\n", + "default keyword False\n" + ] + } + ], + "source": [ + "print_args2(\"p\", keyword=\"k\", default=False)" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "required positional p\n", + "optional positional 0 x\n", + "optional positional 1 y\n", + "required keyword k\n", + "default keyword True\n", + "optional keyword flag True\n" + ] + } + ], + "source": [ + "print_args2(\"p\", \"x\", \"y\", keyword=\"k\", flag=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "required positional p\n", + "optional positional 0 x\n", + "optional positional 1 y\n", + "required keyword k\n", + "default keyword False\n", + "optional keyword flag True\n" + ] + } + ], + "source": [ + "print_args2(\"p\", \"x\", \"y\", keyword=\"k\", default=False, flag=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "As above, we may unpack `list` or `dict` objects in a function call." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "positionals = [\"x\", \"y\", \"z\"]" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "required positional p\n", + "optional positional 0 x\n", + "optional positional 1 y\n", + "optional positional 2 z\n", + "required keyword k\n", + "default keyword False\n", + "optional keyword flag True\n", + "optional keyword another_flag False\n" + ] + } + ], + "source": [ + "print_args2(\"p\", *positionals, keyword=\"k\", default=False, **flags)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "## Memoization" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "### \"Easy at first Glance\" Example: [Fibonacci Numbers ](https://en.wikipedia.org/wiki/Fibonacci_number) (repeated)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "The *recursive* implementation of the [Fibonacci numbers ](https://en.wikipedia.org/wiki/Fibonacci_number) in [Chapter 4 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/develop/04_iteration/00_content.ipynb#\"Easy-at-first-Glance\"-Example:-Fibonacci-Numbers) takes long to compute for large Fibonacci numbers. For easier comparison, we show the old `fibonacci()` version here again." + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [], + "source": [ + "def fibonacci(i):\n", + " \"\"\"Calculate the ith Fibonacci number.\n", + "\n", + " Args:\n", + " i (int): index of the Fibonacci number to calculate\n", + "\n", + " Returns:\n", + " ith_fibonacci (int)\n", + " \"\"\"\n", + " if i == 0:\n", + " return 0\n", + " elif i == 1:\n", + " return 1\n", + " return fibonacci(i - 1) + fibonacci(i - 2)" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "144" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fibonacci(12)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Efficiency of Algorithms" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Timing the code cells below with the `%%timeit` magic shows how doubling the input (i.e., `12` becomes `24`) more than doubles how long it takes `fibonacci()` to calculate the solution. This is actually an understatement as we see the time go up by roughly a factor of $1000$ (i.e., from nano-seconds to milli-seconds). That is an example of **exponential growth**." + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "40.1 µs ± 1.26 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n" + ] + } + ], + "source": [ + "%%timeit -n 100\n", + "fibonacci(12)" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "12.1 ms ± 149 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n" + ] + } + ], + "source": [ + "%%timeit -n 100\n", + "fibonacci(24)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "The computation graph below visualizes what the problem is and also suggests a solution: In the recursive implementation, the same function calls are made over and over again. For example, in the visualization the call `fibonacci(3)`, shown as $F(3)$, is made *twice* when the actual goal is to calculate `fibonacci(5)`, shown as $F(5)$. This problem \"grows\" if the initial argument (i.e., `5` in the example) is chosen to be larger as we see with the many `fibonacci(2)`, `fibonacci(1)` and `fibonacci(0)` calls.\n", + "\n", + "Instead of calculating the return value of the `fibonacci()` function for the *same* argument over and over again, it makes sense to **cache** (i.e., \"store\") the result and reuse it. This concept is called **[memoization ](https://en.wikipedia.org/wiki/Memoization)**." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### \"Easy at second Glance\" Example: [Fibonacci Numbers ](https://en.wikipedia.org/wiki/Fibonacci_number) (revisited)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Below is a revision of the recursive `fibonacci()` implementation that uses a **globally** defined `dict` object, called `memo`, to store intermediate results and look them up.\n", + "\n", + "To be precise, the the revised `fibonacci()` first checks if the `i`th Fibonacci number has already been calculated before. If yes, it is in the `memo`. That number is then returned immediately *without* any more calculations. As `dict` objects are *optimized* for constant-time key look-ups, this takes essentially \"no\" time! With a `list` object, for example, the `in` operator would trigger a linear search, which takes longer the more elements are in the list. If the `i`th Fibonacci number has not been calculated before, there is no corresponding item in the `memo` and a recursive function call must be made. The result obtained by recursion is then inserted into the `memo`." + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "memo = {\n", + " 0: 0,\n", + " 1: 1,\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": { + "code_folding": [], + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "def fibonacci(i, *, debug=False):\n", + " \"\"\"Calculate the ith Fibonacci number.\n", + "\n", + " Args:\n", + " i (int): index of the Fibonacci number to calculate\n", + " debug (bool): show non-cached calls; defaults to False\n", + "\n", + " Returns:\n", + " ith_fibonacci (int)\n", + " \"\"\"\n", + " if i in memo:\n", + " return memo[i]\n", + "\n", + " if debug: # added for didactical purposes\n", + " print(f\"fibonacci({i}) is calculated\")\n", + "\n", + " recurse = fibonacci(i - 1, debug=debug) + fibonacci(i - 2, debug=debug)\n", + " memo[i] = recurse\n", + " return recurse" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "When we follow the flow of execution closely, we realize that the intermediate results represented by the left-most path in the graph above are calculated first. `fibonacci(1)`, the left-most leaf node $F(1)$, is the first base case reached, followed immediately by `fibonacci(0)`. From that moment onwards, the flow of execution moves back up the left-most path while adding together the two corresponding child nodes. Effectively, this mirrors the *iterative* implementation in that the order of all computational steps are *identical* (cf., the \"*Hard at first Glance*\" example in [Chapter 4 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/develop/04_iteration/02_content.ipynb#\"Hard-at-first-Glance\"-Example:-Fibonacci-Numbers--(revisited))).\n", + "\n", + "We added a keyword-only argument `debug` that allows the caller to print out a message every time a `i` was *not* in the `memo`." + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "fibonacci(12) is calculated\n", + "fibonacci(11) is calculated\n", + "fibonacci(10) is calculated\n", + "fibonacci(9) is calculated\n", + "fibonacci(8) is calculated\n", + "fibonacci(7) is calculated\n", + "fibonacci(6) is calculated\n", + "fibonacci(5) is calculated\n", + "fibonacci(4) is calculated\n", + "fibonacci(3) is calculated\n", + "fibonacci(2) is calculated\n" + ] + }, + { + "data": { + "text/plain": [ + "144" + ] + }, + "execution_count": 27, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fibonacci(12, debug=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Now, calling `fibonacci()` has the *side effect* of growing the `memo` in the *global scope*. So, subsequent calls to `fibonacci()` need not calculate any Fibonacci number with an index `i` smaller than the maximum `i` used so far. Because of that, this `fibonacci()` is *not* a *pure* function." + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "144" + ] + }, + "execution_count": 28, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fibonacci(12, debug=True) # no more recursive calls needed" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{0: 0,\n", + " 1: 1,\n", + " 2: 1,\n", + " 3: 2,\n", + " 4: 3,\n", + " 5: 5,\n", + " 6: 8,\n", + " 7: 13,\n", + " 8: 21,\n", + " 9: 34,\n", + " 10: 55,\n", + " 11: 89,\n", + " 12: 144}" + ] + }, + "execution_count": 29, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "memo" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "#### Efficiency of Algorithms (continued)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "With memoization, the recursive `fibonacci()` implementation is as fast as its iterative counterpart, even for large numbers.\n", + "\n", + "The `%%timeit` magic, by default, runs a code cell seven times. Whereas in the first run, *new* Fibonacci numbers (i.e., intermediate results) are added to the `memo`, `fibonacci()` has no work to do in the subsequent six runs. `%%timeit` realizes this and tells us that \"an intermediate result is being cached.\"" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The slowest run took 252.65 times longer than the fastest. This could mean that an intermediate result is being cached.\n", + "6.68 µs ± 15.8 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" + ] + } + ], + "source": [ + "%%timeit -n 1\n", + "fibonacci(99)" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The slowest run took 3603.20 times longer than the fastest. This could mean that an intermediate result is being cached.\n", + "85.1 µs ± 208 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" + ] + } + ], + "source": [ + "%%timeit -n 1\n", + "fibonacci(999)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "The iterative implementation still has an advantage as the `RecursionError` shows for larger `i`." + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": {}, + "outputs": [ + { + "ename": "RecursionError", + "evalue": "maximum recursion depth exceeded", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mRecursionError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mget_ipython\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrun_cell_magic\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'timeit'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m'-n 1'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m'fibonacci(9999)\\n'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;32m~/repos/intro-to-python/.venv/lib/python3.8/site-packages/IPython/core/interactiveshell.py\u001b[0m in \u001b[0;36mrun_cell_magic\u001b[0;34m(self, magic_name, line, cell)\u001b[0m\n\u001b[1;32m 2379\u001b[0m \u001b[0;32mwith\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mbuiltin_trap\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2380\u001b[0m \u001b[0margs\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0mmagic_arg_s\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mcell\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 2381\u001b[0;31m \u001b[0mresult\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mfn\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 2382\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mresult\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2383\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m\u001b[0m in \u001b[0;36mtimeit\u001b[0;34m(self, line, cell, local_ns)\u001b[0m\n", + "\u001b[0;32m~/repos/intro-to-python/.venv/lib/python3.8/site-packages/IPython/core/magic.py\u001b[0m in \u001b[0;36m\u001b[0;34m(f, *a, **k)\u001b[0m\n\u001b[1;32m 185\u001b[0m \u001b[0;31m# but it's overkill for just that one bit of state.\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 186\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mmagic_deco\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0marg\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 187\u001b[0;31m \u001b[0mcall\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mlambda\u001b[0m \u001b[0mf\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0ma\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mk\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mf\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0ma\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mk\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 188\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 189\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mcallable\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0marg\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/repos/intro-to-python/.venv/lib/python3.8/site-packages/IPython/core/magics/execution.py\u001b[0m in \u001b[0;36mtimeit\u001b[0;34m(self, line, cell, local_ns)\u001b[0m\n\u001b[1;32m 1171\u001b[0m \u001b[0;32mbreak\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1172\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 1173\u001b[0;31m \u001b[0mall_runs\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mtimer\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrepeat\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mrepeat\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mnumber\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 1174\u001b[0m \u001b[0mbest\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mmin\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mall_runs\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m/\u001b[0m \u001b[0mnumber\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1175\u001b[0m \u001b[0mworst\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mmax\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mall_runs\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m/\u001b[0m \u001b[0mnumber\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/.pyenv/versions/3.8.6/lib/python3.8/timeit.py\u001b[0m in \u001b[0;36mrepeat\u001b[0;34m(self, repeat, number)\u001b[0m\n\u001b[1;32m 203\u001b[0m \u001b[0mr\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 204\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mi\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mrange\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mrepeat\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 205\u001b[0;31m \u001b[0mt\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtimeit\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnumber\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 206\u001b[0m \u001b[0mr\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mappend\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mt\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 207\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mr\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/repos/intro-to-python/.venv/lib/python3.8/site-packages/IPython/core/magics/execution.py\u001b[0m in \u001b[0;36mtimeit\u001b[0;34m(self, number)\u001b[0m\n\u001b[1;32m 167\u001b[0m \u001b[0mgc\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdisable\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 168\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 169\u001b[0;31m \u001b[0mtiming\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0minner\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mit\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtimer\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 170\u001b[0m \u001b[0;32mfinally\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 171\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mgcold\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m\u001b[0m in \u001b[0;36minner\u001b[0;34m(_it, _timer)\u001b[0m\n", + "\u001b[0;32m\u001b[0m in \u001b[0;36mfibonacci\u001b[0;34m(i, debug)\u001b[0m\n\u001b[1;32m 15\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34mf\"fibonacci({i}) is calculated\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 16\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 17\u001b[0;31m \u001b[0mrecurse\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mfibonacci\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mi\u001b[0m \u001b[0;34m-\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mdebug\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mdebug\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0mfibonacci\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mi\u001b[0m \u001b[0;34m-\u001b[0m \u001b[0;36m2\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mdebug\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mdebug\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 18\u001b[0m \u001b[0mmemo\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mi\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mrecurse\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 19\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mrecurse\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "... last 1 frames repeated, from the frame below ...\n", + "\u001b[0;32m\u001b[0m in \u001b[0;36mfibonacci\u001b[0;34m(i, debug)\u001b[0m\n\u001b[1;32m 15\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34mf\"fibonacci({i}) is calculated\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 16\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 17\u001b[0;31m \u001b[0mrecurse\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mfibonacci\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mi\u001b[0m \u001b[0;34m-\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mdebug\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mdebug\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0mfibonacci\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mi\u001b[0m \u001b[0;34m-\u001b[0m \u001b[0;36m2\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mdebug\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mdebug\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 18\u001b[0m \u001b[0mmemo\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mi\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mrecurse\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 19\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mrecurse\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mRecursionError\u001b[0m: maximum recursion depth exceeded" + ] + } + ], + "source": [ + "%%timeit -n 1\n", + "fibonacci(9999)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "This exception occurs as Python must keep track of *every* function call *until* it has returned, and with large enough `i`, the recursion tree above grows too big. By default, Python has a limit of up to 3000 *simultaneous* function calls. So, theoretically this exception is not a bug in the narrow sense but the result of a \"security\" measure that is supposed to keep a computer from crashing. However, practically most high-level languages like Python incur such an overhead cost: It results from the fact that someone (i.e., Python) needs to manage each function call's *local scope*. With the `for`-loop in the iterative version, we do this managing as the programmer.\n", + "\n", + "We could \"hack\" a bit with Python's default configuration using the [sys ](https://docs.python.org/3/library/sys.html) module in the [standard library ](https://docs.python.org/3/library/index.html) and make it work. As we are good citizens, we reset everything to the defaults after our hack is completed." + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [], + "source": [ + "import sys" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [], + "source": [ + "old_recursion_limit = sys.getrecursionlimit()" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "3000" + ] + }, + "execution_count": 35, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "old_recursion_limit" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [], + "source": [ + "sys.setrecursionlimit(99999)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Computational speed is *not* the problem here." + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The slowest run took 50532.39 times longer than the fastest. This could mean that an intermediate result is being cached.\n", + "1.21 ms ± 2.97 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" + ] + } + ], + "source": [ + "%%timeit -n 1\n", + "fibonacci(9999)" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [], + "source": [ + "sys.setrecursionlimit(old_recursion_limit)" + ] + } + ], + "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.8.6" + }, + "livereveal": { + "auto_select": "code", + "auto_select_fragment": true, + "scroll": true, + "theme": "serif" + }, + "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": { + "height": "calc(100% - 180px)", + "left": "10px", + "top": "150px", + "width": "384px" + }, + "toc_section_display": false, + "toc_window_display": false + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/09_mappings/static/fibonacci_call_graph.png b/09_mappings/static/fibonacci_call_graph.png new file mode 100644 index 0000000000000000000000000000000000000000..64cba5d4c09a43f0c2599046e6821dfe595db40e GIT binary patch literal 32706 zcmb@uc{tST8$YhS=CntPqQpd`lI&YYg(3TvYzLJP8vCwJO{J`3-;=R360*~gHOpio zVUTsKBkS1x?)P+#KHq71kOV`6%k9`Ch` zA;){KpHDNbeue)W%Evtle+tUGTaVvc(CsnXgw{^i!f1r@Y9E#n)X%-|N4RqDGnZHL zoni+I(+rKJLmAo`VYlDg4SlX!*v9j$?$<}lJjC>>RM*J$Cb?v)oNqLjBprO!ec>UF zLJQGU3DQc}N;_pM;Y>5YOqI_16O78-FvXJ}x~@omYm5{`d(PBz8+C?PbP>tGOeN{i{_Su13b@-)gTw=ppv zaPIXiAjC#B!}I4g{R|W?br$BWroP!pjr0wmjc}ZKC&j8&L+^5QP z$wuyzp#xFy&-kn6&8KkU)hHHTg*yc{hNiLU+8LH>AuUedO?F|98M!17{MjVV(Xu>O z7Lc!bSP$7B@Bf7NtyZ*450}!tDt_$Nuo!c8@fa`0Jl^vIImLH4lC#%iRvEMFw8uMw z+x*y&rP7Cv`zJB*NRkMzFO}aWKiS33W6|qL_q*r4RpAU{<*vS$V|vVb^+Dib zEW^&*4|2lmz>em$+Wz?P*gv~Yhvd0nx;4jcRzGZD9m!#;nzC=YkjuMvINoj$FHF_^! z>zN*hugYRQ!bPgSE46orgIYrQbX8UXXI5RxbNC-P}e7}QD?~40?p9t4Hty`b$wKB<_cCR4}e@;1y%c7^$72hm5dke!I?_n24 zvRatv!1HRQo*Av<4DfXq*@k9goa48lb_bwnwK&qdot50+7Z$sJh8n{8?!L;mY7f&& zQe;6YRWMR>?z@t74IM)X^}*aqQR0s2vbN(|e;mCKc=c}GLCmM;`!p3!9gtalgz-Bm z66Vk-P&+v0iku=c6hdx`%4d5}MVyas|GVqpy_sEUCnlSl;mz8YIn$(yJ3M3fbcq)% za+`!3X5uQ=7W6T-Y|b$RC?RU((Uf?0S3X;x)7SwyDfbH>>_5YT8fuJx%Npn`WAcS^ z54{RmxkI#y4KG~F7fw0fK&^bsHV@CLlQxBal4Hc|x%RP%YiU5;9O&714fAi^glGPE zH*LFTw_`iz&ilJQm8p{s18E}u<7aC9PI6m(4ms~5p9x2rBEj_d2IDb5M81MuUz|HF zD9>r~4o1ptPVIPp+4-1zDlww!CQdc`*~EDo1@^oN{9VV(A;D}uVR#x`v*e9r3%UEc4O}~q3Mzmb=_eb+YcDM~A|V!}1<> zyg$`01NE7Uv0-4D_(tD6-H*SD?Z1Ih3cC>0nx!w2CDSRcmni4Q#VB*jbE8M-`;V$ZlwF z!2G8JG72*r;RphVlJ+bn6xmZ1?-beHGi`!mX{a$uPC`qq{``cuY)$1zv*|AO8Dc{1 z=?VO=P*i_Inm4~u>1{HU6ZGhOyD$FYlABXy=k=3CBj zGLv~2Ct}&;oi;|wH<3uGAy1%0JV#OV~kfKh8qL!j|z7W9|>9=!CDXI!^ z45JA<{y3`OhLo9or$p7ruwO!N+muYae=VCo1T7}xi^Y9d>JX?Ep@s1%wV40;23y$o zmBAUo#WjCTR@PKm)BVunW`)~-F-||ndt)7Cf7+&eX&5ULnz`t@2K^FGL?Fae#YVX- zZ)79s!BiPtCw0_;YNczvweI@w?|^F^tPe()E%v|0Ki_)-Z_)(#PCw!F98|>%qa%p{ z{_eB0-&@l((Vjp5*@Z7(9^=EPN>1N+K7XlryPFKbU*- z^-;9fR7>Iu0yKO~l$d=o0pOnCbi4J34!H#9*HqKYJNfB^>-)uS+)TNT{kkO64t)v^3Afz86Y>WYkQHtgdaj>$6#(-duBytA>t#+vHp&96tjxymsPc zs_A>^amM7FT6VtCFnt@$g-d&9FC1k@(AgaHGfeT?LKhl#jXA!2g(iWiSe;QaRVGb5 zU^k?dixN>;aFi&jJ9tWe_l}uvrAuXmFXkQR2QsqDqfFMIM;axlOyiX<25FVK%@@Ys z2paU1cAB`2ai(Bj5FE$94HXV!YuUJw1`(o_Z6t*w3Nju3pI7v){aO690U%2H<*l8b z$_DP{v{nt|sLP+iJ|ob&OO$wx%S#Dzeyr7NTCX@r^CRtbv&L?6jcpmae668T@@XPA zl-mhm4)LA~hw=$L)4yL^7*E|XM|t%%MK!#&#Mu@XR9c^VxpT{|Q*Qz<8DXng@cuIE zE;#kFq0gbOSN4RSq1jJPPr&YoBe>Iwaoh^TI^d4)3wecI7D3`OHH(>cp52x$Eh^+?`e|h zYf)B2^;C^Xa??n!E-sEn)dJ#vTRkWV^d!(jBAsx#vL=_v8czR zQJf>e<{lKl04)A560X1CUV56Ou78R_4}$AkH7fYd?+Vb76s3IMflJr~L@TeyRfn9K{7or;vAQv_*EyWal> zJcR}*5ODoS(!{`KYtW5yulY_7+B>Q~hOswGh?WXR{# zZs?3jS4;St0H^dkm&mo!m>^P|ls!c)w9?WcNcrOyO{Z9WgIX*}bg1trdXpfu`2)#d~dg0Br+^5*l!+ zk_MgDjfSe?jhfog<%|9fv7#74~k#Nur%8tFL7?|5sj zMgBB@nq^x$D%6NO)&Vcuzm?fhdU!3q7?=gV$CGY|%an=?SmM=AS3n3G;r+b^Me+Go z&1#9QuFp1?q-r8ao?Sh)e@1jfReFi5RtN!jY0gtUK4@J7EKW9gYshw)^~FR&j#;hZ zoxBG@1bEbfpO*RW|3M&F6{e80qD%7 zoq1cG3`9FQFz8`^n+_goWjxZ2UV1<&Mh#@T&y6GmQY3ul?5w`OOOO?wD*sO0)8jUt z1r30Qb+Zy(leWY4larLt`%X(6kdclJ5XSPUkQ8etr&M;ux*LPh%J%O*bFz6#YR0YV ztTSjcl@PH$JkYzAfa5V4#_KY-U~N8Ec0aFCgOc8Ik0^iwukbCq7_<6sOJ0I zDJpCXf-S3h%=rUT;OSKr=-++>lTXhr)>H)RfoX83L19LfbzXv!Unp`Kx8oORheJ~* z6!6^1M!;*;1s8R*{gT(}bnVH^ilH$5b6(EF*j9CUpPnz*PEmC40};5hu~uT!Esmo* zz6aW7)EB+QIcl1|E!z5n9<+@eewz>MZgp9Ll<@2~eOu)VdVq#y6;0TqT|aKwrI4d*UVFwgm;=-Z;|8#pWh}yQC4%zeTx5wjn|-4I-lk&B2?OXwBh~lDZ^V1e zXBNOItY-Tz?$`Fz=Fkuil~jYG_XOx1dI|TZK|fGCzRwU47VNXf$6bi>@qD0kphuzK z;^zREop0>2L}*{-3A2$e38-0K7ov{0^OLii=i`Yft@gCH*6|&q&pzhzCx6Y<6T|>z z!8;5`n)5n5XL{V&<1(-hLz1G$$QFz{bbherO9Lu`ip}o6guGO;z|+d;M!3$zZ&~`UTl|@Xo4)6mpSY67EtE?|XKH%g}Ww6#qH=QugKMLrgQD`m0~F6dSJi zUVZfA8V0C^dc>!H?61IHxJd?n&q_bXX}(!vnDvQP6I}(_m8HJ(E*KQ5 zX%yIWbHSHZ>^pgQjP;M-aQ@$32s+OHS7q`4S&+=Tg0*E*(|{v$8V}n8ohFMz;T6L# z@{ht1$L9fr-dn)0OQh_S@sx?o(l1cah`dtd9_{X)2~5SO7g|?umGCwm=Isnn*W2EsO{pdP``)GT3&NaBE6D1){*fyhaKOwEbapG@c2Pe z%xR`N83f@u^x=*v-g)=e6NIR!lRNXR&~2&e*U9@OCISbnH&&)(Ffyw@pUU1@0%W4l zXcdU$eIRQX}E_n}k%o(JV zW2f#gxef!}3w2l;3Rhx~-N?_Qu_SeAt@IOQu4&8F`Q|juAHP$Gxc)0l;foh5eiBOj5w?M zZ}D^VyH-9MN~;UvL<5qw#j8k6Uc~?<2yO*+33L7V-umee<$pU5Q~4&KyP3OWh8uh7 zkvG=qGMLG(0+QVyYq6h@eTz_LF*FJ}B*3ZAm;=&_ZwMc(yA(SCxeoOF!!+{|UjEW3 zfK-h|uSDdq^adJu&bQ%#T zxVm4W=v%1EJJJybhA@3xhDiYGRxOJR?4Yo@sw1jAp5I0;*!NfY;1Q7mqiw<1!n&BU3NWgq``#dKz1!j+%2ShueMg)B{KWVM4 z(8Ccu8v~kb+p>8ylvm?ENwJ6DFK2l_pDQ@)-0c`nh_)%l^p$`F*~#^7dRf|h|MAP$ zGy>SAnC;{PW@_X>9{qL3&g?uX;{1=v3$}BJ3QGB_sFrtmc?Id*HX1dEn#K@M*c`t6 zi7yArRi~f-utS3(T0?JACPSrTYlzz(=xyhoY=?IS)MdHWVKf=4YoxxYQ^lF6S@BgA z)K0WEP>R|Wxb&w&r<#i-&-insP<_9ZyFJ`hpu6U1vRcXI~9TA%Yv_S zu)jo5IuYzDTx%8}r86E-p`u;x;fkAX?=ErHjaV`PCgS7H&7%FgOj*WVpZ@!gi)t!~ z8D-gXFO4gAMylkVfey(ct4auw{-hO>?-~c93J|OI>`W{vt()EZ^UgcJshyI}@}6_F9Vwh(aOEAjBD2DNCjpB&2YGgqZSnV(f-$$!sg&S@H%*RKp_&O z=-W9KepMtreQmHQo}Ibhd`Oeg8W5lxV|SzdxT|F1xZ@SlhY{?{W>%@^E%VpvCC_#l z_zn8+Z)OV+8;&?tz_o;p4cN-#r9(7`9GEe5?$#c4|KfghcRZThCVijO))iObgowpN zK({#8Q-bDx^(Ru~oy<5lfh(|(7k~2V?sg^K2oPvA;2&fcBubP{m9LyV-JYu;KAMMU zChP*SP}(oi*ml0Trk$aqtdVV0X1AI#D+85DP}|7j%bO>ag%1PXHD6fjVi^wU{;hK| zV;Ex6%^>;n#a=w!nSOB2HMN9DNWqudSe-@9zMY@!(oF~x=$0xe$Q0N(@%XOFJY~A0 z?K^D(zn1A2$@b{u7W{s(!N5A9_DsFt#kH55{x98YnSY<_s*j%Pn%<;1D<6ZV0oEJF z|DR9-3EHQe=z=1Fy1#2H4^w?&oFHj;v_DC`%cdlY(2;A-ch@U& zMTZnGV@w8y72g*pW%lXjF+;)xz5zNGw--2B-G8|09zC{szLgN5bt{n zO+6#Gy%?ws&@G(l0o_T&tqcG4^C0V+Mxg8>xCx{c>gc z;rkbCP7wqs1~>aAsJ2QPuFqe9F2L&$?j*uWx-vdHm}l|zF#Qpfshc)WNyVTThUCF# z_Q}K^*?fM1Q!#ypz>HBoZ%PzOE{y>w&H8e()A311W6A@>up63*6v%V(;nahVpV2Q%$ zlp=d`+@uql}o#&P|C z#-pvU&%VKIZ>(<^m|%$Lb0t&8h9ZjF>o_x5j}p4h^y(}e#jT z)1{Kp{uk;E5=I7XL9#gUZO%+=YL3$eSffvTQS3POzv|J5k;pZb#UlyF2vlmo1*Zc{n` zQ|CuvQG*$9reulCbfyDQNB5n$a)aEaYs^3Tc-#KEV0WWphcp6roQtdm5CsI3^U_lE zJsuSUU`2GPH^=SdcTxR;1U`tPP(h_7ev#@2nf}$v`K>%NpnRn~vZ>F=Z0RN%wilA5 zSE|;SU!UCM?*FgwJOh|NUbO?USrOdtNPQ4M>9RkLjYMB>qzq_UpFYSq#Jy&x);PeE}by{U(I^k^h_uOJacOv*Y)^#`% zf9_U9;B6y77gczWiE7FszaQM}LDf}{w`}7Ah^Ryc7LE?RWNbtRWSRXXxEL`3giZ=S zNfWPp>?m^gQqv3ZT?dC z)drW+*WNc=?D}g7KH0g^pP#@J%(KwMRkf&y_HDOk+k<(Ug-D&; zIaNzg&7a$TsZ=25)s!s-G7eGTK6qRT2-!thRW7^~9=Wl@qa|>+r^SAc@?ggkTl?Rnw~70;Y$q8R^4TkS%us1CA@KP@{hUz=psJ&@Zs*AM759n_fziT7Ke7W>h zfXJyX`rhH>WaX0>q`)ztzj>@<7y&^x%JYL4E(7er0q$gr%tC#Z-J7PEoK>A z`Bo``Cz!5@L#(Ap)_)}ZhI6WA%Uc}7f)IyPiAaB33gA2oq+|(7-w_U_{EsxpEY6OW zdhq28MpbHF%hVNiB#Suul*^ANcwU{%h!+9l9jAjc{9~+&z3PfMnYg0C!>TMeGD{_? zUU2>_ia6|mc3k5mzaib!ag=;B!Sp?RI>a|QE2n*I_vZOT9SSf2pWECNlctfx>~;&F zaR+K3c~%ut6~7c2aJmk6eJQXt#z2jDOW3ww^bE~##~EQj%#r4CK@QNtFbrQ2eo|fo zQB%92x8XmB@kdhwXm{k=J))`Ejy_MuV%lI;~_vyBsdjp{f(5q%ZdE+zCF0oSj z)9zR#;-fk|&Ics|gK%0dgn>-c#F=&g3BQN(d@EJ(bP}L7-pFJ6CI+Yo|6~3WqL1Fl z`k7sE_AdgaGSH)4ePnZAdtMgxXxgv z3HwWoE4&aBE4|R!2z&iM@ATReIGbUzQ`>a~hQjNdV z!fg!H%uJ+uoTD{u53H5EBUbc>5(qG8zvBY3CWCe+KZ;JiIB+`dcE1~znpk(_chwJU z)QgqKjsY7MYkU8hgWl*N`BOnI7e#tK>?zTE&KxML=}Zi@26Cr0qUKn+hr6om$@mgA z-kbS;j7z@{_u&S^$VvMNf!b3?==YCWZ@%teV!zbR4zrIW@$n0|CAOxeXSJ8N^4wu} z9Xy2d5LnVbOf1Nrm@!Jx?9PU6i()W{v9^B^=SNzulL4>rBM~mm zMICcWAr=p1pP9b%sMPNdDnR-mYC-10ooX&a8L&9OcTyDp@pxN*PooDG2{w#%H;}Xv40t4dq7O&)x>&nj5#T-IQ1qA<_R zGbxD5+;i*AjTNZ1Xd$a_x5yy$1l{(6s>2KKs7o}UL`Rw*y=V~OQm%4aDjc_R2iy)5 zRBV3G@3Iy>SbfY`t594uF852~^VmyqP_E!<@BNmkmw1_d7KotAg>2vygm5D_j7g0m z(~eO9NTCj&&!_NkJ%ScmK;R6J)F5v=AbF6!b2t&f>~Sj$83_^>!45y3?Bbm(?SKACX=^+&u&>x2AYO{-vROuD;?ugMgTpOVx4~DJ9bN`I1RVw}Q{Tj^7IfUIvdC)! zRMfyfPj~S(T3oj6kz~l_m3!Yy&kiW%a*$`Mq~@RD z7Y&(b-0eS)OvzE!c0?vEGz*X<#(5VG|K0-|h!U>IDrQIc1wu+8frVm`WyEv90;(#@ zsr~|L7CH@FK9peU5`bMzZXBgWNRA1=4^$^I)QF_Z>je&@Wc)IH>JnmV;6XrShjJru z+G{a^kU=a~B!ddUF$%ac%mFzWgd)W4br<{t$wELN6}~MT{15`{dwDX1UcztFurHDA zuzKU4NBjdI`4#^&^XJo@nBs}|4gEdW-l!eFa`zMx0L|`7srd7ra0~7$gpSz$^9y~Qw1T)9p z?hl$E@)ZC0`ww`pGdtt#e}G~Zga9bqTmz>^G@zwLpVH7}wwEQkGRq^0DD#xxj);0c zBxtTPdL)2Ek(^Q_Tu3WlvB9~XP(R)X_MM4GYWAo|*F!<1vxQy-We?-WD)h=-@QDXV zTT|y-Z(q`T&y_=A`L0r_=t9MB{Sl=ekhP8!^>t$daF!98b`%c!rOj{W^A3zp%#m}q z0v$l0Qdou#fS!cN0m@Ks*ZxzfhlN>*b<@Xdpt+yTxjUX!5Z0P)EQ6^%W$#N6cN{~` zb{vv1s(hH-oO6`_4i^&9(GYbm+IoD41pq`;ZvDAgg9_Sh!wZ`m^OYD~XyK)-adsEP zrP40Dbyg()1k*LPQoFolzEG)<&908)SO12znZLW)6Xh#N(J+s}K9@DWD>3oz6PF*D z%Ut7FduLnk*F))6vm&|x3SB8DBj}s8qxu`fn>j6z)=-Yb#(wW_JCzA&iGZDUfE>2)`HDCx}y?*TGbI_(U_p8*pBid5F-s(k~nJXa@`@ z_CrM0*L~ZQyv_oU#TiWkJJgSOK|-*lX!~wl6cu!$@`64Wg#=eI; zq3l23f$Gju(^Jg5zxos6=_2VVRDL@Pua;WG_yAODKlg3+Fa9Gf>QxM@II;(+S{trxxGX9cq znmA71`8L8GFa_EWGGadHxFr(=r8xTW^u&l3e=3yM2FRdC2CbPl4GJFMq^tWip+rLB z`9@IA{U%o2xE)>Tk?l9ev9;ham6U5+Gh@IuG3h6(j65o2zBEQw`UTc`EF{y5Exslr zsER8MLV&zyXT3H%1Irra4QGrsu{2&UyzzLeu5h=vfD*kT`}n|@e^rWy1XY57Y(Bp$Kmxk(>#`D#Cn{%iye_93SSz;F%S*lO)6ys-Yh&1ymQ4)c)~{- z0PNUeKm!h*gT1H7*q#U=!^3)VjY0^WG%a1R*#8HHPbV`Fq0)uUmOg?|`7sbr#Lqzu zR)o+%Xl2k3GfqPyC^D>=iuL6T0^l)2GO*p)zMOAFL%z|hf*yPxfPRH^&6MVybYa(- z!73f)SaEH#j=nXTk&#r9UV^_%vZ;rxhk6pNvr+}pYk{<27>vd^mQLpfk zNxxTTo<*>UXZ8|+dL>K5g9L+YxGnil+nXuK;mrj{fB~cGpb)7?LKft^Pvx<3gYgW30Yup9vZq1YxG~c1qFv*_ z5&N^HU&WkAu{+v^$fUMP2m(CSJ~urOQb!F22?f&$K^EpK=}3Ajro>}mHY-Mfk>r72 z(DS2Txv>Z8V*;0=kH*`6wH6~!2mRRC0YfPH1Oc-eg-+0)S<3AdYR!F-?xFz+qa!k! zOQ&Xl@ABP!BG2a7x^sN7jZSxwzOri{E0+a)15|I)+(-*2m~tECFz3MtNi(nO+LXPHzL!vw;ZK=Mh%; zTeUsm9x*G0Y|ya?NK8@#vLMWMUmzOn@w9fP2+Y&$l*m6aX}3$4Th{O~AE_ z=YklBN!Lhvg+yAgPb(N|8c@b0HICS2RFtuEMb!Ffs_jpYkY$&`{#Sfw3u^ZiY>Y4I zGq;;l_xwU;i7tP*evDL}DRqma7GBYffSj+&XA2c+bA|G=&a5GlXB%}VRrbXmA*36X z760{$o+;peA6>O?Jln`uZU&>b*((Tp#nI>dH zRG*C)8St05SnK{sXRv51BNNN$Q~VDtiIA4rh5=nSRhIWB^waZe3YaRs`P7DC%1~}t zg&}6hp*B&^kR#kcX_D8dz10Z31hJm$I1~%>;m^=I&JT_d*OZmh4gTyZb)BusGUy$) zzwkcXqZuQj`H)vvvcyX~azk*r#2+D`qndKF<8hMLUy@{+S##S#O~>8s)XO*D`&-a> z`CYVccK67j+l_#W4xaT;4bm^ax*VC^{WZzAUec|_Uf@14H&efWSSflVKt}?UOXeD@ z#7A~^6R+a$s<_4M7Ei!T_Wu~*bzHC3it}zg@ikh_u2AyFVF_K4BbYPuv5H) zCr*pOEJp8NMe^D!4;(5(?x+rk*>%7I50 z)Dm4uABbc%^b3iUZxKH|6J`MY9XlnQSsTk@;60o{ZB48b>?>MPDkjPlO`eOPl<;&L z5Qc*mQ)291RZ0dmKQTyp_O^~wUyA)(uPlMx5h1Fw(u2A(p#g6vR#Lc6C#}=aZ05nl zMlay=%)_B~d%mpx5WBLr_OxPNab5UR?jP>ob~v9?qTSHG>|ryPF-7>gMi$O_E&dE9yIvEtSI)9sVFb%a9zEB2y$gOUSKlROI60QD z{6nzweiUD;L1j9xKoc9BqDV$XGtlW0DCf`Qv8myFwtPQqWJD_E&8`Lo;-kau9N;ZB~3pes{Z z6$9F7o%tE$fHl(Ug+|8$qTh9suqgIO^PP^yD_Zp@29sk-7+4Xgo6ZQE2UM@&F?Nqu z*Qz}8!wPiYK)qr?U%EA5)qJnE{36u1T&eFh^Y~`I`hYlYs@x{`f6@ua4*
1?x4 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 literal 0 HcmV?d00001 diff --git a/CONTENTS.md b/CONTENTS.md index 20adfea..4ca214f 100644 --- a/CONTENTS.md +++ b/CONTENTS.md @@ -240,3 +240,7 @@ If this is not possible, - [exercises ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/develop/09_mappings/01_exercises.ipynb) [](https://mybinder.org/v2/gh/webartifex/intro-to-python/develop?urlpath=lab/tree/09_mappings/01_exercises.ipynb) (Working with Nested Data) + - [content ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/develop/09_mappings/02_content.ipynb) + [](https://mybinder.org/v2/gh/webartifex/intro-to-python/develop?urlpath=lab/tree/09_mappings/02_content.ipynb) + (`**kwargs` in Function Definitions; + Memoization)