From 675dbc43b2fad74a6019493e65c5ba28680c0033 Mon Sep 17 00:00:00 2001 From: Alexander Hess Date: Wed, 20 Nov 2019 10:58:01 +0100 Subject: [PATCH 1/7] Adjust content overview in README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index d2f8d4b..4da6b2f 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ As such they can be viewed in a plain web browser: - [05 - Bits & Numbers](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/05_numbers.ipynb) - [06 - Bytes & Text](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/06_text.ipynb) - [07 - Sequential Data](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/07_sequences.ipynb) +- [08 - Mappings & Sets](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/08_mappings.ipynb) However, it is recommended that students **install Python and Jupyter locally** and run the code in the notebooks on their own. From fe138176058c9fbcb6fe85040421f46ceb267867 Mon Sep 17 00:00:00 2001 From: Alexander Hess Date: Wed, 20 Nov 2019 10:58:54 +0100 Subject: [PATCH 2/7] Add note for lecture in spring term 2020 --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4da6b2f..97c9120 100644 --- a/README.md +++ b/README.md @@ -146,6 +146,7 @@ This opens a new tab in your web browser just as above. Alexander Hess is a PhD student at the Chair of Logistics Management at the [WHU - Otto Beisheim School of Management](https://www.whu.edu) where he conducts research on urban delivery platforms and teaches an introductory -course on Python (cf., [course listing](https://vlv.whu.edu/campus/all/event.asp?objgguid=0xE57C2715B01B441AAFD3E79AA05CACCF&from=vvz&gguid=0x6A2B0ED5B2B949E69957A2099E7DE2F1&mode=own&tguid=0x3980A9BBC3BF4A638E977F2DC163F44B&lang=en)). +course on Python (cf., [Fall Term 2019](https://vlv.whu.edu/campus/all/event.asp?objgguid=0xE57C2715B01B441AAFD3E79AA05CACCF&from=vvz&gguid=0x6A2B0ED5B2B949E69957A2099E7DE2F1&mode=own&tguid=0x3980A9BBC3BF4A638E977F2DC163F44B&lang=en), +[Spring Term 2020](https://vlv.whu.edu/campus/all/event.asp?objgguid=0x3354F4C108FF4E959CDD692A325D9AFE&from=vvz&gguid=0x262E29795DD742CFBDE72B12B69CEFD6&mode=own&lang=en&tguid=0x2E4A7D1FF3C34AD08FF07685461781C9)). Connect him on [LinkedIn](https://www.linkedin.com/in/webartifex). From ca8fefdce19c731cc25e6fe498394a7991076a81 Mon Sep 17 00:00:00 2001 From: Alexander Hess Date: Wed, 20 Nov 2019 11:00:24 +0100 Subject: [PATCH 3/7] Streamline previous content --- 00_start_up.ipynb | 8 +- 01_elements.ipynb | 97 +- 01_elements_review_and_exercises.ipynb | 4 +- 02_functions.ipynb | 58 +- 02_functions_review_and_exercises.ipynb | 58 +- 03_conditionals.ipynb | 12 +- 03_conditionals_review_and_exercises.ipynb | 30 +- 04_iteration.ipynb | 160 +- 04_iteration_review_and_exercises.ipynb | 23 +- 05_numbers.ipynb | 456 ++- 05_numbers_review_and_exercises.ipynb | 22 +- 06_text.ipynb | 660 ++-- 07_sequences.ipynb | 3920 ++++++++++++++------ 13 files changed, 3595 insertions(+), 1913 deletions(-) diff --git a/00_start_up.ipynb b/00_start_up.ipynb index 97fe84b..9aed12d 100644 --- a/00_start_up.ipynb +++ b/00_start_up.ipynb @@ -585,7 +585,7 @@ " - replaced the infamous course on the [Scheme](https://groups.csail.mit.edu/mac/projects/scheme/) language (cf., [source](https://news.ycombinator.com/item?id=602307))\n", "- **[Google](https://www.google.com/)**\n", " - used the strategy **\"Python where we can, C++ where we must\"** from its early days on to stay flexible in a rapidly changing environment (cf., [source](https://stackoverflow.com/questions/2560310/heavy-usage-of-python-at-google))\n", - " - the very first web-crawler was written in **Java and so difficult to maintain** that it was **re-written in Python** right away (cf., [source](https://www.amazon.com/Plex-Google-Thinks-Works-Shapes/dp/1416596585/ref=sr_1_1?ie=UTF8&qid=1539101827&sr=8-1&keywords=in+the+plex))\n", + " - the very first web-crawler was written in **Java and so difficult to maintain** that it was **rewritten in Python** right away (cf., [source](https://www.amazon.com/Plex-Google-Thinks-Works-Shapes/dp/1416596585/ref=sr_1_1?ie=UTF8&qid=1539101827&sr=8-1&keywords=in+the+plex))\n", " - Python and C++, Java, and Go are the only four server-side languages to be deployed to production\n", " - Guido van Rossom was hired by Google from 2005 to 2012 to advance the language there\n", "- **[NASA](https://www.nasa.gov/)** open-sources many of its projects, often written in Python and regarding analyses with big data (cf., [source](https://code.nasa.gov/language/python/))\n", @@ -605,7 +605,7 @@ } }, "source": [ - "As images tell more than words, here are two plots of popular languages' \"market shares\" based on the number of questions asked on [Stack Overflow](https://stackoverflow.blog/2017/09/06/incredible-growth-python/), the most relevant platform for answering programming related questions: As of late 2017, Python surpassed [Java](https://www.java.com/en/), heavily used in big corporates, and [JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript), the \"language of the internet\" that does everything in web browsers, in popularity. Two blog posts from \"technical\" people explain this in more depth to the layman: [Stack Overflow](https://stackoverflow.blog/2017/09/14/python-growing-quickly/) and [DataCamp](https://www.datacamp.com/community/blog/python-scientific-computing-case)." + "As images tell more than words, here are two plots of popular languages' \"market shares\" based on the number of questions asked on [Stack Overflow](https://stackoverflow.blog/2017/09/06/incredible-growth-python/), the most relevant platform for answering programming-related questions: As of late 2017, Python surpassed [Java](https://www.java.com/en/), heavily used in big corporates, and [JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript), the \"language of the internet\" that does everything in web browsers, in popularity. Two blog posts from \"technical\" people explain this in more depth to the layman: [Stack Overflow](https://stackoverflow.blog/2017/09/14/python-growing-quickly/) and [DataCamp](https://www.datacamp.com/community/blog/python-scientific-computing-case)." ] }, { @@ -715,7 +715,7 @@ "- **Managers**: People that need to organize things and command others (like a \"boss\"). Their schedule is usually organized by the hour or even 30-minute intervals.\n", "- **Makers**: People that create things (like programmers, artists, or writers). Such people think in half days or full days.\n", "\n", - "Have you ever wondered why so many tech people work during nights and sleep at \"weird\" times? The reason is that many programming related tasks require a \"flow\" state in one's mind that is hard to achieve when one can get interrupted, even if it is only for one short question. Graham describes that only knowing that one has an appointment in three hours can cause a programmer to not get into a flow state.\n", + "Have you ever wondered why so many tech people work during nights and sleep at \"weird\" times? The reason is that many programming-related tasks require a \"flow\" state in one's mind that is hard to achieve when one can get interrupted, even if it is only for one short question. Graham describes that only knowing that one has an appointment in three hours can cause a programmer to not get into a flow state.\n", "\n", "As a result, do not set aside a certain amount of time for learning something but rather plan in an **entire evening** or a **rainy Sunday** where you can work on a problem in an **open end** setting. And do not be surprised anymore to hear \"I looked at it over the weekend\" from a programmer." ] @@ -795,7 +795,7 @@ " 5. [Bits & Numbers](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/05_numbers.ipynb)\n", " 6. [Bytes & Text](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/06_text.ipynb)\n", " 7. [Sequential Data](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/07_sequences.ipynb)\n", - " 8. Mappings & Sets\n", + " 8. [Mappings & Sets](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/08_mappings.ipynb)\n", "- How can we create custom data types?\n", " 9. Object-Orientation" ] diff --git a/01_elements.ipynb b/01_elements.ipynb index fed6458..07d6774 100644 --- a/01_elements.ipynb +++ b/01_elements.ipynb @@ -48,7 +48,7 @@ "source": [ "As our introductory example, we want to calculate the *average* of all *evens* in a **list** of numbers: `[7, 11, 8, 5, 3, 12, 2, 6, 9, 10, 1, 4]`.\n", "\n", - "While we could try to develop an [analytical solution](https://math.stackexchange.com/questions/935405/what-s-the-difference-between-analytical-and-numerical-approaches-to-problems/935446#935446) (i.e., derive some equation with \"pen and paper\"), we solve the task programmatically instead.\n", + "While we are used to finding an [analytical solution](https://math.stackexchange.com/questions/935405/what-s-the-difference-between-analytical-and-numerical-approaches-to-problems/935446#935446) in math (i.e., derive some equation with \"pen and paper\"), we solve this task *programmatically* instead.\n", "\n", "We start by creating a list called `numbers` that holds all the individual numbers between **brackets** `[` and `]`." ] @@ -114,7 +114,7 @@ "\n", "Intuitively, the line `for number in numbers` describes a \"loop\" over all the numbers in the `numbers` list, one at a time.\n", "\n", - "The `if number % 2 == 0` may look disturbing at first sight. Both the `%` and `==` must have an unintuitive meaning here. Luckily, the **comment** in the same line after the `#` symbol has the answer: The program only does something if the current `number` is even.\n", + "The `if number % 2 == 0` may look confusing at first sight. Both `%` and `==` must have an unintuitive meaning here. Luckily, the **comment** in the same line after the `#` symbol has the answer: The program does something only for an even `number`.\n", "\n", "In particular, it increases `count` by `1` and adds the current `number` onto the [running](https://en.wikipedia.org/wiki/Running_total) `total`. Both `count` and `number` are initially set to `0` and the single `=` symbol reads as \"... is *set* equal to ...\". It could not indicate a mathematical equation as, for example, `count` is generally not equal to `count + 1`.\n", "\n", @@ -200,7 +200,7 @@ "source": [ "Note how only two of the previous four code cells generate an **output** while two remained \"silent\" (i.e., there is no \"**Out[...]**\" after running the cell).\n", "\n", - "By default, Jupyter notebooks show the value of a cell's last **expression**. This output can be suppressed by ending the last line with a semicolon `;`." + "By default, Jupyter notebooks show the value of the **expression** in the last line of a code cell only. This output can be suppressed by ending the last line with a semicolon `;`." ] }, { @@ -249,7 +249,7 @@ } }, "source": [ - "To visualize something before the end of the cell, we use the built-in [print()](https://docs.python.org/3/library/functions.html#print) **function**. Here, the parentheses `()` indicate that we execute code defined somewhere else." + "To visualize something before the end of the cell, we use the built-in [print()](https://docs.python.org/3/library/functions.html#print) **function**. Here, the parentheses `()` indicate that we execute code written somewhere else." ] }, { @@ -331,9 +331,9 @@ "\n", "The arithmetic operators either \"operate\" with the number immediately following them (= **unary** operators; e.g., negation) or \"process\" the two numbers \"around\" them (= **binary** operators; e.g., addition).\n", "\n", - "By definition, operators have **no** permanent **side effects** in the computer's memory. Although the code cells in this section do indeed create *new* numbers in memory (e.g., `77 + 13` creates `90`), they are immediately \"forgotten\" as they are not stored in a **variable** like `numbers` or `average` above. We continue this thought further below when we compare **expressions** with **statements**.\n", + "By definition, operators have **no** permanent **side effects** in the computer's memory. Although the code cells in this section do indeed create *new* numbers in memory (e.g., `77 + 13` creates `90`), they are immediately \"forgotten\" as they are not stored in a **variable** like `numbers` or `average` above. We develop this thought further at the end of this chapter when we compare **expressions** with **statements**.\n", "\n", - "Let's see some examples of operators. We start with the binary `+` and the `-` operators for addition and subtraction. Binary operators are designed to resemble what mathematicians call [infix notation](https://en.wikipedia.org/wiki/Infix_notation) and have the expected meaning." + "Let's see some examples of operators. We start with the binary `+` and the `-` operators for addition and subtraction. Binary operators mimic what mathematicians call [infix notation](https://en.wikipedia.org/wiki/Infix_notation) and have the expected meaning." ] }, { @@ -392,7 +392,7 @@ } }, "source": [ - "The `-` operator is used as a unary operator as well. Then it just flips the sign of a number." + "The `-` operator may be used as a unary operator as well. Then it just flips the sign of a number." ] }, { @@ -617,7 +617,9 @@ "source": [ "The remainder is `0` *only if* a number is *divisible* by another.\n", "\n", - "A popular convention in both, computer science and mathematics, is to abbreviate \"only if\" as **iff**, which is short for \"**[if and only if](https://en.wikipedia.org/wiki/If_and_only_if)**.\" The iff means that a remainder of `0` implies that a number is divisible by another but also that a number divisible by another implies a remainder of `0`. The implication goes in *both* directions!" + "A popular convention in both, computer science and mathematics, is to abbreviate \"only if\" as **iff**, which is short for \"**[if and only if](https://en.wikipedia.org/wiki/If_and_only_if)**.\" The iff means that a remainder of `0` implies that a number is divisible by another but also that a number divisible by another implies a remainder of `0`. The implication goes in *both* directions!\n", + "\n", + "So, `49` is divisible by `7`." ] }, { @@ -652,7 +654,7 @@ } }, "source": [ - "Modulo division is also useful if we, for example, need to get the last couple of digits of a large integer." + "Modulo division is also useful if we want to extract the last couple of digits in a large integer." ] }, { @@ -711,7 +713,7 @@ } }, "source": [ - "The [divmod()](https://docs.python.org/3/library/functions.html#divmod) built-in function combines the integer and modulo divisions into one operation. However, this is not an operator but a function. Also, [divmod()](https://docs.python.org/3/library/functions.html#divmod) returns a \"pair\" of integers and not just one object." + "The built-in [divmod()](https://docs.python.org/3/library/functions.html#divmod) function combines the integer and modulo divisions into one operation. However, this is not an operator but a function. Also, [divmod()](https://docs.python.org/3/library/functions.html#divmod) returns a \"pair\" of integers and not just one." ] }, { @@ -781,7 +783,7 @@ } }, "source": [ - "The standard [order of precedence](https://docs.python.org/3/reference/expressions.html#operator-precedence) from mathematics applies (i.e., \"PEMDAS\" rule)." + "The standard [order of precedence](https://docs.python.org/3/reference/expressions.html#operator-precedence) from mathematics applies (i.e., [PEMDAS](http://mathworld.wolfram.com/PEMDAS.html) rule) when several operators are combined." ] }, { @@ -988,7 +990,7 @@ { "data": { "text/plain": [ - "140706351197104" + "140398796190992" ] }, "execution_count": 28, @@ -1012,7 +1014,7 @@ { "data": { "text/plain": [ - "140706351380256" + "140398796375888" ] }, "execution_count": 29, @@ -1036,7 +1038,7 @@ { "data": { "text/plain": [ - "140706351003760" + "140398796056176" ] }, "execution_count": 30, @@ -1056,7 +1058,7 @@ } }, "source": [ - "These addresses are *not* meaningful for anything other than checking if two variables **point** at the same object. Let's create a second variable `d` and also set it to `789`." + "These addresses are *not* meaningful for anything other than checking if two variables reference the *same* object. Let's create a second variable `d` and also set it to `789`." ] }, { @@ -1507,7 +1509,7 @@ } }, "source": [ - "In this book, we follow the convention of creating strings with **double quotes** `\"` instead of the **single quotes** `'` to which Python defaults in its literal output for `str` objects. Both types of quotes may be used interchangeably." + "In this book, we follow the convention of creating strings with **double quotes** `\"` instead of the **single quotes** `'` to which Python defaults in its *literal* notation for `str` objects. Both types of quotes may be used interchangeably." ] }, { @@ -1825,7 +1827,7 @@ "source": [ "Thus, adhering to just syntax rules is *never* enough. Over time, **best practices** and **style guides** were created to make it less likely for a developer to mess up a program and also to allow \"onboarding\" him as a contributor to an established code base, often called **legacy code**, faster. These rules are not enforced by Python itself: Badly styled code still runs. At the very least, Python programs should be styled according to [PEP 8](https://www.python.org/dev/peps/pep-0008/) and documented \"inline\" (i.e., in the code itself) according to [PEP 257](https://www.python.org/dev/peps/pep-0257/).\n", "\n", - "An easier to read version of PEP 8 is [here](https://pep8.org/). The video below features a well known \"[Pythonista](https://en.wiktionary.org/wiki/Pythonista)\" talking about the importance of code style." + "An easier to read version of PEP 8 is [here](https://pep8.org/). The video below features a well known **[Pythonista](https://en.wiktionary.org/wiki/Pythonista)** talking about the importance of code style." ] }, { @@ -1852,7 +1854,7 @@ " " ], "text/plain": [ - "" + "" ] }, "execution_count": 52, @@ -1873,7 +1875,7 @@ } }, "source": [ - "For example, while the above code to calculate the average of the even numbers from `1` through `12` is correct, a Pythonista would re-write it in a more \"Pythonic\" way and use the [sum()](https://docs.python.org/3/library/functions.html#sum) and [len()](https://docs.python.org/3/library/functions.html#len) (= \"length\") [built-in functions](https://docs.python.org/3/library/functions.html) (cf., [Chapter 2](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/02_functions.ipynb#Built-in-Functions)) as well as a so-called **list comprehension** (cf., [Chapter 7](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/07_sequences.ipynb#List-Comprehensions)). Pythonic code runs faster in many cases and is less error-prone." + "For example, while the above code to calculate the average of the even numbers in `[7, 11, 8, 5, 3, 12, 2, 6, 9, 10, 1, 4]` is correct, a Pythonista would rewrite it in a more \"Pythonic\" way and use the [sum()](https://docs.python.org/3/library/functions.html#sum) and [len()](https://docs.python.org/3/library/functions.html#len) (= \"length\") [built-in functions](https://docs.python.org/3/library/functions.html) (cf., [Chapter 2](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/02_functions.ipynb#Built-in-Functions)) as well as a so-called **list comprehension** (cf., [Chapter 7](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/07_sequences.ipynb#List-Comprehensions)). Pythonic code runs faster in many cases and is less error-prone." ] }, { @@ -2075,7 +2077,7 @@ "\n", "At the same time, for a beginner's course, it is often easier to code linearly.\n", "\n", - "In real data science projects, one would probably employ a mixed approach and put re-usable code into so-called Python modules (i.e., *.py* files; cf., [Chapter 2](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/02_functions.ipynb#Local-Modules-and-Packages)) and then use Jupyter notebooks to build up a linear report or storyline for a business argument to be made." + "In real data science projects, one would probably employ a mixed approach and put reusable code into so-called Python modules (i.e., *.py* files; cf., [Chapter 2](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/02_functions.ipynb#Local-Modules-and-Packages)) and then use Jupyter notebooks to build up a linear report or storyline for a business argument to be made." ] }, { @@ -2097,9 +2099,9 @@ } }, "source": [ - "**Variables** are created with the **[assignment statement](https://docs.python.org/3/reference/simple_stmts.html#assignment-statements)** `=`, which is *not* an operator, mainly because of its side effect of making a **[name](https://docs.python.org/3/reference/lexical_analysis.html#identifiers)** point to an object in memory.\n", + "**Variables** are created with the **[assignment statement](https://docs.python.org/3/reference/simple_stmts.html#assignment-statements)** `=`, which is *not* an operator, mainly because of its side effect of making a **[name](https://docs.python.org/3/reference/lexical_analysis.html#identifiers)** reference an object in memory.\n", "\n", - "We read the terms **variable**, **name**, and **identifier** used interchangebly in many Python-related texts. In this book, we adopt the following convention: First, we treat *name* and *identifier* as perfect synonyms but only use the term *name* in the text for clarity. Second, whereas *name* only refers to a string of letters, numbers, and some other symbols, a *variable* refers to the combination of a *name* and a *pointer* to some object in memory." + "We read the terms **variable**, **name**, and **identifier** used interchangebly in many Python-related texts. In this book, we adopt the following convention: First, we treat *name* and *identifier* as perfect synonyms but only use the term *name* in the text for clarity. Second, whereas *name* only refers to a string of letters, numbers, and some other symbols, a *variable* means the combination of a *name* and a *reference* to an object in memory." ] }, { @@ -2124,7 +2126,7 @@ } }, "source": [ - "When referenced, a variable evaluates to the value of the object it points to. Colloquially, we could say that `a` evaluates to `20.0`, but this would not be an accurate description of what is going on in memory. We see some more colloquialisms in this section but should always relate this to what Python actually does in memory." + "When used as a *literal*, a variable evaluates to the value of the object it references. Colloquially, we could say that `a` evaluates to `20.0`, but this would not be an accurate description of what is going on in memory. We see some more colloquialisms in this section but should always relate this to what Python actually does in memory." ] }, { @@ -2172,7 +2174,7 @@ }, "outputs": [], "source": [ - "a = 20 # this makes a point to an object of a different type" + "a = 20 # this makes a reference an object of a different type" ] }, { @@ -2281,7 +2283,7 @@ } }, "source": [ - "Variables are **[de-referenced](https://docs.python.org/3/reference/simple_stmts.html#the-del-statement)** (i.e., \"deleted\") with the `del` statement. It does *not* delete the object to which a variable points to but merely removes the variable's name from the \"global list of all names.\"" + "Variables are **[dereferenced](https://docs.python.org/3/reference/simple_stmts.html#the-del-statement)** (i.e., \"deleted\") with the `del` statement. It does *not* delete the object a variable references but merely removes the variable's name from the \"global list of all names.\"" ] }, { @@ -2594,9 +2596,9 @@ } }, "source": [ - "It is *crucial* to understand that *several* variables may point to the *same* object in memory. Not having this in mind may lead to many hard to track down bugs.\n", + "It is *crucial* to understand that *several* variables may reference the *same* object in memory. Not having this in mind may lead to many hard to track down bugs.\n", "\n", - "Make `b` point to whatever object `a` is pointing to." + "Make `b` reference whatever object `a` is referencing." ] }, { @@ -2670,7 +2672,7 @@ "source": [ "For \"simple\" types like `int` or `float` this never causes troubles.\n", "\n", - "Let's \"change the value\" of `a`. To be precise, let's create a *new* `123` object and make `a` point to it." + "Let's \"change the value\" of `a`. To be precise, let's create a *new* `123` object and make `a` reference it." ] }, { @@ -2718,7 +2720,7 @@ } }, "source": [ - "`b` \"is still the same\" as before. To be precise, `b` still points to the *same object* as before." + "`b` \"is still the same\" as before. To be precise, `b` still references the *same object* as before." ] }, { @@ -2753,7 +2755,7 @@ } }, "source": [ - "However, if a variable points to an object of a more \"complex\" type (e.g., `list`), \"weird\" things happen." + "However, if a variable references an object of a more \"complex\" type (e.g., `list`), \"weird\" things happen." ] }, { @@ -2816,7 +2818,7 @@ "source": [ "Let's change the first element of `x`.\n", "\n", - "[Chapter 7](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/07_sequences.ipynb#The-list-Type) discusses lists in more depth. For now, let's view a `list` object as some sort of **container** that holds an arbitrary number of pointers to other objects and treat the brackets `[]` attached to it as just another operator, called the **indexing operator**. `x[0]` instructs Python to first follow the pointer from the global list of all names to the `x` object. Then, it follows the first pointer it finds there to the `1` object. The indexing operator must be an operator as we merely read the first element and do not change anything in memory.\n", + "[Chapter 7](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/07_sequences.ipynb#The-list-Type) discusses lists in more depth. For now, let's view a `list` object as some sort of **container** that holds an arbitrary number of references to other objects and treat the brackets `[]` attached to it as just another operator, called the **indexing operator**. `x[0]` instructs Python to first follow the reference from the global list of all names to the `x` object. Then, it follows the first reference it finds there to the `1` object. The indexing operator must be an operator as we merely read the first element and do not change anything in memory.\n", "\n", "Note how Python **begins counting at 0**. This is not the case for many other languages, for example, [MATLAB](https://en.wikipedia.org/wiki/MATLAB), [R](https://en.wikipedia.org/wiki/R_%28programming_language%29), or [Stata](https://en.wikipedia.org/wiki/Stata). To understand why this makes sense, see this short [note](https://www.cs.utexas.edu/users/EWD/transcriptions/EWD08xx/EWD831.html) by one of the all-time greats in computer science, the late [Edsger Dijkstra](https://en.wikipedia.org/wiki/Edsger_W._Dijkstra)." ] @@ -2853,7 +2855,7 @@ } }, "source": [ - "To change the first entry in the list, we use the assignment statement `=` again. Here, this does *not* create a *new* variable (or overwrite an existing one) but only changes the object to which the first pointer in the `x` list points to. As we only change parts of the `x` object, we say that we **mutate** (i.e., \"change\") its **state**. To use the bag analogy from above, we keep the same bag but \"flip\" some of the $0$s into $1$s and some of the $1$s into $0$s." + "To change the first entry in the list, we use the assignment statement `=` again. Here, this does *not* create a *new* variable, or overwrite an existing one, but only changes the object which the first element in `x` referenced. As we only change parts of the `x` object, we say that we **mutate** (i.e., \"change\") its **state**. To use the bag analogy from above, we keep the same bag but \"flip\" some of the $0$s into $1$s and some of the $1$s into $0$s." ] }, { @@ -2901,7 +2903,7 @@ } }, "source": [ - "The changes made to the object `x` is pointing to are also seen through the `y` variable!" + "The changes made to the object `x` is referencing can also be seen through the `y` variable!" ] }, { @@ -2938,11 +2940,11 @@ "source": [ "The illustrated difference in behavior has to do with the fact that integers and floats are **immutable** types while lists are **mutable**.\n", "\n", - "In the first case, an object cannot be changed \"in place\" once it is created in memory. When we assigned `123` to the already existing `a`, we did not change the $0$s and $1$s in the object `a` pointed to before the assignment but created a new integer object and made `a` point to it while the `b` variable is *not* affected.\n", + "In the first case, an object cannot be changed \"in place\" once it is created in memory. When we assigned `123` to the already existing `a`, we did not change the $0$s and $1$s in the object `a` referenced before the assignment but created a new integer object and made `a` reference it while the `b` variable is *not* affected.\n", "\n", - "In the second case, `x[0] = 99` creates a *new* integer object `99` and merely changes the first pointer in the `x` list.\n", + "In the second case, `x[0] = 99` creates a *new* integer object `99` and merely changes the first reference in the `x` list.\n", "\n", - "In general, the assignment statement creates a new name and makes it point to whatever object is on the right-hand side *iff* the left-hand side is a *pure* name (i.e., it contains no operators like the indexing operator in the example). Otherwise, it *mutates* an already existing object. And, we always must expect that the latter might have more than one variable pointing to it.\n", + "In general, the assignment statement creates a new name and makes it reference whatever object is on the right-hand side *iff* the left-hand side is a *pure* name (i.e., it contains no operators like the indexing operator in the example). Otherwise, it *mutates* an already existing object. And, we always must expect that the latter might have more than one variable referencing it.\n", "\n", "Visualizing what is going on in the memory with a tool like [PythonTutor](http://pythontutor.com/visualize.html#code=x%20%3D%20%5B1,%202,%203%5D%0Ay%20%3D%20x%0Ax%5B0%5D%20%3D%2099%0Aprint%28y%5B0%5D%29&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false) might be helpful for a beginner." ] @@ -3226,7 +3228,7 @@ " " ], "text/plain": [ - "" + "" ] }, "execution_count": 95, @@ -3258,7 +3260,7 @@ } }, "source": [ - "An **[expression](https://docs.python.org/3/reference/expressions.html)** is any syntactically correct **combination** of **variables** and **literals** with **operators**.\n", + "An **[expression](https://docs.python.org/3/reference/expressions.html)** is any syntactically correct *combination* of *variables* and *literals* with *operators*.\n", "\n", "In simple words, anything that may be used on the right-hand side of an assignment statement without creating a `SyntaxError` is an expression.\n", "\n", @@ -3645,7 +3647,7 @@ "source": [ "We use the `#` symbol to write comments in plain English right into the code.\n", "\n", - "As a good practice, comments should not describe *what* happens (this should be evident by reading the code; otherwise, it is most likely badly written code) but *why* something happens.\n", + "As a good practice, comments should *not* describe *what* happens (this should be evident by reading the code; otherwise, it is most likely badly written code) but *why* something happens.\n", "\n", "Comments may be added either at the end of a line of code, by convention separated with two spaces, or on a line on their own." ] @@ -3732,9 +3734,9 @@ "\n", "\n", "- input (examples)\n", - " - (numeric) data from a CSV file\n", + " - data from a CSV file\n", " - text entered on a command line\n", - " - (relational) data obtained from a database\n", + " - data obtained from a database\n", " - etc.\n", "\n", "\n", @@ -3749,20 +3751,20 @@ " - distinct and well-contained areas/parts of the memory that hold the actual data\n", " - the concept by which Python manages the memory for us\n", " - can be classified into objects of the same **type** (i.e., same abstract \"structure\" but different concrete data)\n", - " - built-in objects (incl. **literals**) vs. user-defined objects (cf., Chapter 8)\n", + " - built-in objects (incl. **literals**) vs. user-defined objects (cf., Chapter 9)\n", " - e.g., `1`, `1.0`, and `\"one\"` are three different objects of distinct types that are also literals (i.e., by the way we type them into the command line Python knows what the value and type are)\n", "\n", "\n", "- variables\n", " - storage of intermediate **state**\n", - " - **names** that point to **objects** in **memory**\n", - " - e.g., `x = 1` creates the variable `x` that points to the object `1`\n", + " - are **names** referencing **objects** in **memory**\n", + " - e.g., `x = 1` creates the variable `x` that references the object `1`\n", "\n", "\n", "- operators\n", " - special built-in symbols that perform operations with objects in memory\n", " - usually, operate with one or two objects\n", - " - e.g., addition `+`, subtraction `-`, multiplication `*`, and division `/` all take two objects whereas the negation `-` only takes one\n", + " - e.g., addition `+`, subtraction `-`, multiplication `*`, and division `/` all take two objects, whereas the negation `-` only takes one\n", "\n", "\n", "- expressions\n", @@ -3774,9 +3776,9 @@ "\n", "- statements\n", " - instructions that **\"do\" something** and **have side effects** in memory\n", - " - re-map names to different objects and *change* the state of the program\n", + " - (re-)assign names to (different) objects, *change* an existing object *in place*, or, more conceptually, *change* the state of the program\n", " - usually, work with expressions\n", - " - e.g., the assignment statement `=` makes a name point to an object\n", + " - e.g., the assignment statement `=` makes a name reference an object\n", "\n", "\n", "- comments\n", @@ -3788,6 +3790,7 @@ " - named sequences of instructions\n", " - the smaller parts in a larger program\n", " - make a program more modular and thus easier to understand\n", + " - include [built-in functions](https://docs.python.org/3/library/functions.html) like [print()](https://docs.python.org/3/library/functions.html#print), [sum()](https://docs.python.org/3/library/functions.html#sum), or [len()](https://docs.python.org/3/library/functions.html#len)\n", "\n", "\n", "- flow control (cf., [Chapter 3](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/03_conditionals.ipynb))\n", diff --git a/01_elements_review_and_exercises.ipynb b/01_elements_review_and_exercises.ipynb index 965b8be..21ba4c5 100644 --- a/01_elements_review_and_exercises.ipynb +++ b/01_elements_review_and_exercises.ipynb @@ -138,7 +138,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "**Q7**: \"**dunder**\" refers to a group of Australian (\"down under\") geeks that work on core Python." + "**Q7**: \"**dunder**\" refers to a group of Australian (i.e., \"down under\") geeks that work on core Python." ] }, { @@ -166,7 +166,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "**Q9**: When NASA famously converted some measurements to the wrong unit and lost a Mars satellite in 1999 ([source](https://www.wired.com/2010/11/1110mars-climate-observer-report/)), this is an example of a so-called **runtime error**." + "**Q9**: When NASA famously converted some measurements to the wrong unit and lost a Mars satellite in 1999 (cf., [source](https://www.wired.com/2010/11/1110mars-climate-observer-report/)), this is an example of a so-called **runtime error**." ] }, { diff --git a/02_functions.ipynb b/02_functions.ipynb index 897f8a7..39d7084 100644 --- a/02_functions.ipynb +++ b/02_functions.ipynb @@ -19,9 +19,9 @@ } }, "source": [ - "In [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb#Example:-Averaging-Even-Numbers), we typed the code to calculate the average of the even numbers in a list into several code cells. Then, we executed them one after another. We had no way of **re-using** the code except for either re-executing the cells or copying and pasting their contents into other cells. And, whenever we find ourselves doing repetitive manual work, we can be sure that there must be a way of automating what we are doing.\n", + "In [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb#Example:-Averaging-Even-Numbers), we typed the code to calculate the average of the even numbers in a list into several code cells. Then, we executed them one after another. We had no way of **reusing** the code except for either executing cells multiple times or copying and pasting their contents into other cells. And, whenever we find ourselves doing repetitive manual work, we can be sure that there must be a way of automating what we are doing.\n", "\n", - "At the same time, we executed built-in functions (e.g., [print()](https://docs.python.org/3/library/functions.html#print), [sum()](https://docs.python.org/3/library/functions.html#sum), [len()](https://docs.python.org/3/library/functions.html#len), [id()](https://docs.python.org/3/library/functions.html#id), or [type()](https://docs.python.org/3/library/functions.html#type)) that obviously must be re-using the same parts inside core Python every time we use them.\n", + "At the same time, we executed built-in functions (e.g., [print()](https://docs.python.org/3/library/functions.html#print), [sum()](https://docs.python.org/3/library/functions.html#sum), [len()](https://docs.python.org/3/library/functions.html#len), [id()](https://docs.python.org/3/library/functions.html#id), or [type()](https://docs.python.org/3/library/functions.html#type)) that obviously must be reusing the same parts inside core Python every time we use them.\n", "\n", "This chapter shows how Python offers language constructs that let us **define** functions ourselves that we may then **call** just like the built-in ones." ] @@ -45,7 +45,7 @@ } }, "source": [ - "So-called **[user-defined functions](https://docs.python.org/3/reference/compound_stmts.html#function-definitions)** may be created with the `def` statement. To extend an already familiar example, we re-use the introductory example from [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb#Best-Practices) in its final Pythonic version and transform it into the function `average_evens()` below. \n", + "So-called **[user-defined functions](https://docs.python.org/3/reference/compound_stmts.html#function-definitions)** may be created with the `def` statement. To extend an already familiar example, we reuse the introductory example from [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb#Best-Practices) in its final Pythonic version and transform it into the function `average_evens()` below. \n", "\n", "A function's **name** must be chosen according to the same naming rules as ordinary variables as Python manages function names like variables. In this book, we further adopt the convention of ending function names with parentheses `()` in text cells for faster comprehension when reading (i.e., `average_evens()` vs. `average_evens`). These are *not* part of the name but must always be written out in the `def` statement for syntactic reasons.\n", "\n", @@ -142,7 +142,7 @@ { "data": { "text/plain": [ - "139646582981088" + "140384699277792" ] }, "execution_count": 3, @@ -407,7 +407,7 @@ } }, "source": [ - "Notice how the parameters listed in a function's definition (i.e., `numbers`) and variables created inside it during execution (i.e., `evens` and `average`) are **local** to that function. That means they only point to an object in memory *while* the function is being executed and de-referenced immediately when the function returns. We say they **go out of scope** once the function terminates." + "Notice how the parameters listed in a function's definition (i.e., `numbers`) and variables created inside it during execution (i.e., `evens` and `average`) are **local** to that function. That means they only reference an object in memory *while* the function is being executed and de-referenced immediately when the function returns. We say they **go out of scope** once the function terminates." ] }, { @@ -493,7 +493,7 @@ } }, "source": [ - "[PythonTutor](http://pythontutor.com/visualize.html#code=nums%20%3D%20%5B1,%202,%203,%204,%205,%206,%207,%208,%209,%2010,%2011,%2012%5D%0A%0Adef%20average_evens%28numbers%29%3A%0A%20%20%20%20evens%20%3D%20%5Bn%20for%20n%20in%20numbers%20if%20n%20%25%202%20%3D%3D%200%5D%0A%20%20%20%20average%20%3D%20sum%28evens%29%20/%20len%28evens%29%0A%20%20%20%20return%20average%0A%0Arv%20%3D%20average_evens%28nums%29&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false) visualizes what happens in memory: To be precise, in the exact moment when the function call is initiated and `nums` passed in as the `numbers` argument, there are *two* pointers to the *same* `list` object (cf., steps 4-5 in the visualization). We also see how Python creates a *new* **frame** that holds the function's local scope (i.e., \"internal names\") in addition to the **global** frame. Frames are nothing but [namespaces](https://en.wikipedia.org/wiki/Namespace) to *isolate* the names of different **scopes** from each other. The list comprehension `[n for n in numbers if n % 2 == 0]` constitutes yet another frame that is in scope as the `list` object assigned to `evens` is *being* created (cf., steps 6-20). When the function returns, only the global frame is left (cf., last step)." + "[PythonTutor](http://pythontutor.com/visualize.html#code=nums%20%3D%20%5B1,%202,%203,%204,%205,%206,%207,%208,%209,%2010,%2011,%2012%5D%0A%0Adef%20average_evens%28numbers%29%3A%0A%20%20%20%20evens%20%3D%20%5Bn%20for%20n%20in%20numbers%20if%20n%20%25%202%20%3D%3D%200%5D%0A%20%20%20%20average%20%3D%20sum%28evens%29%20/%20len%28evens%29%0A%20%20%20%20return%20average%0A%0Arv%20%3D%20average_evens%28nums%29&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false) visualizes what happens in memory: To be precise, in the exact moment when the function call is initiated and `nums` passed in as the `numbers` argument, there are *two* references to the *same* `list` object (cf., steps 4-5 in the visualization). We also see how Python creates a *new* **frame** that holds the function's local scope (i.e., \"internal names\") in addition to the **global** frame. Frames are nothing but [namespaces](https://en.wikipedia.org/wiki/Namespace) to *isolate* the names of different **scopes** from each other. The list comprehension `[n for n in numbers if n % 2 == 0]` constitutes yet another frame that is in scope as the `list` object assigned to `evens` is *being* created (cf., steps 6-20). When the function returns, only the global frame is left (cf., last step)." ] }, { @@ -655,9 +655,9 @@ } }, "source": [ - "[PythonTutor](http://pythontutor.com/visualize.html#code=nums%20%3D%20%5B1,%202,%203,%204,%205,%206,%207,%208,%209,%2010,%2011,%2012%5D%0A%0Adef%20average_wrong%28numbers%29%3A%0A%20%20%20%20evens%20%3D%20%5Bn%20for%20n%20in%20nums%20if%20n%20%25%202%20%3D%3D%200%5D%0A%20%20%20%20average%20%3D%20sum%28evens%29%20/%20len%28evens%29%0A%20%20%20%20return%20average%0A%0Arv%20%3D%20average_wrong%28%5B123,%20456,%20789%5D%29&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false) is again helpful at visualizing the error interactively: Creating the `list` object `evens` eventually points to takes *16* computational steps, namely two for managing the list comprehension, one for setting up an empty `list` object, *twelve* for filling it with elements derived from `nums` in the global scope (i.e., that is the error), and one to make `evens` point at it (cf., steps 6-21).\n", + "[PythonTutor](http://pythontutor.com/visualize.html#code=nums%20%3D%20%5B1,%202,%203,%204,%205,%206,%207,%208,%209,%2010,%2011,%2012%5D%0A%0Adef%20average_wrong%28numbers%29%3A%0A%20%20%20%20evens%20%3D%20%5Bn%20for%20n%20in%20nums%20if%20n%20%25%202%20%3D%3D%200%5D%0A%20%20%20%20average%20%3D%20sum%28evens%29%20/%20len%28evens%29%0A%20%20%20%20return%20average%0A%0Arv%20%3D%20average_wrong%28%5B123,%20456,%20789%5D%29&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false) is again helpful at visualizing the error interactively: Creating the `list` object `evens` eventually references takes *16* computational steps, namely two for managing the list comprehension, one for setting up an empty `list` object, *twelve* for filling it with elements derived from `nums` in the global scope (i.e., that is the error), and one to make `evens` reference it (cf., steps 6-21).\n", "\n", - "The frames logic shown by PythonTutor is the mechanism by which Python not only manages the names inside *one* function call but also for *many* potentially *simultaneous* calls, as revealed in [Chapter 4](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/04_iteration.ipynb#Trivial-Example:-Countdown). It is the reason why we may re-use the same names for the parameters and variables inside both `average_evens()` and `average_wrong()` without Python mixing them up. So, as we already read in the [Zen of Python](https://www.python.org/dev/peps/pep-0020/), \"namespaces are one honking great idea\" (cf., `import this`), and a frame is just a special kind of namespace." + "The frames logic shown by PythonTutor is the mechanism by which Python not only manages the names inside *one* function call but also for *many* potentially *simultaneous* calls, as revealed in [Chapter 4](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/04_iteration.ipynb#Trivial-Example:-Countdown). It is the reason why we may reuse the same names for the parameters and variables inside both `average_evens()` and `average_wrong()` without Python mixing them up. So, as we already read in the [Zen of Python](https://www.python.org/dev/peps/pep-0020/), \"namespaces are one honking great idea\" (cf., `import this`), and a frame is just a special kind of namespace." ] }, { @@ -822,7 +822,7 @@ } }, "source": [ - "Python, however, is again smart enough to keep all the involved `nums` variables apart. So the global `nums` is still pointing to the very same `list` object as before." + "Python, however, is again smart enough to keep all the involved `nums` variables apart. So, the global `nums` is still referencing the *same* `list` object as before." ] }, { @@ -859,7 +859,7 @@ "source": [ "The reason why everything works is that *every time* we (re-)assign an object to a variable *inside* a function with the `=` statement, this is done in the *local* scope by default. There are ways to change variables existing in an outer scope from within a function, but this is a rather advanced topic on its own.\n", "\n", - "[PythonTutor](http://pythontutor.com/visualize.html#code=nums%20%3D%20%5B1,%202,%203,%204,%205,%206,%207,%208,%209,%2010,%2011,%2012%5D%0A%0Adef%20average_odds%28numbers%29%3A%0A%20%20%20%20nums%20%3D%20%5Bint%28n%29%20for%20n%20in%20numbers%5D%0A%20%20%20%20odds%20%3D%20%5Bn%20for%20n%20in%20nums%20if%20n%20%25%202%20!%3D%200%5D%0A%20%20%20%20average%20%3D%20sum%28odds%29%20/%20len%28odds%29%0A%20%20%20%20return%20average%0A%0Arv%20%3D%20average_odds%28%5B1.0,%2010.0,%203.0,%2010.0,%205.0%5D%29&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false) shows how *two* `nums` variables exist in *different* scopes pointing to *different* objects (cf., steps 14-25) when we execute `average_odds([1.0, 10.0, 3.0, 10.0, 5.0])`.\n", + "[PythonTutor](http://pythontutor.com/visualize.html#code=nums%20%3D%20%5B1,%202,%203,%204,%205,%206,%207,%208,%209,%2010,%2011,%2012%5D%0A%0Adef%20average_odds%28numbers%29%3A%0A%20%20%20%20nums%20%3D%20%5Bint%28n%29%20for%20n%20in%20numbers%5D%0A%20%20%20%20odds%20%3D%20%5Bn%20for%20n%20in%20nums%20if%20n%20%25%202%20!%3D%200%5D%0A%20%20%20%20average%20%3D%20sum%28odds%29%20/%20len%28odds%29%0A%20%20%20%20return%20average%0A%0Arv%20%3D%20average_odds%28%5B1.0,%2010.0,%203.0,%2010.0,%205.0%5D%29&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false) shows how *two* `nums` variables exist in *different* scopes referencing *different* objects (cf., steps 14-25) when we execute `average_odds([1.0, 10.0, 3.0, 10.0, 5.0])`.\n", "\n", "Variables whose names collide with the ones of variables in enclosing scopes - and the global scope is just the most enclosing scope - are said to **shadow** them.\n", "\n", @@ -1124,7 +1124,7 @@ } }, "source": [ - "We may also go in the other direction with the [float()](https://docs.python.org/3/library/functions.html#float) built-in function." + "We may also go in the other direction with the [float()](https://docs.python.org/3/library/functions.html#float) built-in." ] }, { @@ -1432,9 +1432,9 @@ "source": [ "Defining both `average_evens()` and `scaled_average_evens()` is also kind of repetitive as most of their code is the same. Such a redundancy makes a codebase harder to maintain in the long run as whenever we change the logic in one function, we must *not* forget to do so for the other function as well.\n", "\n", - "A better way is to design related functions in a **modular** fashion such that they re-use each other's logic.\n", + "A better way is to design related functions in a **modular** fashion such that they reuse each other's logic.\n", "\n", - "For example, as not scaling an average is just a special case of scaling it with `1`, we could re-define the two functions like below: In this setting, the function resembling the *special* case (i.e., `average_evens()`) **forwards** the call to the more *general* function (i.e., `scaled_average_evens()`) using a `scalar=1` argument." + "For example, as not scaling an average is just a special case of scaling it with `1`, we could redefine the two functions like below: In this setting, the function resembling the *special* case (i.e., `average_evens()`) **forwards** the call to the more *general* function (i.e., `scaled_average_evens()`) using a `scalar=1` argument." ] }, { @@ -1796,17 +1796,17 @@ } }, "source": [ - "The `def` statement is a statement because of its side effect of creating a *new* name that points to a *new* `function` object in memory.\n", + "The `def` statement is a statement because of its side effect of creating a *new* name that references a *new* `function` object in memory.\n", "\n", - "We can thus think of it as doing *two* things at once (i.e., either both of them happen or none). First, a `function` object is created that contains the concrete $0$s and $1$s that resemble the instructions we put into the function's body. In the context of a function, these $0$s and $1$s are also called **[byte code](https://en.wikipedia.org/wiki/Bytecode)**. Then, a name pointing at the new `function` object is created.\n", + "We can thus think of it as doing *two* things at once (i.e., either both of them happen or none). First, a `function` object is created that contains the concrete $0$s and $1$s that resemble the instructions we put into the function's body. In the context of a function, these $0$s and $1$s are also called **[byte code](https://en.wikipedia.org/wiki/Bytecode)**. Then, a name referencing the new `function` object is created.\n", "\n", "Only this second aspect makes `def` a statement: Merely creating a new object in memory without making it accessible for later reference does *not* constitute a side effect because the state the program is *not* changed. After all, if we cannot reference an object, how do we know it exists in the first place?\n", "\n", - "Python provides a so-called **[lambda expression](https://docs.python.org/3/reference/expressions.html#lambda)** syntax that allows us to *only* create a `function` object in memory *without* making a name point to it.\n", + "Python provides a so-called **[lambda expression](https://docs.python.org/3/reference/expressions.html#lambda)** syntax that allows us to *only* create a `function` object in memory *without* making a name reference it.\n", "\n", "It starts with the keyword `lambda` followed by an optional comma separated enumeration of parameters, a mandatory colon, and *one* expression that also is the resulting `function` object's return value.\n", "\n", - "Because it does not create a name pointing to the object, we effectively create \"anonymous\" functions with it. In the example, we create a `function` object that adds `3` to the only argument passed in as the parameter `x` and returns that sum." + "Because it does not create a name referencing the object, we effectively create \"anonymous\" functions with it. In the example, we create a `function` object that adds `3` to the only argument passed in as the parameter `x` and returns that sum." ] }, { @@ -1965,9 +1965,9 @@ "source": [ "So far, we have only used what we refer to as **core** Python in this book. By this, we mean all the syntactical rules as specified in the [language reference](https://docs.python.org/3/reference/) and a minimal set of about 50 built-in [functions](https://docs.python.org/3/library/functions.html). With this, we could already implement any algorithm or business logic we can think of!\n", "\n", - "However, after our first couple of programs, we would already start seeing recurring patterns in the code we write. In other words, we would constantly be \"re-inventing the wheel\" in each new project.\n", + "However, after our first couple of programs, we would already start seeing recurring patterns in the code we write. In other words, we would constantly be \"reinventing the wheel\" in each new project.\n", "\n", - "Would it not be smarter to pull out the re-usable components from our programs and put them into some project independent **library** of generically useful functionalities? Then we would only need a way of including these **utilities** in our projects.\n", + "Would it not be smarter to pull out the reusable components from our programs and put them into some project independent **library** of generically useful functionalities? Then we would only need a way of including these **utilities** in our projects.\n", "\n", "As all programmers across all languages face this very same issue, most programming languages come with a so-called **[standard library](https://en.wikipedia.org/wiki/Standard_library)** that provides utilities to accomplish everyday tasks without much code. Examples are making an HTTP request to some website, open and read popular file types (e.g., CSV or Excel files), do something on a computer's file system, and many more." ] @@ -1993,7 +1993,7 @@ "source": [ "Python also comes with a [standard library](https://docs.python.org/3/library/index.html) that is structured into coherent modules and packages for given topics: A **module** is just a plain text file with the file extension *.py* that contains Python code while a **package** is a folder that groups several related modules.\n", "\n", - "The code in the [standard library](https://docs.python.org/3/library/index.html) is contributed and maintained by many volunteers around the world. In contrast to so-called \"third-party\" packages (cf., the next section below), the Python core development team closely monitors and tests the code in the [standard library](https://docs.python.org/3/library/index.html). Consequently, we can be reasonably sure that anything provided by it works correctly independent of our computer's operating system and will most likely also be there in the next Python versions. Parts in the [standard library](https://docs.python.org/3/library/index.html) that are computationally expensive are often re-written in C and, therefore, much faster than anything we could write in Python ourselves. So, whenever we can solve a problem with the help of the [standard library](https://docs.python.org/3/library/index.html), it is almost always the best way to do so as well.\n", + "The code in the [standard library](https://docs.python.org/3/library/index.html) is contributed and maintained by many volunteers around the world. In contrast to so-called \"third-party\" packages (cf., the next section below), the Python core development team closely monitors and tests the code in the [standard library](https://docs.python.org/3/library/index.html). Consequently, we can be reasonably sure that anything provided by it works correctly independent of our computer's operating system and will most likely also be there in the next Python versions. Parts in the [standard library](https://docs.python.org/3/library/index.html) that are computationally expensive are often rewritten in C and, therefore, much faster than anything we could write in Python ourselves. So, whenever we can solve a problem with the help of the [standard library](https://docs.python.org/3/library/index.html), it is almost always the best way to do so as well.\n", "\n", "The [standard library](https://docs.python.org/3/library/index.html) has grown very big over the years, and we refer to the website [PYMOTW](https://pymotw.com/3/index.html) (i.e., \"Python Module of the Week\") that features well written introductory tutorials and how-to guides to most parts of the library. The same author also published a [book](https://www.amazon.com/Python-Standard-Library-Example-Developers/dp/0134291050/ref=as_li_ss_tl?ie=UTF8&qid=1493563121&sr=8-1&keywords=python+3+standard+library+by+example) that many Pythonistas keep on their shelf for reference. Knowing what is in the [standard library](https://docs.python.org/3/library/index.html) is quite valuable for solving real-world tasks quickly.\n", "\n", @@ -2045,7 +2045,7 @@ } }, "source": [ - "This creates the variable `math` that points to a **[module object](https://docs.python.org/3/glossary.html#term-module)** (i.e., type `module`) in memory." + "This creates the variable `math` that references a **[module object](https://docs.python.org/3/glossary.html#term-module)** (i.e., type `module`) in memory." ] }, { @@ -2084,7 +2084,7 @@ { "data": { "text/plain": [ - "139646755983752" + "140384754610488" ] }, "execution_count": 58, @@ -2430,7 +2430,7 @@ "source": [ "If we only need one particular function from a module, we may also use the alternative `from ... import ...` syntax.\n", "\n", - "This does *not* create a module object but only makes a variable in our current location point to an object defined inside a module directly." + "This does *not* create a module object but only makes a variable in our current location reference an object defined inside a module directly." ] }, { @@ -2697,7 +2697,7 @@ { "data": { "text/plain": [ - "0.21932911318720072" + "0.17184403163109208" ] }, "execution_count": 75, @@ -2732,7 +2732,7 @@ { "data": { "text/plain": [ - ">" + ">" ] }, "execution_count": 76, @@ -2781,7 +2781,7 @@ { "data": { "text/plain": [ - "7" + "2" ] }, "execution_count": 78, @@ -3242,7 +3242,7 @@ "\n", "The *name* to be imported is the file's name except for the *.py* part. For this to work, the file's name *must* adhere to the *same* rules as hold for [variable names](https://docs.python.org/3/reference/lexical_analysis.html#identifiers) in general.\n", "\n", - "What happens during an import is as follows. When Python sees the `import sample_module` part, it first creates a *new* object of type `module` in memory. This is effectively an *empty* namespace. Then, it executes the imported file's code from top to bottom. Whatever variables are still defined at the end of this, are put into the module's namespace. Only if the file's code does *not* raise an error, will Python make a variable in our current location (i.e., `mod` here) point to the created `module` object. Otherwise, it is discarded. In essence, it is as if we copied and pasted the file's code in place of the import statement. If we import an already imported module again, Python is smart enough to avoid doing all this work all over and does nothing." + "What happens during an import is as follows. When Python sees the `import sample_module` part, it first creates a *new* object of type `module` in memory. This is effectively an *empty* namespace. Then, it executes the imported file's code from top to bottom. Whatever variables are still defined at the end of this, are put into the module's namespace. Only if the file's code does *not* raise an error, will Python make a variable in our current location (i.e., `mod` here) reference the created `module` object. Otherwise, it is discarded. In essence, it is as if we copied and pasted the file's code in place of the import statement. If we import an already imported module again, Python is smart enough to avoid doing all this work all over and does nothing." ] }, { @@ -3341,7 +3341,7 @@ } }, "source": [ - "We use the imported `mod.average_evens()` just like `average_evens()` defined above. The advantage we get from **modularization** with *.py* files is that we can now easily re-use functions across different Jupyter notebooks without re-defining them again and again. Also, we can \"source out\" code that distracts from the storyline told in a notebook." + "We use the imported `mod.average_evens()` just like `average_evens()` defined above. The advantage we get from **modularization** with *.py* files is that we can now easily reuse functions across different Jupyter notebooks without redefining them again and again. Also, we can \"source out\" code that distracts from the storyline told in a notebook." ] }, { @@ -3462,7 +3462,7 @@ "Functions provide benefits as they:\n", "\n", "- make programs easier to comprehend and debug for humans as they give names to the smaller parts of a larger program (i.e., they **modularize** a codebase), and\n", - "- eliminate redundancies by allowing **re-use of code**.\n", + "- eliminate redundancies by allowing **reuse of code**.\n", "\n", "Functions are **defined** once with the `def` statement. Then, they may be **called** many times with the call operator `()`.\n", "\n", diff --git a/02_functions_review_and_exercises.ipynb b/02_functions_review_and_exercises.ipynb index d0935c6..ca631a7 100644 --- a/02_functions_review_and_exercises.ipynb +++ b/02_functions_review_and_exercises.ipynb @@ -215,21 +215,27 @@ "execution_count": null, "metadata": {}, "outputs": [], - "source": [] + "source": [ + "import ..." + ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], - "source": [] + "source": [ + "r = ..." + ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], - "source": [] + "source": [ + "..." + ] }, { "cell_type": "markdown", @@ -244,17 +250,8 @@ "metadata": {}, "outputs": [], "source": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n" + "def sphere_volume(...):\n", + " ..." ] }, { @@ -269,42 +266,54 @@ "execution_count": null, "metadata": {}, "outputs": [], - "source": [] + "source": [ + "radius = ..." + ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], - "source": [] + "source": [ + "sphere_volume(...)" + ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], - "source": [] + "source": [ + "sphere_volume(...)" + ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], - "source": [] + "source": [ + "sphere_volume(...)" + ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], - "source": [] + "source": [ + "sphere_volume(...)" + ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], - "source": [] + "source": [ + "sphere_volume(...)" + ] }, { "cell_type": "markdown", @@ -334,7 +343,9 @@ "execution_count": null, "metadata": {}, "outputs": [], - "source": [] + "source": [ + "radius = ..." + ] }, { "cell_type": "code", @@ -342,14 +353,15 @@ "metadata": {}, "outputs": [], "source": [ - "\n" + "for ... in ...:\n", + " ..." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "**Q11.6**: What lesson did you learn about the `float` type?" + "**Q11.6**: What lesson do you learn about the `float` type?" ] }, { diff --git a/03_conditionals.ipynb b/03_conditionals.ipynb index 072448a..9ffe09e 100644 --- a/03_conditionals.ipynb +++ b/03_conditionals.ipynb @@ -19,7 +19,7 @@ } }, "source": [ - "We analyzed every aspect of the `average_evens()` function in [Chapter 2](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/02_functions.ipynb) except for the `if` related parts. While it seems to do what we expect it to, there is a whole lot more we learn from taking it apart. In particular, the `if` may occur within both a **statement** or an **expression**, analogous as to how a noun in a natural language is *either* the subject of *or* an object in a sentence. What is common to both usages is that it leads to code being executed for *parts* of the input only. It is our first way of **controlling** the **flow of execution** in a program.\n", + "We analyzed every aspect of the `average_evens()` function in [Chapter 2](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/02_functions.ipynb) except for the `if`-related parts. While it seems to do what we expect it to, there is a whole lot more we learn from taking it apart. In particular, the `if` may occur within both a **statement** or an **expression**, analogous as to how a noun in a natural language is *either* the subject of *or* an object in a sentence. What is common to both usages is that it leads to code being executed for *parts* of the input only. It is our first way of **controlling** the **flow of execution** in a program.\n", "\n", "After deconstructing `if` in the first part of this chapter, we take a close look at a similar concept, namely handling **exceptions**." ] @@ -189,7 +189,7 @@ { "data": { "text/plain": [ - "94711290794976" + "93918328484832" ] }, "execution_count": 5, @@ -213,7 +213,7 @@ { "data": { "text/plain": [ - "94711290794944" + "93918328484800" ] }, "execution_count": 6, @@ -313,7 +313,7 @@ { "data": { "text/plain": [ - "94711290781936" + "93918328471792" ] }, "execution_count": 10, @@ -357,7 +357,7 @@ } }, "source": [ - "`True`, `False`, and `None` have the property that they each exist in memory only *once*. Objects designed this way are so-called **singletons**. This **[design pattern](https://en.wikipedia.org/wiki/Design_Patterns)** was originally developed to keep a program's memory usage at a minimum. It may only be employed in situations where we know that an object does *not* mutate its value (i.e., to re-use the bag analogy from [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb#Objects-vs.-Types-vs.-Values), no flipping of $0$s and $1$s in the bag is allowed). In languages \"closer\" to the memory like C, we would have to code this singleton logic ourselves, but Python has this built in for *some* types.\n", + "`True`, `False`, and `None` have the property that they each exist in memory only *once*. Objects designed this way are so-called **singletons**. This **[design pattern](https://en.wikipedia.org/wiki/Design_Patterns)** was originally developed to keep a program's memory usage at a minimum. It may only be employed in situations where we know that an object does *not* mutate its value (i.e., to reuse the bag analogy from [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb#Objects-vs.-Types-vs.-Values), no flipping of $0$s and $1$s in the bag is allowed). In languages \"closer\" to the memory like C, we would have to code this singleton logic ourselves, but Python has this built in for *some* types.\n", "\n", "We verify this with either the `is` operator or by comparing memory addresses." ] @@ -418,7 +418,7 @@ } }, "source": [ - "So the following expression regards *four* objects in memory: *One* `list` object holding ten pointers to *three* other objects." + "So the following expression regards *four* objects in memory: *One* `list` object holding ten references to *three* other objects." ] }, { diff --git a/03_conditionals_review_and_exercises.ipynb b/03_conditionals_review_and_exercises.ipynb index 89d7009..90b4ff9 100644 --- a/03_conditionals_review_and_exercises.ipynb +++ b/03_conditionals_review_and_exercises.ipynb @@ -110,7 +110,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "**Q6**: What does the `finally`-branch enforce in this code snippet? How can a `try` statement be useful *without* an `except`-branch?" + "**Q6**: What does the `finally`-clause enforce in this code snippet? How can a `try` statement be useful *without* an `except`-clause?" ] }, { @@ -210,22 +210,8 @@ "metadata": {}, "outputs": [], "source": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n" + "def discounted_price(...):\n", + " ..." ] }, { @@ -279,7 +265,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "**Q9.3**: Re-calculate the last two line items with order quantities of $20$ and $15$. What do you observe?" + "**Q9.3**: Calculate the last two line items with order quantities of $20$ and $15$. What do you observe?" ] }, { @@ -304,7 +290,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - " (your observation)" + " " ] }, { @@ -373,12 +359,6 @@ "outputs": [], "source": [ "for number in numbers:\n", - " ...\n", - " ...\n", - " ...\n", - " ...\n", - " ...\n", - " ...\n", " ..." ] }, diff --git a/04_iteration.ipynb b/04_iteration.ipynb index 2246da5..aabaaef 100644 --- a/04_iteration.ipynb +++ b/04_iteration.ipynb @@ -91,7 +91,7 @@ " Args:\n", " n (int): seconds until the party begins\n", " \"\"\"\n", - " if n == 0: # base case\n", + " if n == 0: # = base case\n", " print(\"Happy New Year!\")\n", " else:\n", " print(n)\n", @@ -222,7 +222,7 @@ " Returns:\n", " factorial (int)\n", " \"\"\"\n", - " if n == 0:\n", + " if n == 0: # = base case\n", " return 1\n", " else:\n", " recurse = factorial(n - 1)\n", @@ -323,7 +323,7 @@ " Returns:\n", " factorial (int)\n", " \"\"\"\n", - " if n == 0:\n", + " if n == 0: # = base case\n", " return 1\n", " return n * factorial(n - 1)" ] @@ -517,7 +517,7 @@ " Returns:\n", " gcd (int)\n", " \"\"\"\n", - " if b == 0:\n", + " if b == 0: # = base case\n", " return a \n", " return gcd(b, a % b)" ] @@ -787,9 +787,9 @@ " Returns:\n", " ith_fibonacci (int)\n", " \"\"\"\n", - " if i == 0:\n", + " if i == 0: # = first base case\n", " return 0\n", - " elif i == 1:\n", + " elif i == 1: # = second base case\n", " return 1\n", " return fibonacci(i - 1) + fibonacci(i - 2)" ] @@ -841,7 +841,7 @@ "\n", "To understand this in detail, we would 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., \"re-use\") 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 8, after introducing the [dictionaries](https://docs.python.org/3/library/stdtypes.html#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 8](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/08_mappings.ipynb#Memoization), after introducing the [dictionaries](https://docs.python.org/3/library/stdtypes.html#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." ] @@ -859,7 +859,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "36.5 µs ± 2.47 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n" + "37.5 µs ± 4.26 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n" ] } ], @@ -881,7 +881,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "1.58 ms ± 20.7 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n" + "1.49 ms ± 22.4 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n" ] } ], @@ -903,7 +903,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "194 ms ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n" + "199 ms ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n" ] } ], @@ -925,7 +925,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "2.15 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n" + "2.04 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n" ] } ], @@ -947,7 +947,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "5.59 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n" + "5.33 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n" ] } ], @@ -4011,9 +4011,9 @@ "\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[0mcountdown\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m3.1\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\u001b[0m in \u001b[0;36mcountdown\u001b[0;34m(n)\u001b[0m\n\u001b[1;32m 9\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 10\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mn\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 11\u001b[0;31m \u001b[0mcountdown\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mn\u001b[0m \u001b[0;34m-\u001b[0m \u001b[0;36m1\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\u001b[0m in \u001b[0;36mcountdown\u001b[0;34m(n)\u001b[0m\n\u001b[1;32m 9\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 10\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mn\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 11\u001b[0;31m \u001b[0mcountdown\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mn\u001b[0m \u001b[0;34m-\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "... last 1 frames repeated, from the frame below ...\n", - "\u001b[0;32m\u001b[0m in \u001b[0;36mcountdown\u001b[0;34m(n)\u001b[0m\n\u001b[1;32m 9\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 10\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mn\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 11\u001b[0;31m \u001b[0mcountdown\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mn\u001b[0m \u001b[0;34m-\u001b[0m \u001b[0;36m1\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\u001b[0m in \u001b[0;36mcountdown\u001b[0;34m(n)\u001b[0m\n\u001b[1;32m 9\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 10\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mn\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 11\u001b[0;31m \u001b[0mcountdown\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mn\u001b[0m \u001b[0;34m-\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mRecursionError\u001b[0m: maximum recursion depth exceeded while calling a Python object" ] } @@ -4050,9 +4050,9 @@ "\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[0mfactorial\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m3.1\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\u001b[0m in \u001b[0;36mfactorial\u001b[0;34m(n)\u001b[0m\n\u001b[1;32m 10\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mn\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;36m0\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 11\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 12\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mn\u001b[0m \u001b[0;34m*\u001b[0m \u001b[0mfactorial\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mn\u001b[0m \u001b[0;34m-\u001b[0m \u001b[0;36m1\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\u001b[0m in \u001b[0;36mfactorial\u001b[0;34m(n)\u001b[0m\n\u001b[1;32m 10\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mn\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;36m0\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;31m# = base case\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 11\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 12\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mn\u001b[0m \u001b[0;34m*\u001b[0m \u001b[0mfactorial\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mn\u001b[0m \u001b[0;34m-\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "... last 1 frames repeated, from the frame below ...\n", - "\u001b[0;32m\u001b[0m in \u001b[0;36mfactorial\u001b[0;34m(n)\u001b[0m\n\u001b[1;32m 10\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mn\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;36m0\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 11\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 12\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mn\u001b[0m \u001b[0;34m*\u001b[0m \u001b[0mfactorial\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mn\u001b[0m \u001b[0;34m-\u001b[0m \u001b[0;36m1\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\u001b[0m in \u001b[0;36mfactorial\u001b[0;34m(n)\u001b[0m\n\u001b[1;32m 10\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mn\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;36m0\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;31m# = base case\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 11\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 12\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mn\u001b[0m \u001b[0;34m*\u001b[0m \u001b[0mfactorial\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mn\u001b[0m \u001b[0;34m-\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mRecursionError\u001b[0m: maximum recursion depth exceeded in comparison" ] } @@ -4143,7 +4143,7 @@ " raise TypeError(\"Factorial is only defined for integers\")\n", " elif n < 0:\n", " raise ValueError(\"Factorial is not defined for negative integers\")\n", - " elif n == 0:\n", + " elif n == 0: # = base case\n", " return 1\n", " return n * factorial(n - 1)" ] @@ -4235,7 +4235,7 @@ "\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[0mfactorial\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m3.1\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\u001b[0m in \u001b[0;36mfactorial\u001b[0;34m(n)\u001b[0m\n\u001b[1;32m 13\u001b[0m \"\"\"\n\u001b[1;32m 14\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0misinstance\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mn\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mint\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---> 15\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mTypeError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Factorial is only defined for integers\"\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 16\u001b[0m \u001b[0;32melif\u001b[0m \u001b[0mn\u001b[0m \u001b[0;34m<\u001b[0m \u001b[0;36m0\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 17\u001b[0m \u001b[0;32mraise\u001b[0m \u001b[0mValueError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Factorial is not defined for negative integers\"\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;36mfactorial\u001b[0;34m(n)\u001b[0m\n\u001b[1;32m 13\u001b[0m \"\"\"\n\u001b[1;32m 14\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0misinstance\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mn\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mint\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---> 15\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mTypeError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Factorial is only defined for integers\"\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 16\u001b[0m \u001b[0;32melif\u001b[0m \u001b[0mn\u001b[0m \u001b[0;34m<\u001b[0m \u001b[0;36m0\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 17\u001b[0m \u001b[0;32mraise\u001b[0m \u001b[0mValueError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Factorial is not defined for negative integers\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;31mTypeError\u001b[0m: Factorial is only defined for integers" ] } @@ -4261,7 +4261,7 @@ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mValueError\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[0mfactorial\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m-\u001b[0m\u001b[0;36m42\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\u001b[0m in \u001b[0;36mfactorial\u001b[0;34m(n)\u001b[0m\n\u001b[1;32m 15\u001b[0m \u001b[0;32mraise\u001b[0m \u001b[0mTypeError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Factorial is only defined for integers\"\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;32melif\u001b[0m \u001b[0mn\u001b[0m \u001b[0;34m<\u001b[0m \u001b[0;36m0\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 17\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mValueError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Factorial is not defined for negative integers\"\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[0;32melif\u001b[0m \u001b[0mn\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;36m0\u001b[0m\u001b[0;34m:\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[0;36m1\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m\u001b[0m in \u001b[0;36mfactorial\u001b[0;34m(n)\u001b[0m\n\u001b[1;32m 15\u001b[0m \u001b[0;32mraise\u001b[0m \u001b[0mTypeError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Factorial is only defined for integers\"\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;32melif\u001b[0m \u001b[0mn\u001b[0m \u001b[0;34m<\u001b[0m \u001b[0;36m0\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 17\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mValueError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Factorial is not defined for negative integers\"\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[0;32melif\u001b[0m \u001b[0mn\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;36m0\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;31m# = base case\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[0;36m1\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;31mValueError\u001b[0m: Factorial is not defined for negative integers" ] } @@ -4565,7 +4565,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "4.69 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n" + "4.6 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n" ] } ], @@ -4790,11 +4790,11 @@ } }, "source": [ - "Recursion and the `while` statement are two sides of the same coin. Disregarding that in the case of recursion Python internally faces some additional burden for managing the stack of frames in memory, both approaches lead to the *same* computational steps in memory. More importantly, we can re-formulate a recursive implementation in an iterative way and vice versa despite one of the two ways often \"feeling\" a lot more natural given a particular problem.\n", + "Recursion and the `while` statement are two sides of the same coin. Disregarding that in the case of recursion Python internally faces some additional burden for managing the stack of frames in memory, both approaches lead to the *same* computational steps in memory. More importantly, we can formulate any recursive implementation in an iterative way and vice versa despite one of the two ways often \"feeling\" a lot more natural given a particular problem.\n", "\n", "So how does the compound `for` statement (cf., [reference](https://docs.python.org/3/reference/compound_stmts.html#the-for-statement)) in this book's very first example fit into this picture? It is a *redundant* language construct to provide a *shorter* and more *convenient* syntax for common applications of the `while` statement. In programming, such additions to a language are called **syntactic sugar**. A cup of tea tastes better with sugar, but we may drink tea without sugar too.\n", "\n", - "Consider `elements` below. Without the `for` statement, we have to manage a temporary **index variable**, `index`, to loop over all the elements and also obtain the individual elements with the `[]` operator in each iteration of the loop." + "Consider `elements` below. Without the `for` statement, we must manage a temporary **index variable**, `index`, to loop over all the elements and also obtain the individual elements with the `[]` operator in each iteration of the loop." ] }, { @@ -5000,21 +5000,21 @@ } }, "source": [ - "The essential difference between the above `list` objects, `[0, 1, 2, 3, 4]` and `[1, 3, 5, 7, 9]`, and the `range` objects, `range(5)` and `range(1, 10, 2)`, is that in the former case *six* objects are created in memory *before* the `for` statement starts running, *one* `list` holding pointers to *five* `int` objects, whereas in the latter case only *one* `range` object is created that **generates** `int` objects one at a time *while* the `for`-loop runs.\n", + "The essential difference between the above `list` objects, `[0, 1, 2, 3, 4]` and `[1, 3, 5, 7, 9]`, and the `range` objects, `range(5)` and `range(1, 10, 2)`, is that in the former case *six* objects are created in memory *before* the `for` statement starts running, *one* `list` holding references to *five* `int` objects, whereas in the latter case only *one* `range` object is created that **generates** `int` objects one at a time *while* the `for`-loop runs.\n", "\n", "However, we can loop over both of them. So a natural question to ask is why Python treats objects of *different* types in the *same* way when used with a `for` statement.\n", "\n", - "So far, the overarching storyline in this book goes like this: In Python, *everything* is an object. Besides its *identity* and *value*, every object is characterized by belonging to *one data type* that determines how the object behaves and what we may do with it.\n", + "So far, the overarching storyline in this book goes like this: In Python, *everything* is an object. Besides its *identity* and *value*, every object is characterized by \"belonging\" to *one* data type that determines how the object behaves and what we may do with it.\n", "\n", - "Now, just as we classify objects by their types, we also classify these **concrete data types** (e.g., `int`, `float`, `str`, or `list`) into **abstract concepts**.\n", + "Now, just as we classify objects by data type, we also classify these data types (e.g., `int`, `float`, `str`, or `list`) into **abstract concepts**.\n", "\n", - "We did this already in [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb#Who-am-I?-And-how-many?) when we described a `list` object as \"some sort of container that holds [...] pointers to other objects\". So, abstractly speaking, **containers** are any objects that are \"composed\" of other objects and also \"manage\" how these objects are organized. `list` objects, for example, have the property that they model an internal order associated with its elements. There exist, however, other container types, many of which do *not* come with an order. So, containers primarily \"contain\" other objects and have *nothing* to do with looping.\n", + "We did this already in [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb#Who-am-I?-And-how-many?) when we described a `list` object as \"some sort of container that holds [...] references to other objects\". So, abstractly speaking, **containers** are any objects that are \"composed\" of other objects and also \"manage\" how these objects are organized. `list` objects, for example, have the property that they model an order associated with their elements. There exist, however, other container types, many of which do *not* come with an order. So, containers primarily \"contain\" other objects and have *nothing* to do with looping.\n", "\n", - "On the contrary, the abstract concept of **iterables** is all about looping: Any object that we can loop over is, by definition, an iterable. So, `range` objects, for example, are iterables that do *not* contain other objects. Moreover, looping does *not* have to occur in a *predictable* order, although this is the case for both `list` and `range` objects.\n", + "On the contrary, the abstract concept of **iterables** is all about looping: Any object that we can loop over is, by definition, an iterable. So, `range` objects, for example, are iterables, even though they hold no references to other objects. Moreover, looping does *not* have to occur in a *predictable* order, although this is the case for both `list` and `range` objects.\n", "\n", - "Typically, containers are iterables, and iterables are containers. Yet, only because these two concepts coincide often, we must not think of them as the same. Chapter 9 finally gives an explanation as to how abstract concepts are implemented and play together.\n", + "Typically, containers are iterables, and iterables are containers. Yet, only because these two concepts coincide often, we must not think of them as the same. In [Chapter 7](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/07_sequences.ipynb#Collections-vs.-Sequences), we formalize these two concepts and introduce many more. Finally, Chapter 9 gives an explanation how abstract concepts are implemented and play together.\n", "\n", - "So, `list` objects like `first_names` below are iterable containers. They implement even more abstract concepts, as [Chapter 7](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/07_sequences.ipynb#Collections-vs.-Sequences) reveals." + "Let's continue with `first_names` below as an example an illustrate what iterable containers are." ] }, { @@ -5038,7 +5038,7 @@ } }, "source": [ - "The characteristic operator associated with a container type is the `in` operator: It checks if a given object evaluates equal to at least one of the objects in the container. Colloquially, it checks if an object is \"contained\" in the container. Formally, this operation is called **membership testing**." + "The characteristic operator associated with container types is the `in` operator: It checks if a given object evaluates equal to at least one of the objects in the container. Colloquially, it checks if an object is \"contained\" in the container. Formally, this operation is called **membership testing**." ] }, { @@ -5112,7 +5112,7 @@ { "data": { "text/plain": [ - "True" + "[0, 1, 2, 3, 4]" ] }, "execution_count": 58, @@ -5120,6 +5120,30 @@ "output_type": "execute_result" } ], + "source": [ + "elements" + ] + }, + { + "cell_type": "code", + "execution_count": 59, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 59, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "3.0 in elements" ] @@ -5137,7 +5161,7 @@ }, { "cell_type": "code", - "execution_count": 59, + "execution_count": 60, "metadata": { "slideshow": { "slide_type": "slide" @@ -5170,7 +5194,7 @@ }, { "cell_type": "code", - "execution_count": 60, + "execution_count": 61, "metadata": { "slideshow": { "slide_type": "fragment" @@ -5203,7 +5227,7 @@ }, { "cell_type": "code", - "execution_count": 61, + "execution_count": 62, "metadata": { "slideshow": { "slide_type": "skip" @@ -5236,7 +5260,7 @@ }, { "cell_type": "code", - "execution_count": 62, + "execution_count": 63, "metadata": { "slideshow": { "slide_type": "skip" @@ -5249,7 +5273,7 @@ }, { "cell_type": "code", - "execution_count": 63, + "execution_count": 64, "metadata": { "slideshow": { "slide_type": "skip" @@ -5297,7 +5321,7 @@ }, { "cell_type": "code", - "execution_count": 64, + "execution_count": 65, "metadata": { "code_folding": [], "slideshow": { @@ -5338,7 +5362,7 @@ }, { "cell_type": "code", - "execution_count": 65, + "execution_count": 66, "metadata": { "slideshow": { "slide_type": "slide" @@ -5358,7 +5382,7 @@ "144" ] }, - "execution_count": 65, + "execution_count": 66, "metadata": {}, "output_type": "execute_result" } @@ -5391,7 +5415,7 @@ }, { "cell_type": "code", - "execution_count": 66, + "execution_count": 67, "metadata": { "slideshow": { "slide_type": "slide" @@ -5411,7 +5435,7 @@ "218922995834555169026" ] }, - "execution_count": 66, + "execution_count": 67, "metadata": {}, "output_type": "execute_result" } @@ -5444,7 +5468,7 @@ }, { "cell_type": "code", - "execution_count": 67, + "execution_count": 68, "metadata": { "slideshow": { "slide_type": "slide" @@ -5480,7 +5504,7 @@ }, { "cell_type": "code", - "execution_count": 68, + "execution_count": 69, "metadata": { "slideshow": { "slide_type": "slide" @@ -5500,7 +5524,7 @@ "3628800" ] }, - "execution_count": 68, + "execution_count": 69, "metadata": {}, "output_type": "execute_result" } @@ -5555,7 +5579,7 @@ }, { "cell_type": "code", - "execution_count": 69, + "execution_count": 70, "metadata": { "slideshow": { "slide_type": "slide" @@ -5568,7 +5592,7 @@ }, { "cell_type": "code", - "execution_count": 70, + "execution_count": 71, "metadata": { "slideshow": { "slide_type": "-" @@ -5592,7 +5616,7 @@ }, { "cell_type": "code", - "execution_count": 71, + "execution_count": 72, "metadata": { "slideshow": { "slide_type": "fragment" @@ -5638,7 +5662,7 @@ }, { "cell_type": "code", - "execution_count": 72, + "execution_count": 73, "metadata": { "slideshow": { "slide_type": "slide" @@ -5716,7 +5740,7 @@ }, { "cell_type": "code", - "execution_count": 73, + "execution_count": 74, "metadata": { "slideshow": { "slide_type": "slide" @@ -5759,7 +5783,7 @@ }, { "cell_type": "code", - "execution_count": 74, + "execution_count": 75, "metadata": { "slideshow": { "slide_type": "slide" @@ -5797,7 +5821,7 @@ }, { "cell_type": "code", - "execution_count": 75, + "execution_count": 76, "metadata": { "slideshow": { "slide_type": "skip" @@ -5885,7 +5909,7 @@ }, { "cell_type": "code", - "execution_count": 76, + "execution_count": 77, "metadata": { "slideshow": { "slide_type": "slide" @@ -5898,7 +5922,7 @@ }, { "cell_type": "code", - "execution_count": 77, + "execution_count": 78, "metadata": { "slideshow": { "slide_type": "-" @@ -5918,7 +5942,7 @@ "370" ] }, - "execution_count": 77, + "execution_count": 78, "metadata": {}, "output_type": "execute_result" } @@ -5980,7 +6004,7 @@ }, { "cell_type": "code", - "execution_count": 78, + "execution_count": 79, "metadata": { "slideshow": { "slide_type": "slide" @@ -5993,7 +6017,7 @@ "[7, 11, 8, 5, 3, 12, 2, 6, 9, 10, 1, 4]" ] }, - "execution_count": 78, + "execution_count": 79, "metadata": {}, "output_type": "execute_result" } @@ -6004,7 +6028,7 @@ }, { "cell_type": "code", - "execution_count": 79, + "execution_count": 80, "metadata": { "slideshow": { "slide_type": "-" @@ -6024,7 +6048,7 @@ "227" ] }, - "execution_count": 79, + "execution_count": 80, "metadata": {}, "output_type": "execute_result" } @@ -6070,7 +6094,7 @@ }, { "cell_type": "code", - "execution_count": 80, + "execution_count": 81, "metadata": { "slideshow": { "slide_type": "slide" @@ -6090,7 +6114,7 @@ "227" ] }, - "execution_count": 80, + "execution_count": 81, "metadata": {}, "output_type": "execute_result" } @@ -6178,7 +6202,7 @@ }, { "cell_type": "code", - "execution_count": 81, + "execution_count": 82, "metadata": { "slideshow": { "slide_type": "slide" @@ -6191,7 +6215,7 @@ }, { "cell_type": "code", - "execution_count": 82, + "execution_count": 83, "metadata": { "slideshow": { "slide_type": "-" @@ -6204,7 +6228,7 @@ }, { "cell_type": "code", - "execution_count": 83, + "execution_count": 84, "metadata": { "code_folding": [], "slideshow": { @@ -6283,7 +6307,7 @@ }, { "cell_type": "code", - "execution_count": 84, + "execution_count": 85, "metadata": { "slideshow": { "slide_type": "slide" @@ -6320,7 +6344,7 @@ }, { "cell_type": "code", - "execution_count": 85, + "execution_count": 86, "metadata": { "slideshow": { "slide_type": "slide" @@ -6358,7 +6382,7 @@ }, { "cell_type": "code", - "execution_count": 86, + "execution_count": 87, "metadata": { "slideshow": { "slide_type": "skip" @@ -6371,7 +6395,7 @@ }, { "cell_type": "code", - "execution_count": 87, + "execution_count": 88, "metadata": { "slideshow": { "slide_type": "slide" @@ -6441,7 +6465,7 @@ "\n", "Second, the `while` and `for` statements are alternative and potentially more intuitive ways to express iteration as they correspond to a **forward** reasoning. The `for` statement is **syntactic sugar** that allows rewriting common occurrences of the `while` statement concisely. Python provides the `break` and `continue` statements as well as an optional `else`-clause that make working with the `for` and `while` statements even more convenient.\n", "\n", - "**Iterables** are any **concrete data types** that support being looped over, for example, with the `for` statement. The idea behind iterables is an **abstract concept** that may or may not be implemented by any given concrete data type. For example, both `list` and `range` objects can be looped over. The `list` type is also a **container** as any given `list` objects \"contains\" pointers to other objects in memory. On the contrary, the `range` type does not point to any other objects but instead creates *new* `int` objects \"on the fly\" (i.e., when being looped over)." + "**Iterables** are any **concrete data types** that support being looped over, for example, with the `for` statement. The idea behind iterables is an **abstract concept** that may or may not be implemented by any given concrete data type. For example, both `list` and `range` objects can be looped over. The `list` type is also a **container** as any given `list` objects \"contains\" references to other objects in memory. On the contrary, the `range` type does not reference any other object but instead creates *new* `int` objects \"on the fly\" (i.e., when being looped over)." ] } ], diff --git a/04_iteration_review_and_exercises.ipynb b/04_iteration_review_and_exercises.ipynb index 64d6903..9e2b28f 100644 --- a/04_iteration_review_and_exercises.ipynb +++ b/04_iteration_review_and_exercises.ipynb @@ -455,19 +455,8 @@ "\n", " # answer to Q15.6\n", " # ...\n", - " # ...\n", - " # ...\n", - " # ...\n", - " # ...\n", - " # ...\n", - " # ...\n", - " # ...\n", - " # ...\n", - " # ...\n", - " # ...\n", "\n", " # answer to Q15.7\n", - " # ...\n", " # ..." ] }, @@ -543,7 +532,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "#### Pythonic Re-Factoring" + "#### Pythonic Refactoring" ] }, { @@ -569,7 +558,6 @@ " # ...\n", "\n", " # answer to Q15.10\n", - " # ...\n", " # ..." ] }, @@ -726,7 +714,8 @@ "metadata": {}, "outputs": [], "source": [ - "\n" + "for ... in ...:\n", + " ..." ] }, { @@ -811,9 +800,9 @@ " 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 ()" + " hanoi_ordered(..., offset=offset) # <- answer to Q15.18\n", + " # ... <- answer to Q15.18\n", + " hanoi_ordered(..., offset=count) # <- answer to Q15.18" ] }, { diff --git a/05_numbers.ipynb b/05_numbers.ipynb index a671e68..48d8623 100644 --- a/05_numbers.ipynb +++ b/05_numbers.ipynb @@ -21,9 +21,9 @@ "source": [ "After learning about the basic building blocks of expressing and structuring the business logic in programs, we focus our attention on the **data types** Python offers us, both built-in and available via the [standard library](https://docs.python.org/3/library/index.html) or third-party packages.\n", "\n", - "We start with the \"simple\" ones: Numeric types in this chapter and textual data in [Chapter 6](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/06_text.ipynb). An important fact that holds for all objects of these types is that they are **immutable**. To re-use the bag analogy from [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb#Objects-vs.-Types-vs.-Values), this means that the $0$s and $1$s making up an object's *value* cannot be changed once the bag is created in memory, implying that any operation with or method on the object creates a *new* object in a *different* memory location.\n", + "We start with the \"simple\" ones: Numeric types in this chapter and textual data in [Chapter 6](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/06_text.ipynb). An important fact that holds for all objects of these types is that they are **immutable**. To reuse the bag analogy from [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb#Objects-vs.-Types-vs.-Values), this means that the $0$s and $1$s making up an object's *value* cannot be changed once the bag is created in memory, implying that any operation with or method on the object creates a *new* object in a *different* memory location.\n", "\n", - "[Chapter 7](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/07_sequences.ipynb) and Chapter 8 then cover the more \"complex\" data types, including, for example, the `list` type. Finally, Chapter 9 completes the picture by introducing language constructs to create custom types.\n", + "[Chapter 7](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/07_sequences.ipynb) and [Chapter 8](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/08_mappings.ipynb) then cover the more \"complex\" data types, including, for example, the `list` type. Finally, Chapter 9 completes the picture by introducing language constructs to create custom types.\n", "\n", "We have already seen many hints indicating that numbers are not as trivial to work with as it seems at first sight:\n", "\n", @@ -92,7 +92,7 @@ { "data": { "text/plain": [ - "140087541220560" + "140673805309168" ] }, "execution_count": 2, @@ -307,7 +307,7 @@ } }, "source": [ - "We may pass a `str` object formatted this way as the argument to the [int()](https://docs.python.org/3/library/functions.html#int) built-in, together with `base=2`, to (re-)create an `int` object, for example, with the value of `3`." + "We may pass a `str` object formatted this way as the argument to the [int()](https://docs.python.org/3/library/functions.html#int) built-in, together with `base=2`, to create an `int` object, for example, with the value of `3`." ] }, { @@ -883,7 +883,7 @@ } }, "source": [ - "To (re-)create an `int` object with the value `177`, we call the [int()](https://docs.python.org/3/library/functions.html#int) built-in with a properly formatted `str` object and `base=16` as arguments." + "To obtain a *new* `int` object with the value `177`, we call the [int()](https://docs.python.org/3/library/functions.html#int) built-in with a properly formatted `str` object and `base=16` as arguments." ] }, { @@ -2115,7 +2115,7 @@ { "data": { "text/plain": [ - "140087541402072" + "140673805486768" ] }, "execution_count": 63, @@ -5498,7 +5498,7 @@ { "data": { "text/plain": [ - "140087540555856" + "140673804641712" ] }, "execution_count": 176, @@ -6054,7 +6054,7 @@ } }, "source": [ - "Also, a conjugate() method is bound to every `complex` object. The [complex conjugate](https://en.wikipedia.org/wiki/Complex_conjugate) is defined to be the complex number with identical real part but an imaginary part reversed in sign." + "Also, a `conjugate()` method is bound to every `complex` object. The [complex conjugate](https://en.wikipedia.org/wiki/Complex_conjugate) is defined to be the complex number with identical real part but an imaginary part reversed in sign." ] }, { @@ -6111,17 +6111,17 @@ } }, "source": [ - "Analogous to the discussion of containers and iterables in [Chapter 4](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/04_iteration.ipynb#Containers-vs.-Iterables), we contrast the *concrete* numeric data types in this chapter with the *abstract* ideas behind [numbers in mathematics](https://en.wikipedia.org/wiki/Number).\n", + "Analogous to the discussion of *containers* and *iterables* in [Chapter 4](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/04_iteration.ipynb#Containers-vs.-Iterables), we contrast the *concrete* numeric data types in this chapter with the *abstract* ideas behind [numbers in mathematics](https://en.wikipedia.org/wiki/Number).\n", "\n", "The figure below summarizes five *major* sets of [numbers in mathematics](https://en.wikipedia.org/wiki/Number) as we know them from high school:\n", "\n", "- $\\mathbb{N}$: [Natural numbers](https://en.wikipedia.org/wiki/Natural_number) are all non-negative count numbers, e.g., $0, 1, 2, ...$\n", "- $\\mathbb{Z}$: [Integers](https://en.wikipedia.org/wiki/Integer) are all numbers *without* a fractional component, e.g., $-1, 0, 1, ...$\n", "- $\\mathbb{Q}$: [Rational numbers](https://en.wikipedia.org/wiki/Rational_number) are all numbers that can be expressed as a quotient of two integers, e.g., $-\\frac{1}{2}, 0, \\frac{1}{2}, ...$\n", - "- $\\mathbb{R}$: [Real numbers](https://en.wikipedia.org/wiki/Real_number) are all numbers that can be represented as a distance along a line (negative means \"reversed\"), e.g., $\\sqrt{2}, \\pi, \\text{e}, ...$\n", + "- $\\mathbb{R}$: [Real numbers](https://en.wikipedia.org/wiki/Real_number) are all numbers that can be represented as a distance along a line, and negative means \"reversed,\" e.g., $\\sqrt{2}, \\pi, \\text{e}, ...$\n", "- $\\mathbb{C}$: [Complex numbers](https://en.wikipedia.org/wiki/Complex_number) are all numbers of the form $a + b\\textbf{i}$ where $a$ and $b$ are real numbers and $\\textbf{i}$ is the [imaginary number](https://en.wikipedia.org/wiki/Imaginary_number), e.g., $0, \\textbf{i}, 1 + \\textbf{i}, ...$\n", "\n", - "In the listed order, the five sets are perfect subsets and $\\mathbb{C}$ is the largest set (to be precise, all sets are infinite, but they still have a different number of elements)." + "In the listed order, the five sets are perfect subsets of the respective following sets, and $\\mathbb{C}$ is the largest set (cf., the figure below illustrates that observation as well). To be precise, all sets are infinite, but they still have a different number of elements." ] }, { @@ -6143,13 +6143,13 @@ } }, "source": [ - "The *concrete* data types introduced in this chapter are all *imperfect* models of *abstract* mathematical ideas.\n", + "The data types introduced in this chapter are all *imperfect* models of *abstract* mathematical ideas.\n", "\n", - "The `int` and `Fraction` types are the models \"closest\" to the idea they implement: Whereas $\\mathbb{Z}$ and $\\mathbb{Q}$ are, by definition, infinite, every computer runs out of bits when representing sufficiently large integers or fractions with a sufficiently large number of decimals. However, within a system-dependent minimum and maximum integer range, we can model an integer or fraction without any loss in precision.\n", + "The `int` and `Fraction` types are the models \"closest\" to the idea they implement: Whereas $\\mathbb{Z}$ and $\\mathbb{Q}$ are, by definition, infinite, every computer runs out of bits when representing sufficiently large integers or fractions with a sufficiently large number of decimals. However, within a system-dependent range, we can model an integer or fraction without any loss in precision.\n", "\n", "For the other types, in particular, the `float` type, the implications of their imprecision are discussed in detail above.\n", "\n", - "The abstract concepts behind the four outer-most mathematical sets are part of Python since [PEP 3141](https://www.python.org/dev/peps/pep-3141/) in 2007. The [numbers](https://docs.python.org/3/library/numbers.html) module in the [standard library](https://docs.python.org/3/library/index.html) defines what programmers call the **[numerical tower](https://en.wikipedia.org/wiki/Numerical_tower)**, a collection of five **[abstract data types](https://en.wikipedia.org/wiki/Abstract_data_type)**, or **abstract base classes** as they are called in Python jargon:\n", + "The abstract concepts behind the four outer-most mathematical sets are formalized in Python since [PEP 3141](https://www.python.org/dev/peps/pep-3141/) in 2007. The [numbers](https://docs.python.org/3/library/numbers.html) module in the [standard library](https://docs.python.org/3/library/index.html) defines what programmers call the **[numerical tower](https://en.wikipedia.org/wiki/Numerical_tower)**, a collection of five **[abstract data types](https://en.wikipedia.org/wiki/Abstract_data_type)**, or **abstract base classes** (ABCs) as they are called in Python jargon:\n", "\n", "- `numbers.Number`: \"any number\" (cf., [documentation](https://docs.python.org/3/library/numbers.html#numbers.Number))\n", "- `numbers.Complex`: \"all complex numbers\" (cf., [documentation](https://docs.python.org/3/library/numbers.html#numbers.Complex))\n", @@ -6220,9 +6220,9 @@ "source": [ "As a reminder, the built-in [help()](https://docs.python.org/3/library/functions.html#help) function is always our friend.\n", "\n", - "The abstract types' docstrings are unsurprisingly similar to the corresponding concrete types' docstrings (for now, let's not worry about the dunder-style names in the docstrings).\n", + "The ABCs' docstrings are unsurprisingly similar to the corresponding data types' docstrings. For now, let's not worry about the dunder-style names in the docstrings.\n", "\n", - "For example, both `numbers.Complex` and `complex` list the `imag` and `real` attributes." + "For example, both `numbers.Complex` and `complex` list the `imag` and `real` attributes shown above." ] }, { @@ -6231,7 +6231,7 @@ "metadata": { "scrolled": true, "slideshow": { - "slide_type": "slide" + "slide_type": "skip" } }, "outputs": [ @@ -6336,17 +6336,6 @@ "help(numbers.Complex)" ] }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "skip" - } - }, - "source": [ - "For sure, Python understands the built-in types as literals." - ] - }, { "cell_type": "code", "execution_count": 201, @@ -6509,9 +6498,9 @@ } }, "source": [ - "One way to use *abstract* data types is to use them in place of a *concrete* data type.\n", + "The primary purpose of ABCs is to classify the *concrete* data types and standardize how they behave.\n", "\n", - "For example, we may pass them as arguments to the built-in [isinstance()](https://docs.python.org/3/library/functions.html#isinstance) function and check in which of the five mathematical sets the object `1 / 10` is." + "For, example, as all numeric data types are `Complex` numbers in the abstract sense, they all work with the built-in [abs()](https://docs.python.org/3/library/functions.html#abs) function (cf., [documentation](https://docs.python.org/3/library/numbers.html#numbers.Complex)). While it is intuitively clear what the [absolute value](https://en.wikipedia.org/wiki/Absolute_value) (i.e., \"distance\" to $0$) of an integer, a fraction, or any real number is, [abs()](https://docs.python.org/3/library/functions.html#abs) calculates the equivalent of that for complex numbers. That concept is called the [magnitude](https://en.wikipedia.org/wiki/Magnitude_%28mathematics%29) of a number, and is really a *generalization* of the absolute value." ] }, { @@ -6526,7 +6515,7 @@ { "data": { "text/plain": [ - "True" + "42" ] }, "execution_count": 202, @@ -6535,7 +6524,7 @@ } ], "source": [ - "isinstance(1 / 10, float)" + "abs(-42)" ] }, { @@ -6543,14 +6532,14 @@ "execution_count": 203, "metadata": { "slideshow": { - "slide_type": "fragment" + "slide_type": "-" } }, "outputs": [ { "data": { "text/plain": [ - "True" + "Decimal('0.1')" ] }, "execution_count": 203, @@ -6559,7 +6548,7 @@ } ], "source": [ - "isinstance(1 / 10, numbers.Number)" + "abs(Decimal(\"-0.1\"))" ] }, { @@ -6574,7 +6563,7 @@ { "data": { "text/plain": [ - "True" + "5.0" ] }, "execution_count": 204, @@ -6583,12 +6572,263 @@ } ], "source": [ - "isinstance(1 / 10, numbers.Complex)" + "abs(4 - 3j)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "On the contrary, only `Real` numbers in the abstract sense may be rounded with the built-in [round()](https://docs.python.org/3/library/functions.html#round) function." ] }, { "cell_type": "code", "execution_count": 205, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "42" + ] + }, + "execution_count": 205, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "round(42.1)" + ] + }, + { + "cell_type": "code", + "execution_count": 206, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "0" + ] + }, + "execution_count": 206, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "round(Decimal(\"0.1\"))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "`Complex` numbers are two-dimensional. So, rounding makes no sense and raises a `TypeError`." + ] + }, + { + "cell_type": "code", + "execution_count": 207, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "ename": "TypeError", + "evalue": "type complex doesn't define __round__ method", + "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[0mround\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m4\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0;36m3j\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: type complex doesn't define __round__ method" + ] + } + ], + "source": [ + "round(4 + 3j)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Knowing what ABCs a numeric type adheres to, is not only important in the context of built-ins. The [trunc()](https://docs.python.org/3/library/math.html#math.trunc) function from the [math](https://docs.python.org/3/library/math.html) module in the [standard library](https://docs.python.org/3/library/index.html), for example, only works with `Real` types (cf., [documentation](https://docs.python.org/3/library/numbers.html#numbers.Real))." + ] + }, + { + "cell_type": "code", + "execution_count": 208, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "import math" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "[trunc()](https://docs.python.org/3/library/math.html#math.trunc) cuts off a number's decimals." + ] + }, + { + "cell_type": "code", + "execution_count": 209, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "0" + ] + }, + "execution_count": 209, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "math.trunc(9 / 10)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "A `Complex` number leads to a `TypeError`." + ] + }, + { + "cell_type": "code", + "execution_count": 210, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "ename": "TypeError", + "evalue": "type complex doesn't define __trunc__ method", + "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[0mmath\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtrunc\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m0.9\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0;36m1j\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: type complex doesn't define __trunc__ method" + ] + } + ], + "source": [ + "math.trunc(0.9 + 1j)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Another way to use ABCs is in place of a *concrete* data type.\n", + "\n", + "For example, we may pass them as arguments to the built-in [isinstance()](https://docs.python.org/3/library/functions.html#isinstance) function and check in which of the five mathematical sets the object `1 / 10` is." + ] + }, + { + "cell_type": "code", + "execution_count": 211, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 211, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "isinstance(1 / 10, float) # we know that from before" + ] + }, + { + "cell_type": "code", + "execution_count": 212, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 212, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "isinstance(1 / 10, numbers.Number) # a float object is a Number in the abstract sense" + ] + }, + { + "cell_type": "code", + "execution_count": 213, "metadata": { "slideshow": { "slide_type": "-" @@ -6601,13 +6841,37 @@ "True" ] }, - "execution_count": 205, + "execution_count": 213, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "isinstance(1 / 10, numbers.Real)" + "isinstance(1 / 10, numbers.Complex) # float objects are always also Complex numbers" + ] + }, + { + "cell_type": "code", + "execution_count": 214, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 214, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "isinstance(1 / 10, numbers.Real) # a float object's purpose is to model a Real number" ] }, { @@ -6623,7 +6887,7 @@ }, { "cell_type": "code", - "execution_count": 206, + "execution_count": 215, "metadata": { "slideshow": { "slide_type": "slide" @@ -6636,13 +6900,13 @@ "False" ] }, - "execution_count": 206, + "execution_count": 215, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "isinstance(1 / 10, numbers.Rational)" + "isinstance(1 / 10, numbers.Rational) # the type of `1 / 10` is what is important, not its value" ] }, { @@ -6658,7 +6922,7 @@ }, { "cell_type": "code", - "execution_count": 207, + "execution_count": 216, "metadata": { "slideshow": { "slide_type": "fragment" @@ -6671,7 +6935,7 @@ "True" ] }, - "execution_count": 207, + "execution_count": 216, "metadata": {}, "output_type": "execute_result" } @@ -6682,7 +6946,7 @@ }, { "cell_type": "code", - "execution_count": 208, + "execution_count": 217, "metadata": { "slideshow": { "slide_type": "-" @@ -6695,7 +6959,7 @@ "False" ] }, - "execution_count": 208, + "execution_count": 217, "metadata": {}, "output_type": "execute_result" } @@ -6723,46 +6987,9 @@ } }, "source": [ - "Replacing *concrete* data types with *abstract* ones is particularly valuable in the context of input validation: The revised version of the `factorial()` function below allows its user to take advantage of *duck typing*: If a real but non-integer argument `n` is passed in, `factorial()` tries to cast `n` as an `int` object with the [trunc()](https://docs.python.org/3/library/math.html#math.trunc) function from the [math](https://docs.python.org/3/library/math.html) module in the [standard library](https://docs.python.org/3/library/index.html). [trunc()](https://docs.python.org/3/library/math.html#math.trunc) cuts off all decimals and any *concrete* numeric type implementing the *abstract* `numbers.Real` type supports it (cf., [documentation](https://docs.python.org/3/library/numbers.html#numbers.Real)).\n", + "Replacing *concrete* data types with ABCs is particularly valuable in the context of input validation: The revised version of the `factorial()` function below allows its user to take advantage of *duck typing*: If a real but non-integer argument `n` is passed in, `factorial()` tries to cast `n` as an `int` object with [math.trunc()](https://docs.python.org/3/library/math.html#math.trunc).\n", "\n", - "Two popular and distinguished Pythonistas, [Luciano Ramalho](https://github.com/ramalho) and [Alex Martelli](https://en.wikipedia.org/wiki/Alex_Martelli), coin the term **goose typing** to specifically mean using the built-in [isinstance()](https://docs.python.org/3/library/functions.html#isinstance) function with an *abstract base class* (cf., Chapter 11 in this [book](https://www.amazon.com/Fluent-Python-Concise-Effective-Programming/dp/1491946008) or this [summary](https://dgkim5360.github.io/blog/python/2017/07/duck-typing-vs-goose-typing-pythonic-interfaces/) thereof)." - ] - }, - { - "cell_type": "code", - "execution_count": 209, - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, - "outputs": [], - "source": [ - "import math" - ] - }, - { - "cell_type": "code", - "execution_count": 210, - "metadata": { - "slideshow": { - "slide_type": "-" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "0" - ] - }, - "execution_count": 210, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "math.trunc(1 / 10)" + "Two popular and distinguished Pythonistas, [Luciano Ramalho](https://github.com/ramalho) and [Alex Martelli](https://en.wikipedia.org/wiki/Alex_Martelli), coin the term **goose typing** to specifically mean using the built-in [isinstance()](https://docs.python.org/3/library/functions.html#isinstance) function with an ABC (cf., Chapter 11 in this [book](https://www.amazon.com/Fluent-Python-Concise-Effective-Programming/dp/1491946008) or this [summary](https://dgkim5360.github.io/blog/python/2017/07/duck-typing-vs-goose-typing-pythonic-interfaces/) thereof)." ] }, { @@ -6778,7 +7005,7 @@ }, { "cell_type": "code", - "execution_count": 211, + "execution_count": 218, "metadata": { "slideshow": { "slide_type": "slide" @@ -6810,7 +7037,7 @@ "\n", " if n < 0:\n", " raise ValueError(\"Factorial is not defined for negative integers\")\n", - " elif n == 0:\n", + " elif n == 0: # = base case\n", " return 1\n", " return n * factorial(n - 1)" ] @@ -6828,7 +7055,7 @@ }, { "cell_type": "code", - "execution_count": 212, + "execution_count": 219, "metadata": { "slideshow": { "slide_type": "slide" @@ -6841,7 +7068,7 @@ "1" ] }, - "execution_count": 212, + "execution_count": 219, "metadata": {}, "output_type": "execute_result" } @@ -6852,7 +7079,7 @@ }, { "cell_type": "code", - "execution_count": 213, + "execution_count": 220, "metadata": { "slideshow": { "slide_type": "-" @@ -6865,7 +7092,7 @@ "6" ] }, - "execution_count": 213, + "execution_count": 220, "metadata": {}, "output_type": "execute_result" } @@ -6876,7 +7103,7 @@ }, { "cell_type": "code", - "execution_count": 214, + "execution_count": 221, "metadata": { "slideshow": { "slide_type": "fragment" @@ -6889,7 +7116,7 @@ "6" ] }, - "execution_count": 214, + "execution_count": 221, "metadata": {}, "output_type": "execute_result" } @@ -6906,12 +7133,12 @@ } }, "source": [ - "With the keyword-only argument `strict`, we can control whether or not a passed in `float` object may be rounded. By default, this is not allowed and results in a `TypeError`." + "With the keyword-only argument `strict`, we can control whether or not a passed in `float` object may come with decimals that are then truncated. By default, this is not allowed and results in a `TypeError`." ] }, { "cell_type": "code", - "execution_count": 215, + "execution_count": 222, "metadata": { "slideshow": { "slide_type": "slide" @@ -6925,8 +7152,8 @@ "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[0mfactorial\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m3.1\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\u001b[0m in \u001b[0;36mfactorial\u001b[0;34m(n, strict)\u001b[0m\n\u001b[1;32m 16\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0misinstance\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mn\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mnumbers\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mReal\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 17\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mn\u001b[0m \u001b[0;34m!=\u001b[0m \u001b[0mmath\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtrunc\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mn\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mand\u001b[0m \u001b[0mstrict\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 18\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mTypeError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"n is not an integer-like value; it has decimals\"\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 19\u001b[0m \u001b[0mn\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mmath\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtrunc\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mn\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 20\u001b[0m \u001b[0;32melse\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;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mfactorial\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m3.1\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\u001b[0m in \u001b[0;36mfactorial\u001b[0;34m(n, strict)\u001b[0m\n\u001b[1;32m 16\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0misinstance\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mn\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mnumbers\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mReal\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 17\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mn\u001b[0m \u001b[0;34m!=\u001b[0m \u001b[0mmath\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtrunc\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mn\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mand\u001b[0m \u001b[0mstrict\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 18\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mTypeError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"n is not an integer-like value; it has decimals\"\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 19\u001b[0m \u001b[0mn\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mmath\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtrunc\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mn\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 20\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;31mTypeError\u001b[0m: n is not an integer-like value; it has decimals" ] } @@ -6935,9 +7162,20 @@ "factorial(3.1)" ] }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "In non-strict mode, the passed in `3.1` is truncated into `3` resulting in a factorial of `6`." + ] + }, { "cell_type": "code", - "execution_count": 216, + "execution_count": 223, "metadata": { "slideshow": { "slide_type": "fragment" @@ -6950,7 +7188,7 @@ "6" ] }, - "execution_count": 216, + "execution_count": 223, "metadata": {}, "output_type": "execute_result" } @@ -6967,12 +7205,12 @@ } }, "source": [ - "For `complex` numbers, `factorial()` still raises a `TypeError`." + "For `complex` numbers, `factorial()` still raises a `TypeError` because they are neither an `Integral` nor a `Real` number." ] }, { "cell_type": "code", - "execution_count": 217, + "execution_count": 224, "metadata": { "slideshow": { "slide_type": "slide" @@ -6986,8 +7224,8 @@ "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[0mfactorial\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m1\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0;36m2j\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\u001b[0m in \u001b[0;36mfactorial\u001b[0;34m(n, strict)\u001b[0m\n\u001b[1;32m 19\u001b[0m \u001b[0mn\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mmath\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtrunc\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mn\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 20\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 21\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mTypeError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Factorial is only defined for integers\"\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 22\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 23\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mn\u001b[0m \u001b[0;34m<\u001b[0m \u001b[0;36m0\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;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mfactorial\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m1\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0;36m2j\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\u001b[0m in \u001b[0;36mfactorial\u001b[0;34m(n, strict)\u001b[0m\n\u001b[1;32m 19\u001b[0m \u001b[0mn\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mmath\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtrunc\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mn\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 20\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 21\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mTypeError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Factorial is only defined for integers\"\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 22\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 23\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mn\u001b[0m \u001b[0;34m<\u001b[0m \u001b[0;36m0\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;31mTypeError\u001b[0m: Factorial is only defined for integers" ] } @@ -7016,20 +7254,20 @@ }, "source": [ "There exist three numeric types in core Python:\n", - "- `int`: a near-perfect model for whole numbers (i.e., the set $\\mathbb{Z}$); inherently precise\n", - "- `float`: the \"gold\" standard to approximate real numbers (i.e., the set $\\mathbb{R}$); inherently imprecise\n", - "- `complex`: layer on top of the `float` type; therefore inherently imprecise\n", + "- `int`: a near-perfect model for whole numbers (i.e., $\\mathbb{Z}$); inherently precise\n", + "- `float`: the \"gold\" standard to approximate real numbers (i.e., $\\mathbb{R}$); inherently imprecise\n", + "- `complex`: layer on top of the `float` type to approximate complex numbers (i.e., $\\mathbb{C}$); inherently imprecise\n", "\n", - "Furthermore, the [standard library](https://docs.python.org/3/library/index.html) adds two more types that can be used as substitutes for `float` objects:\n", + "Furthermore, the [standard library](https://docs.python.org/3/library/index.html) provides two more types that can be used as substitutes for the `float` type:\n", "- `Decimal`: similar to `float` but allows customizing the precision; still inherently imprecise\n", - "- `Fraction`: a near-perfect model for rational numbers (i.e., the set $\\mathbb{Q}$); built on top of the `int` type and therefore inherently precise\n", + "- `Fraction`: a near-perfect model for rational numbers (i.e., $\\mathbb{Q}$); built on top of the `int` type and therefore inherently precise\n", "\n", "The *important* takeaways for the data science practitioner are:\n", "\n", "1. **Do not mix** precise and imprecise data types, and\n", "2. actively expect `nan` results when working with `float` numbers as there are no **loud failures**.\n", "\n", - "The **numerical tower** is Python's way of implementing the various **abstract** ideas of what numbers are in mathematics." + "The **numerical tower** is Python's way of implementing various **abstract** ideas of what numbers are in mathematics." ] } ], diff --git a/05_numbers_review_and_exercises.ipynb b/05_numbers_review_and_exercises.ipynb index 90983d4..2ae8301 100644 --- a/05_numbers_review_and_exercises.ipynb +++ b/05_numbers_review_and_exercises.ipynb @@ -156,7 +156,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "**Q9**: How can **abstract data types**, for example, as defined in the **numerical tower**, be helpful in enabling **duck typing**?" + "**Q9**: How can **abstract base classes**, for example, as defined in the **numerical tower**, be helpful in enabling **duck typing**?" ] }, { @@ -519,7 +519,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "**Q19.7**: Re-write the function `discounted_price()` from [Chapter 3's Review & Exercises](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/03_conditionals_review_and_exercises.ipynb#Discounting-Customer-Orders) section!\n", + "**Q19.7**: Rewrite the function `discounted_price()` from [Chapter 3's Review & Exercises](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/03_conditionals_review_and_exercises.ipynb#Discounting-Customer-Orders) section!\n", "\n", "It takes the *positional* arguments `unit_price` and `quantity` and implements a discount scheme for a line item in a customer order as follows:\n", "\n", @@ -530,7 +530,7 @@ "\n", "The function then returns the overall price for the line item as a `Decimal` number with a precision of *two* decimals.\n", "\n", - "Enable **duck typing** by allowing the function to be called with various numeric types as the arguments, in particular, `quantity` may be a non-integer as well: Use an appropriate **abstract data type** from the [numbers](https://docs.python.org/3/library/numbers.html) module in the [standard library](https://docs.python.org/3/library/index.html) to verify the arguments' types and also that they are both positive!\n", + "Enable **duck typing** by allowing the function to be called with various numeric types as the arguments, in particular, `quantity` may be a non-integer as well: Use an appropriate **abstract base class** from the [numbers](https://docs.python.org/3/library/numbers.html) module in the [standard library](https://docs.python.org/3/library/index.html) to verify the arguments' types and also that they are both positive!\n", "\n", "It is considered a *best practice* to only round towards the *end* of the calculations." ] @@ -551,22 +551,6 @@ "outputs": [], "source": [ "def discounted_price(...):\n", - " ...\n", - " ...\n", - " ...\n", - " ...\n", - " ...\n", - " ...\n", - " ...\n", - " ...\n", - " ...\n", - " ...\n", - " ...\n", - " ...\n", - " ...\n", - " ...\n", - " ...\n", - " ...\n", " ..." ] }, diff --git a/06_text.ipynb b/06_text.ipynb index 44eaa3c..3fbbcb5 100644 --- a/06_text.ipynb +++ b/06_text.ipynb @@ -54,7 +54,7 @@ }, "outputs": [], "source": [ - "school = \"WHU - Otto Beisheim School of Management\"" + "text = \"Lorem ipsum dolor sit amet, consectetur ...\"" ] }, { @@ -65,7 +65,7 @@ } }, "source": [ - "Like everything in Python, `school` is an object." + "Like everything in Python, `text` is an object." ] }, { @@ -80,7 +80,7 @@ { "data": { "text/plain": [ - "140488988107952" + "139674889049360" ] }, "execution_count": 2, @@ -89,7 +89,7 @@ } ], "source": [ - "id(school)" + "id(text)" ] }, { @@ -113,7 +113,7 @@ } ], "source": [ - "type(school)" + "type(text)" ] }, { @@ -141,7 +141,7 @@ { "data": { "text/plain": [ - "'WHU - Otto Beisheim School of Management'" + "'Lorem ipsum dolor sit amet, consectetur ...'" ] }, "execution_count": 4, @@ -150,7 +150,7 @@ } ], "source": [ - "school" + "text" ] }, { @@ -182,7 +182,7 @@ { "data": { "text/plain": [ - "\"It's cool that strings are so versatile in Python!\"" + "\"It's cool that strings are so versatile!\"" ] }, "execution_count": 5, @@ -191,7 +191,7 @@ } ], "source": [ - "\"It's cool that strings are so versatile in Python!\"" + "\"It's cool that strings are so versatile!\"" ] }, { @@ -206,7 +206,7 @@ { "data": { "text/plain": [ - "\"It's cool that strings are so versatile in Python!\"" + "\"It's cool that strings are so versatile!\"" ] }, "execution_count": 6, @@ -215,7 +215,7 @@ } ], "source": [ - "'It\\'s cool that strings are so versatile in Python!'" + "'It\\'s cool that strings are so versatile!'" ] }, { @@ -623,7 +623,7 @@ "execution_count": 20, "metadata": { "slideshow": { - "slide_type": "slide" + "slide_type": "skip" } }, "outputs": [ @@ -660,7 +660,7 @@ "execution_count": 21, "metadata": { "slideshow": { - "slide_type": "fragment" + "slide_type": "skip" } }, "outputs": [ @@ -737,7 +737,7 @@ } }, "source": [ - "A **sequence** is yet another *abstract* concept (cf., *containers* and *iterables* in [Chapter 4](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/04_iteration.ipynb#Containers-vs.-Iterables)).\n", + "A **sequence** is yet another *abstract* concept (cf., the \"*Containers vs. Iterables*\" section in [Chapter 4](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/04_iteration.ipynb#Containers-vs.-Iterables)).\n", "\n", "It unifies *four* [orthogonal](https://en.wikipedia.org/wiki/Orthogonality) (i.e., \"independent\") behaviors into one idea: Any data type, such as `str`, is considered a sequence if it simultaneously\n", "\n", @@ -748,7 +748,7 @@ "\n", "[Chapter 7](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/07_sequences.ipynb#Collections-vs.-Sequences) formalizes sequences in great detail. Here, we keep our focus on the `str` type that historically received its name as it models a \"**[string of characters](https://en.wikipedia.org/wiki/String_%28computer_science%29)**,\" and a \"string\" is more formally called a sequence in the computer science literature.\n", "\n", - "Behaving like a sequence, `str` objects may be treated like `list` objects in many cases. For example, the built-in [len()](https://docs.python.org/3/library/functions.html#len) function tells us how many elements (i.e., characters) make up `school`. [len()](https://docs.python.org/3/library/functions.html#len) would not work on an \"*infinite*\" object: As anything modeled in a program must fit into a computer's finite memory at runtime, there cannot exist objects containing a truly infinite number of elements; however, [Chapter 7](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/07_sequences.ipynb#Collections-vs.-Sequences#Mapping) introduces *concrete* iterable data types that can be used to model an *infinite* series of elements and that, consequently, have no concept of \"length.\"" + "Behaving like a sequence, `str` objects may be treated like `list` objects in many cases. For example, the built-in [len()](https://docs.python.org/3/library/functions.html#len) function tells us how many elements (i.e., characters) make up `text`. [len()](https://docs.python.org/3/library/functions.html#len) would not work with an *infinite* object: As anything modeled in a program must fit into a computer's finite memory at runtime, there cannot exist objects containing a truly infinite number of elements; however, [Chapter 7](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/07_sequences.ipynb#Iterators-vs.-Iterables) introduces iterable data types that can be used to model an *infinite* series of elements and that, consequently, have no concept of \"length.\"" ] }, { @@ -763,7 +763,7 @@ { "data": { "text/plain": [ - "40" + "43" ] }, "execution_count": 23, @@ -772,7 +772,7 @@ } ], "source": [ - "len(school)" + "len(text)" ] }, { @@ -783,9 +783,7 @@ } }, "source": [ - "Being iterable, we can iterate over a `str` object, for example, with a `for`-loop, and do something with the individual characters, for example, print them out with extra space in between them.\n", - "\n", - "[Chapter 4](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/04_iteration.ipynb#Containers-vs.-Iterables) already shows that we can loop over *different* concrete types: The example there first loops over the `list` object `[0, 1, 2, 3, 4]` and then the `range` object `range(5)`, with the same outcome. Now, we add the `str` type to the list of *concrete* types we can loop over. *Abstractly* speaking, all three are *iterable*, and there are many more iterable types in Python." + "Being iterable, we may loop over `text` and do something with the individual characters, for example, print them out with extra space in between them." ] }, { @@ -801,12 +799,12 @@ "name": "stdout", "output_type": "stream", "text": [ - "W H U - O t t o B e i s h e i m S c h o o l o f M a n a g e m e n t " + "L o r e m i p s u m d o l o r s i t a m e t , c o n s e c t e t u r . . . " ] } ], "source": [ - "for letter in school:\n", + "for letter in text:\n", " print(letter, end=\" \")" ] }, @@ -818,7 +816,9 @@ } }, "source": [ - "Being a container, we can check if a given object is a member of a sequence with the `in` operator. In the context of `str` objects, the `in` operator has *two* usages: First, it checks if a *single* character is contained in a `str` object. Second, it may also check if a shorter `str` object, then called a **substring**, is contained in a longer one." + "Being a container, we may check if a given `str` object is contained in `text` with the `in` operator.\n", + "\n", + "The `in` operator has *two* usages: First, it checks if a *single* character is contained in a `str` object. Second, it may also check if a shorter `str` object, then called a **substring**, is contained in a longer one." ] }, { @@ -842,7 +842,7 @@ } ], "source": [ - "\"O\" in school" + "\"L\" in text" ] }, { @@ -866,7 +866,7 @@ } ], "source": [ - "\"WHU\" in school" + "\"ipsum\" in text" ] }, { @@ -890,7 +890,7 @@ } ], "source": [ - "\"EBS\" in school" + "\"veni, vidi, vici\" in text" ] }, { @@ -927,7 +927,7 @@ { "data": { "text/plain": [ - "'W'" + "'L'" ] }, "execution_count": 28, @@ -936,7 +936,7 @@ } ], "source": [ - "school[0]" + "text[0]" ] }, { @@ -951,7 +951,7 @@ { "data": { "text/plain": [ - "'H'" + "'o'" ] }, "execution_count": 29, @@ -960,7 +960,7 @@ } ], "source": [ - "school[1]" + "text[1]" ] }, { @@ -971,7 +971,7 @@ } }, "source": [ - "The index must be of type `int`." + "The index must be of type `int` or we get a `TypeError`." ] }, { @@ -979,7 +979,7 @@ "execution_count": 30, "metadata": { "slideshow": { - "slide_type": "slide" + "slide_type": "skip" } }, "outputs": [ @@ -990,13 +990,13 @@ "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[0mschool\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m1.0\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\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mtext\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m1.0\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: string indices must be integers" ] } ], "source": [ - "school[1.0]" + "text[1.0]" ] }, { @@ -1015,14 +1015,14 @@ "execution_count": 31, "metadata": { "slideshow": { - "slide_type": "slide" + "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ - "'t'" + "'.'" ] }, "execution_count": 31, @@ -1031,7 +1031,7 @@ } ], "source": [ - "school[39]" + "text[42] # = len(text) - 1" ] }, { @@ -1061,13 +1061,13 @@ "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mIndexError\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[0mschool\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m40\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\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mtext\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m43\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;31m# = len(text)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mIndexError\u001b[0m: string index out of range" ] } ], "source": [ - "school[40]" + "text[43] # = len(text)" ] }, { @@ -1078,7 +1078,7 @@ } }, "source": [ - "We may use *negative* indexes to start counting from the end of the `str` object." + "We may use *negative* indexes to start counting from the end of the `str` object. That only works because sequences are *finite*." ] }, { @@ -1093,7 +1093,7 @@ { "data": { "text/plain": [ - "'t'" + "'.'" ] }, "execution_count": 33, @@ -1102,7 +1102,7 @@ } ], "source": [ - "school[-1]" + "text[-1]" ] }, { @@ -1128,7 +1128,7 @@ { "data": { "text/plain": [ - "'O'" + "'i'" ] }, "execution_count": 34, @@ -1137,7 +1137,7 @@ } ], "source": [ - "school[6]" + "text[6]" ] }, { @@ -1152,7 +1152,7 @@ { "data": { "text/plain": [ - "'O'" + "'i'" ] }, "execution_count": 35, @@ -1161,7 +1161,7 @@ } ], "source": [ - "school[-34]" + "text[-37]" ] }, { @@ -1185,7 +1185,9 @@ "source": [ "A **slice** is a substring of a `str` object.\n", "\n", - "The **slicing operator** is a generalization of the indexing operator: We can put one, two, or three integers within the brackets, separated by colons `:`. The three integers are then referred to as the *start*, *end*, and *step* values." + "The **slicing operator** is a generalization of the indexing operator: We can put one, two, or three integers within the brackets, separated by colons `:`. The three integers are then referred to as the *start*, *end*, and *step* values.\n", + "\n", + "Let's start with two integers, *start* and *end*." ] }, { @@ -1200,7 +1202,7 @@ { "data": { "text/plain": [ - "'WHU'" + "'Lorem'" ] }, "execution_count": 36, @@ -1209,7 +1211,7 @@ } ], "source": [ - "school[0:3]" + "text[0:5]" ] }, { @@ -1220,7 +1222,9 @@ } }, "source": [ - "Whereas the *start* is always included in the result, the *end* is not. Counter-intuitive at first, this makes working with individual slices easier as they add up to the original `str` object again. As the *end* is not included, we must end the second slice with `len(school)` or `40` below." + "Whereas the *start* is always included in the result, the *end* is not. Counter-intuitive at first, this makes working with individual slices easier as they add up to the original `str` object again.\n", + "\n", + "So, as the *end* is *not* included, we must end the second slice with `len(text)` or `43` below." ] }, { @@ -1235,7 +1239,7 @@ { "data": { "text/plain": [ - "'WHU - Otto Beisheim School of Management'" + "'Lorem ipsum dolor sit amet, consectetur ...'" ] }, "execution_count": 37, @@ -1244,7 +1248,7 @@ } ], "source": [ - "school[0:3] + school[3:40]" + "text[0:5] + text[5:len(text)]" ] }, { @@ -1255,7 +1259,7 @@ } }, "source": [ - "For convenience, the indexes do not need to lie in the range from 0 to the `str` object's \"length\" when slicing. This is *not* the case for indexing as the `IndexError` above shows." + "For convenience, the indexes do not need to lie within the range from `0` to `len(text)` when slicing." ] }, { @@ -1270,7 +1274,7 @@ { "data": { "text/plain": [ - "'WHU - Otto Beisheim School of Management'" + "'Lorem ipsum dolor sit amet, consectetur ...'" ] }, "execution_count": 38, @@ -1279,7 +1283,7 @@ } ], "source": [ - "school[0:999]" + "text[0:999]" ] }, { @@ -1305,7 +1309,7 @@ { "data": { "text/plain": [ - "'WHU - Otto Beisheim School of Management'" + "'Lorem ipsum dolor sit amet, consectetur ...'" ] }, "execution_count": 39, @@ -1314,7 +1318,7 @@ } ], "source": [ - "school[:3] + school[3:]" + "text[:5] + text[5:]" ] }, { @@ -1340,7 +1344,7 @@ { "data": { "text/plain": [ - "'WHU Otto Beisheim School'" + "'Lorem ipsum dolor sit amet.'" ] }, "execution_count": 40, @@ -1349,7 +1353,7 @@ } ], "source": [ - "school[:3] + school[5:26]" + "text[:26] + text[42]" ] }, { @@ -1375,7 +1379,7 @@ { "data": { "text/plain": [ - "'WU-Ot esemSho fMngmn'" + "'Lrmismdlrstae,cnettr..'" ] }, "execution_count": 41, @@ -1384,7 +1388,7 @@ } ], "source": [ - "school[::2]" + "text[::2]" ] }, { @@ -1410,7 +1414,7 @@ { "data": { "text/plain": [ - "'tnemeganaM fo loohcS miehsieB ottO - UHW'" + "'... rutetcesnoc ,tema tis rolod muspi meroL'" ] }, "execution_count": 42, @@ -1419,7 +1423,7 @@ } ], "source": [ - "school[::-1]" + "text[::-1]" ] }, { @@ -1443,9 +1447,9 @@ "source": [ "Whereas elements of a `list` object *may* be *re-assigned*, as shortly hinted at in [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb#Who-am-I?-And-how-many?), this is *not* allowed for `str` objects. Once created, they *cannot* be *changed*. Formally, we say that they are **immutable**. In that regard, `str` objects and all the numeric types in [Chapter 5](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/05_numbers.ipynb) are alike.\n", "\n", - "On the contrary, objects that may be changed after creation, are called **mutable**. We already saw in [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb#Who-am-I?-And-how-many?) how mutable objects are more difficult to reason about for a beginner, in particular, if more than *one* variable point to one. Yet, mutability does have its place in a programmer's toolbox, and we revisit this idea in the next chapters.\n", + "On the contrary, objects that may be changed after creation, are called **mutable**. We already saw in [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb#Who-am-I?-And-how-many?) how mutable objects are more difficult to reason about for a beginner, in particular, if more than *one* variable references it. Yet, mutability does have its place in a programmer's toolbox, and we revisit this idea in the next chapters.\n", "\n", - "`str` objects are *immutable* as the `TypeError` indicates: Assignment to an index is *not* supported by this type." + "The `TypeError` indicates that `str` objects are *immutable*: Assignment to an index or a slice are *not* supported." ] }, { @@ -1464,66 +1468,18 @@ "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[0mschool\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m\"E\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mtext\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m\"Z\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mTypeError\u001b[0m: 'str' object does not support item assignment" ] } ], "source": [ - "school[0] = \"E\"" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "skip" - } - }, - "source": [ - "The only thing we can do is to create a *new* `str` object in memory." + "text[0] = \"Z\"" ] }, { "cell_type": "code", "execution_count": 44, - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, - "outputs": [], - "source": [ - "new_school = \"EBS\" + school[3:]" - ] - }, - { - "cell_type": "code", - "execution_count": 45, - "metadata": { - "slideshow": { - "slide_type": "fragment" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "'EBS - Otto Beisheim School of Management'" - ] - }, - "execution_count": 45, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "new_school" - ] - }, - { - "cell_type": "code", - "execution_count": 46, "metadata": { "slideshow": { "slide_type": "-" @@ -1531,125 +1487,19 @@ }, "outputs": [ { - "data": { - "text/plain": [ - "140488987187120" - ] - }, - "execution_count": 46, - "metadata": {}, - "output_type": "execute_result" + "ename": "TypeError", + "evalue": "'str' object does not support item assignment", + "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[0mtext\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;36m5\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m\"random\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;31mTypeError\u001b[0m: 'str' object does not support item assignment" + ] } ], "source": [ - "id(new_school)" - ] - }, - { - "cell_type": "code", - "execution_count": 47, - "metadata": { - "slideshow": { - "slide_type": "-" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "140488988107952" - ] - }, - "execution_count": 47, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "id(school)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, - "source": [ - "## String Operations" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "skip" - } - }, - "source": [ - "As mentioned before, the `+` and `*` operators are *overloaded* and used for **string concatenation**." - ] - }, - { - "cell_type": "code", - "execution_count": 48, - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, - "outputs": [], - "source": [ - "greeting = \"Hello \"" - ] - }, - { - "cell_type": "code", - "execution_count": 49, - "metadata": { - "slideshow": { - "slide_type": "-" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "'Hello WHU'" - ] - }, - "execution_count": 49, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "greeting + school[:3]" - ] - }, - { - "cell_type": "code", - "execution_count": 50, - "metadata": { - "slideshow": { - "slide_type": "-" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "'WHU WHU WHU WHU WHU WHU WHU WHU WHU WHU '" - ] - }, - "execution_count": 50, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "10 * school[:4]" + "text[:5] = \"random\"" ] }, { @@ -1678,7 +1528,7 @@ }, { "cell_type": "code", - "execution_count": 51, + "execution_count": 45, "metadata": { "slideshow": { "slide_type": "slide" @@ -1688,21 +1538,45 @@ { "data": { "text/plain": [ - "6" + "'Lorem ipsum dolor sit amet, consectetur ...'" ] }, - "execution_count": 51, + "execution_count": 45, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "school.find(\"O\")" + "text" ] }, { "cell_type": "code", - "execution_count": 52, + "execution_count": 46, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "22" + ] + }, + "execution_count": 46, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "text.find(\"a\")" + ] + }, + { + "cell_type": "code", + "execution_count": 47, "metadata": { "slideshow": { "slide_type": "-" @@ -1715,18 +1589,18 @@ "-1" ] }, - "execution_count": 52, + "execution_count": 47, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "school.find(\"Z\")" + "text.find(\"z\")" ] }, { "cell_type": "code", - "execution_count": 53, + "execution_count": 48, "metadata": { "slideshow": { "slide_type": "fragment" @@ -1736,16 +1610,16 @@ { "data": { "text/plain": [ - "11" + "12" ] }, - "execution_count": 53, + "execution_count": 48, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "school.find(\"Beisheim\")" + "text.find(\"dolor\")" ] }, { @@ -1761,7 +1635,7 @@ }, { "cell_type": "code", - "execution_count": 54, + "execution_count": 49, "metadata": { "slideshow": { "slide_type": "slide" @@ -1771,21 +1645,21 @@ { "data": { "text/plain": [ - "12" + "1" ] }, - "execution_count": 54, + "execution_count": 49, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "school.find(\"e\")" + "text.find(\"o\")" ] }, { "cell_type": "code", - "execution_count": 55, + "execution_count": 50, "metadata": { "slideshow": { "slide_type": "fragment" @@ -1795,24 +1669,24 @@ { "data": { "text/plain": [ - "16" + "13" ] }, - "execution_count": 55, + "execution_count": 50, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "school.find(\"e\", 13) # 13 not 12 as otherwise the same character is found" + "text.find(\"o\", 2) # 2 not 1 as otherwise the same \"o\" is found again" ] }, { "cell_type": "code", - "execution_count": 56, + "execution_count": 51, "metadata": { "slideshow": { - "slide_type": "-" + "slide_type": "skip" } }, "outputs": [ @@ -1822,13 +1696,13 @@ "-1" ] }, - "execution_count": 56, + "execution_count": 51, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "school.find(\"e\", 13, 15) # \"e\" does not occur in the specified slice" + "text.find(\"o\", 2, 12) # \"o\" does not occur in the specified slice" ] }, { @@ -1844,7 +1718,7 @@ }, { "cell_type": "code", - "execution_count": 57, + "execution_count": 52, "metadata": { "slideshow": { "slide_type": "slide" @@ -1854,16 +1728,16 @@ { "data": { "text/plain": [ - "4" + "1" ] }, - "execution_count": 57, + "execution_count": 52, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "school.count(\"o\")" + "text.count(\"l\")" ] }, { @@ -1874,12 +1748,12 @@ } }, "source": [ - "As [count()](https://docs.python.org/3/library/stdtypes.html#str.count) is *case-sensitive*, we must **chain** it with the [lower()](https://docs.python.org/3/library/stdtypes.html#str.lower) method to get the count of all \"O\"s and \"o\"s." + "As [count()](https://docs.python.org/3/library/stdtypes.html#str.count) is *case-sensitive*, we must **chain** it with the [lower()](https://docs.python.org/3/library/stdtypes.html#str.lower) method to get the count of all `\"L\"`s and `\"l\"`s." ] }, { "cell_type": "code", - "execution_count": 58, + "execution_count": 53, "metadata": { "slideshow": { "slide_type": "fragment" @@ -1889,16 +1763,16 @@ { "data": { "text/plain": [ - "5" + "2" ] }, - "execution_count": 58, + "execution_count": 53, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "school.lower().count(\"o\")" + "text.lower().count(\"l\")" ] }, { @@ -1909,12 +1783,12 @@ } }, "source": [ - "Alternatively, we may use the [upper()](https://docs.python.org/3/library/stdtypes.html#str.upper) method and search for \"O\"s." + "Alternatively, we may use the [upper()](https://docs.python.org/3/library/stdtypes.html#str.upper) method and search for `\"O\"`s." ] }, { "cell_type": "code", - "execution_count": 59, + "execution_count": 54, "metadata": { "slideshow": { "slide_type": "skip" @@ -1924,16 +1798,16 @@ { "data": { "text/plain": [ - "5" + "2" ] }, - "execution_count": 59, + "execution_count": 54, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "school.upper().count(\"O\")" + "text.upper().count(\"L\")" ] }, { @@ -1944,12 +1818,12 @@ } }, "source": [ - "Because `str` objects are *immutable*, the methods always return *new* objects, even if a method does *not* change the value of the `str` object at all." + "Because `str` objects are *immutable*, [upper()](https://docs.python.org/3/library/stdtypes.html#str.upper) and [lower()](https://docs.python.org/3/library/stdtypes.html#str.lower) return *new* `str` objects, even if they do *not* change the value of the original `str` object." ] }, { "cell_type": "code", - "execution_count": 60, + "execution_count": 55, "metadata": { "slideshow": { "slide_type": "slide" @@ -1957,12 +1831,12 @@ }, "outputs": [], "source": [ - "example = \"test\"" + "example = \"random\"" ] }, { "cell_type": "code", - "execution_count": 61, + "execution_count": 56, "metadata": { "slideshow": { "slide_type": "-" @@ -1972,10 +1846,10 @@ { "data": { "text/plain": [ - "140489068668384" + "139674947440512" ] }, - "execution_count": 61, + "execution_count": 56, "metadata": {}, "output_type": "execute_result" } @@ -1986,7 +1860,7 @@ }, { "cell_type": "code", - "execution_count": 62, + "execution_count": 57, "metadata": { "slideshow": { "slide_type": "fragment" @@ -1999,7 +1873,7 @@ }, { "cell_type": "code", - "execution_count": 63, + "execution_count": 58, "metadata": { "slideshow": { "slide_type": "-" @@ -2009,10 +1883,10 @@ { "data": { "text/plain": [ - "140488987340848" + "139674887826488" ] }, - "execution_count": 63, + "execution_count": 58, "metadata": {}, "output_type": "execute_result" } @@ -2034,7 +1908,7 @@ }, { "cell_type": "code", - "execution_count": 64, + "execution_count": 59, "metadata": { "slideshow": { "slide_type": "fragment" @@ -2047,7 +1921,7 @@ "False" ] }, - "execution_count": 64, + "execution_count": 59, "metadata": {}, "output_type": "execute_result" } @@ -2058,7 +1932,7 @@ }, { "cell_type": "code", - "execution_count": 65, + "execution_count": 60, "metadata": { "slideshow": { "slide_type": "-" @@ -2071,7 +1945,7 @@ "True" ] }, - "execution_count": 65, + "execution_count": 60, "metadata": {}, "output_type": "execute_result" } @@ -2088,12 +1962,14 @@ } }, "source": [ - "Another popular string method is [split()](https://docs.python.org/3/library/stdtypes.html#str.split): It separates a longer `str` object into a list of smaller ones. By default, groups of whitespace are used as the *separator*." + "Another popular string method is [split()](https://docs.python.org/3/library/stdtypes.html#str.split): It separates a longer `str` object into smaller ones contained in a `list` object. By default, groups of contiguous whitespace are used as the *separator*.\n", + "\n", + "As an example, we use [split()](https://docs.python.org/3/library/stdtypes.html#str.split) to print out the individual words in `text` on separate lines." ] }, { "cell_type": "code", - "execution_count": 66, + "execution_count": 61, "metadata": { "slideshow": { "slide_type": "slide" @@ -2104,18 +1980,18 @@ "name": "stdout", "output_type": "stream", "text": [ - "WHU\n", - "-\n", - "Otto\n", - "Beisheim\n", - "School\n", - "of\n", - "Management\n" + "Lorem\n", + "ipsum\n", + "dolor\n", + "sit\n", + "amet,\n", + "consectetur\n", + "...\n" ] } ], "source": [ - "for word in school.split():\n", + "for word in text.split():\n", " print(word)" ] }, @@ -2132,7 +2008,7 @@ }, { "cell_type": "code", - "execution_count": 67, + "execution_count": 62, "metadata": { "slideshow": { "slide_type": "slide" @@ -2145,7 +2021,7 @@ }, { "cell_type": "code", - "execution_count": 68, + "execution_count": 63, "metadata": { "slideshow": { "slide_type": "-" @@ -2158,7 +2034,7 @@ }, { "cell_type": "code", - "execution_count": 69, + "execution_count": 64, "metadata": { "slideshow": { "slide_type": "-" @@ -2171,7 +2047,7 @@ "'This will become a sentence'" ] }, - "execution_count": 69, + "execution_count": 64, "metadata": {}, "output_type": "execute_result" } @@ -2193,7 +2069,7 @@ }, { "cell_type": "code", - "execution_count": 70, + "execution_count": 65, "metadata": { "slideshow": { "slide_type": "fragment" @@ -2206,7 +2082,7 @@ "'a b c d e'" ] }, - "execution_count": 70, + "execution_count": 65, "metadata": {}, "output_type": "execute_result" } @@ -2223,12 +2099,12 @@ } }, "source": [ - "The [replace()](https://docs.python.org/3/library/stdtypes.html#str.replace) method creates a *new* `str` object with parts of the text replaced." + "The [replace()](https://docs.python.org/3/library/stdtypes.html#str.replace) method creates a *new* `str` object with parts of the original `str` object potentially replaced." ] }, { "cell_type": "code", - "execution_count": 71, + "execution_count": 66, "metadata": { "slideshow": { "slide_type": "slide" @@ -2241,7 +2117,7 @@ "'This is a sentence'" ] }, - "execution_count": 71, + "execution_count": 66, "metadata": {}, "output_type": "execute_result" } @@ -2258,7 +2134,7 @@ } }, "source": [ - "## String Comparison" + "## String Operations" ] }, { @@ -2269,12 +2145,82 @@ } }, "source": [ - "The *relational* operators also work with `str` objects, another example of operator overloading. Comparison is done one character at a time until the first pair differs or one operand ends. However, `str` objects are sorted in a \"weird\" way. The reason for this is that computers store characters internally as numbers (i.e., $0$s and $1$s). Depending on the character encoding, these numbers vary. Commonly, characters and symbols used in the American language are encoded with the numbers 0 through 127, the so-called [ASCII standard](https://en.wikipedia.org/wiki/ASCII). However, Python works with the more general [Unicode/UTF-8 standard](https://en.wikipedia.org/wiki/UTF-8) that understands every language ever used by humans, even emojis." + "As mentioned in [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb#Operator-Overloading), the `+` and `*` operators are *overloaded* and used for **string concatenation**. They always create *new* `str` objects. That has nothing to do with the `str` type's immutability, but is the default behavior of operators." ] }, { "cell_type": "code", - "execution_count": 72, + "execution_count": 67, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "'Hello Lore'" + ] + }, + "execution_count": 67, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "\"Hello \" + text[:4]" + ] + }, + { + "cell_type": "code", + "execution_count": 68, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "'Lorem ipsum Lorem ipsum Lorem ipsum Lorem ipsum Lorem ipsum '" + ] + }, + "execution_count": 68, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "5 * text[:12]" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "### String Comparison" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "The *relational* operators also work with `str` objects, another example of operator overloading. Comparison is done one character at a time until the first pair differs or one operand ends. However, `str` objects are sorted in a \"weird\" way. The reason for this is that computers store characters internally as numbers (i.e., $0$s and $1$s). Depending on the character encoding, these numbers vary. Commonly, characters and symbols used in American English are encoded with the numbers 0 through 127, the so-called [ASCII standard](https://en.wikipedia.org/wiki/ASCII). However, Python works with the more general [Unicode/UTF-8 standard](https://en.wikipedia.org/wiki/UTF-8) that understands every language ever used by humans, even emojis." + ] + }, + { + "cell_type": "code", + "execution_count": 69, "metadata": { "slideshow": { "slide_type": "slide" @@ -2289,7 +2235,7 @@ }, { "cell_type": "code", - "execution_count": 73, + "execution_count": 70, "metadata": { "slideshow": { "slide_type": "fragment" @@ -2302,7 +2248,7 @@ "True" ] }, - "execution_count": 73, + "execution_count": 70, "metadata": {}, "output_type": "execute_result" } @@ -2313,7 +2259,7 @@ }, { "cell_type": "code", - "execution_count": 74, + "execution_count": 71, "metadata": { "slideshow": { "slide_type": "-" @@ -2326,7 +2272,7 @@ "False" ] }, - "execution_count": 74, + "execution_count": 71, "metadata": {}, "output_type": "execute_result" } @@ -2348,7 +2294,7 @@ }, { "cell_type": "code", - "execution_count": 75, + "execution_count": 72, "metadata": { "slideshow": { "slide_type": "fragment" @@ -2361,7 +2307,7 @@ "True" ] }, - "execution_count": 75, + "execution_count": 72, "metadata": {}, "output_type": "execute_result" } @@ -2378,12 +2324,12 @@ } }, "source": [ - "To provide a simple intuition for the \"weird\" sorting above, let's think of the American alphabet as being represented by the numbers as listed below. Then `\"Banana\"` is clearly \"smaller\" than `\"apple\"`." + "To provide a simple intuition for the \"weird\" sorting above, let's think of the alphabet as being represented by the numbers as listed below. Then `\"Banana\"` is clearly \"smaller\" than `\"apple\"`. In general, all the upper case letters are \"smaller\" than all the lower case letters." ] }, { "cell_type": "code", - "execution_count": 76, + "execution_count": 73, "metadata": { "slideshow": { "slide_type": "slide" @@ -2479,7 +2425,7 @@ }, { "cell_type": "code", - "execution_count": 77, + "execution_count": 74, "metadata": { "slideshow": { "slide_type": "slide" @@ -2493,7 +2439,7 @@ }, { "cell_type": "code", - "execution_count": 78, + "execution_count": 75, "metadata": { "slideshow": { "slide_type": "-" @@ -2506,7 +2452,7 @@ "'Hello Alexander! Good morning.'" ] }, - "execution_count": 78, + "execution_count": 75, "metadata": {}, "output_type": "execute_result" } @@ -2528,7 +2474,7 @@ }, { "cell_type": "code", - "execution_count": 79, + "execution_count": 76, "metadata": { "slideshow": { "slide_type": "slide" @@ -2541,7 +2487,7 @@ }, { "cell_type": "code", - "execution_count": 80, + "execution_count": 77, "metadata": { "slideshow": { "slide_type": "-" @@ -2554,7 +2500,7 @@ "'Pi is 3.14'" ] }, - "execution_count": 80, + "execution_count": 77, "metadata": {}, "output_type": "execute_result" } @@ -2565,7 +2511,7 @@ }, { "cell_type": "code", - "execution_count": 81, + "execution_count": 78, "metadata": { "slideshow": { "slide_type": "-" @@ -2578,7 +2524,7 @@ "'Pi is 3.142'" ] }, - "execution_count": 81, + "execution_count": 78, "metadata": {}, "output_type": "execute_result" } @@ -2611,7 +2557,7 @@ }, { "cell_type": "code", - "execution_count": 82, + "execution_count": 79, "metadata": { "slideshow": { "slide_type": "slide" @@ -2624,7 +2570,7 @@ "'Hello Alexander! Good morning.'" ] }, - "execution_count": 82, + "execution_count": 79, "metadata": {}, "output_type": "execute_result" } @@ -2646,7 +2592,7 @@ }, { "cell_type": "code", - "execution_count": 83, + "execution_count": 80, "metadata": { "slideshow": { "slide_type": "fragment" @@ -2659,7 +2605,7 @@ "'Good morning, Alexander'" ] }, - "execution_count": 83, + "execution_count": 80, "metadata": {}, "output_type": "execute_result" } @@ -2681,7 +2627,7 @@ }, { "cell_type": "code", - "execution_count": 84, + "execution_count": 81, "metadata": { "slideshow": { "slide_type": "fragment" @@ -2694,7 +2640,7 @@ "'Hello Alexander! Good morning.'" ] }, - "execution_count": 84, + "execution_count": 81, "metadata": {}, "output_type": "execute_result" } @@ -2716,7 +2662,7 @@ }, { "cell_type": "code", - "execution_count": 85, + "execution_count": 82, "metadata": { "slideshow": { "slide_type": "fragment" @@ -2729,7 +2675,7 @@ "'Pi is 3.14'" ] }, - "execution_count": 85, + "execution_count": 82, "metadata": {}, "output_type": "execute_result" } @@ -2764,7 +2710,7 @@ }, { "cell_type": "code", - "execution_count": 86, + "execution_count": 83, "metadata": { "slideshow": { "slide_type": "skip" @@ -2777,7 +2723,7 @@ "'Pi is 3.14'" ] }, - "execution_count": 86, + "execution_count": 83, "metadata": {}, "output_type": "execute_result" } @@ -2799,7 +2745,7 @@ }, { "cell_type": "code", - "execution_count": 87, + "execution_count": 84, "metadata": { "slideshow": { "slide_type": "skip" @@ -2812,7 +2758,7 @@ "'Hello Alexander! Good morning.'" ] }, - "execution_count": 87, + "execution_count": 84, "metadata": {}, "output_type": "execute_result" } @@ -2847,7 +2793,7 @@ }, { "cell_type": "code", - "execution_count": 88, + "execution_count": 85, "metadata": { "slideshow": { "slide_type": "slide" @@ -2870,7 +2816,7 @@ }, { "cell_type": "code", - "execution_count": 89, + "execution_count": 86, "metadata": { "slideshow": { "slide_type": "fragment" @@ -2902,7 +2848,7 @@ }, { "cell_type": "code", - "execution_count": 90, + "execution_count": 87, "metadata": { "slideshow": { "slide_type": "fragment" @@ -2934,7 +2880,7 @@ }, { "cell_type": "code", - "execution_count": 91, + "execution_count": 88, "metadata": { "slideshow": { "slide_type": "skip" @@ -2947,7 +2893,7 @@ "'This is a sentence\\nthat is printed\\non three lines.'" ] }, - "execution_count": 91, + "execution_count": 88, "metadata": {}, "output_type": "execute_result" } @@ -2982,7 +2928,7 @@ }, { "cell_type": "code", - "execution_count": 92, + "execution_count": 89, "metadata": { "slideshow": { "slide_type": "slide" @@ -3015,7 +2961,7 @@ }, { "cell_type": "code", - "execution_count": 93, + "execution_count": 90, "metadata": { "slideshow": { "slide_type": "fragment" @@ -3024,10 +2970,10 @@ "outputs": [ { "ename": "SyntaxError", - "evalue": "(unicode error) 'unicodeescape' codec can't decode bytes in position 2-3: truncated \\UXXXXXXXX escape (, line 1)", + "evalue": "(unicode error) 'unicodeescape' codec can't decode bytes in position 2-3: truncated \\UXXXXXXXX escape (, line 1)", "output_type": "error", "traceback": [ - "\u001b[0;36m File \u001b[0;32m\"\"\u001b[0;36m, line \u001b[0;32m1\u001b[0m\n\u001b[0;31m print(\"C:\\Users\\Administrator\\Desktop\\Project_Folder\")\u001b[0m\n\u001b[0m ^\u001b[0m\n\u001b[0;31mSyntaxError\u001b[0m\u001b[0;31m:\u001b[0m (unicode error) 'unicodeescape' codec can't decode bytes in position 2-3: truncated \\UXXXXXXXX escape\n" + "\u001b[0;36m File \u001b[0;32m\"\"\u001b[0;36m, line \u001b[0;32m1\u001b[0m\n\u001b[0;31m print(\"C:\\Users\\Administrator\\Desktop\\Project_Folder\")\u001b[0m\n\u001b[0m ^\u001b[0m\n\u001b[0;31mSyntaxError\u001b[0m\u001b[0;31m:\u001b[0m (unicode error) 'unicodeescape' codec can't decode bytes in position 2-3: truncated \\UXXXXXXXX escape\n" ] } ], @@ -3048,7 +2994,7 @@ }, { "cell_type": "code", - "execution_count": 94, + "execution_count": 91, "metadata": { "slideshow": { "slide_type": "slide" @@ -3069,7 +3015,7 @@ }, { "cell_type": "code", - "execution_count": 95, + "execution_count": 92, "metadata": { "slideshow": { "slide_type": "-" @@ -3101,7 +3047,7 @@ }, { "cell_type": "code", - "execution_count": 96, + "execution_count": 93, "metadata": { "slideshow": { "slide_type": "slide" @@ -3122,7 +3068,7 @@ }, { "cell_type": "code", - "execution_count": 97, + "execution_count": 94, "metadata": { "slideshow": { "slide_type": "-" @@ -3165,7 +3111,7 @@ }, { "cell_type": "code", - "execution_count": 98, + "execution_count": 95, "metadata": { "slideshow": { "slide_type": "slide" @@ -3174,10 +3120,10 @@ "outputs": [ { "ename": "SyntaxError", - "evalue": "EOL while scanning string literal (, line 1)", + "evalue": "EOL while scanning string literal (, line 1)", "output_type": "error", "traceback": [ - "\u001b[0;36m File \u001b[0;32m\"\"\u001b[0;36m, line \u001b[0;32m1\u001b[0m\n\u001b[0;31m \"\u001b[0m\n\u001b[0m ^\u001b[0m\n\u001b[0;31mSyntaxError\u001b[0m\u001b[0;31m:\u001b[0m EOL while scanning string literal\n" + "\u001b[0;36m File \u001b[0;32m\"\"\u001b[0;36m, line \u001b[0;32m1\u001b[0m\n\u001b[0;31m \"\u001b[0m\n\u001b[0m ^\u001b[0m\n\u001b[0;31mSyntaxError\u001b[0m\u001b[0;31m:\u001b[0m EOL while scanning string literal\n" ] } ], @@ -3200,7 +3146,7 @@ }, { "cell_type": "code", - "execution_count": 99, + "execution_count": 96, "metadata": { "slideshow": { "slide_type": "fragment" @@ -3227,7 +3173,7 @@ }, { "cell_type": "code", - "execution_count": 100, + "execution_count": 97, "metadata": { "slideshow": { "slide_type": "fragment" @@ -3240,7 +3186,7 @@ "'\\nI am a multi-line string\\nconsisting of 4 lines.\\n'" ] }, - "execution_count": 100, + "execution_count": 97, "metadata": {}, "output_type": "execute_result" } @@ -3262,7 +3208,7 @@ }, { "cell_type": "code", - "execution_count": 101, + "execution_count": 98, "metadata": { "slideshow": { "slide_type": "fragment" @@ -3297,7 +3243,7 @@ }, { "cell_type": "code", - "execution_count": 102, + "execution_count": 99, "metadata": { "slideshow": { "slide_type": "slide" @@ -3333,7 +3279,7 @@ }, { "cell_type": "code", - "execution_count": 103, + "execution_count": 100, "metadata": { "slideshow": { "slide_type": "slide" @@ -3347,7 +3293,7 @@ }, { "cell_type": "code", - "execution_count": 104, + "execution_count": 101, "metadata": { "slideshow": { "slide_type": "-" @@ -3360,7 +3306,7 @@ "\"Lorem Ipsum is simply dummy text of the printing and typesetting industry.\\nLorem Ipsum has been the industry's standard dummy text ever since the 1500s\\nwhen an unknown printer took a galley of type and scrambled it to make a type\\nspecimen book. It has survived not only five centuries but also the leap into\\nelectronic typesetting, remaining essentially unchanged. It was popularised in\\nthe 1960s with the release of Letraset sheets.\\n\"" ] }, - "execution_count": 104, + "execution_count": 101, "metadata": {}, "output_type": "execute_result" } @@ -3371,7 +3317,7 @@ }, { "cell_type": "code", - "execution_count": 105, + "execution_count": 102, "metadata": { "slideshow": { "slide_type": "-" diff --git a/07_sequences.ipynb b/07_sequences.ipynb index 11add0a..f20b670 100644 --- a/07_sequences.ipynb +++ b/07_sequences.ipynb @@ -23,7 +23,7 @@ "\n", "The `str` type is a bit of a corner case in this regard. While one could argue that a longer `str` object, for example, `\"text\"`, is composed of individual characters, this is *not* the case in memory as the literal `\"text\"` only creates *one* object (i.e., one \"bag\" of $0$s and $1$s modeling all characters).\n", "\n", - "This chapter and Chapter 8 introduce various \"complex\" data types. While some are mutable and others are not, they all share that they are primarily used to \"manage,\" or structure, the memory in a program. Unsurprisingly, computer scientists refer to the ideas and theories behind these data types as **[data structures](https://en.wikipedia.org/wiki/Data_structure)**.\n", + "This chapter and [Chapter 8](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/08_mappings.ipynb) introduce various \"complex\" data types. While some are mutable and others are not, they all share that they are primarily used to \"manage,\" or structure, the memory in a program. Unsurprisingly, computer scientists refer to the ideas and theories behind these data types as **[data structures](https://en.wikipedia.org/wiki/Data_structure)**.\n", "\n", "In this chapter, we focus on data types that model all kinds of sequential data. Examples of such data are [spreadsheets](https://en.wikipedia.org/wiki/Spreadsheet) or [matrices](https://en.wikipedia.org/wiki/Matrix_%28mathematics%29)/[vectors](https://en.wikipedia.org/wiki/Vector_%28mathematics_and_physics%29). Such formats share the property that they are composed of smaller units that come in a sequence of, for example, rows/columns/cells or elements/entries." ] @@ -49,9 +49,9 @@ "source": [ "[Chapter 6](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/06_text.ipynb#A-\"String\"-of-Characters) already describes the *sequence* properties of `str` objects. Here, we take a step back and study these properties on their own before looking at bigger ideas.\n", "\n", - "The [collections.abc](https://docs.python.org/3/library/collections.abc.html) module in the [standard library](https://docs.python.org/3/library/index.html) defines a variety of **abstract base classes** (ABCs). We saw ABCs already in [Chapter 5](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/05_numbers.ipynb#The-Numerical-Tower), where we use the ones from the [numbers](https://docs.python.org/3/library/numbers.html) module in the [standard library](https://docs.python.org/3/library/index.html) to classify Python's numeric data types according to mathematical ideas. Now, we take the ABCs from the [collections.abc](https://docs.python.org/3/library/collections.abc.html) module to classify the data types in this chapter according to their behavior in various contexts.\n", + "The [collections.abc](https://docs.python.org/3/library/collections.abc.html) module in the [standard library](https://docs.python.org/3/library/index.html) defines a variety of **abstract base classes** (ABCs). We saw ABCs already in [Chapter 5](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/05_numbers.ipynb#The-Numerical-Tower), where we use the ones from the [numbers](https://docs.python.org/3/library/numbers.html) module in the [standard library](https://docs.python.org/3/library/index.html) to classify Python's numeric data types according to mathematical ideas. Now, we take the ABCs from the [collections.abc](https://docs.python.org/3/library/collections.abc.html) module to classify the data types in this chapter and [Chapter 8](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/08_mappings.ipynb) according to their behavior in various contexts.\n", "\n", - "As an illustration, consider `numbers` and `word` below, two objects of *different* types." + "As an illustration, consider `numbers` and `text` below, two objects of *different* types." ] }, { @@ -65,7 +65,7 @@ "outputs": [], "source": [ "numbers = [7, 11, 8, 5, 3, 12, 2, 6, 9, 10, 1, 4]\n", - "word = \"random\"" + "text = \"Lorem ipsum dolor sit amet, consectetur ...\"" ] }, { @@ -76,7 +76,7 @@ } }, "source": [ - "They have in common that we may loop over them with the `for` statement. So, in the context of iteration, both exhibit the *same* behavior." + "Among others, one commonality between the two is that we may loop over them with the `for` statement. So, in the context of iteration, both exhibit the *same* behavior." ] }, { @@ -114,12 +114,12 @@ "name": "stdout", "output_type": "stream", "text": [ - "r a n d o m " + "L o r e m i p s u m d o l o r s i t a m e t , c o n s e c t e t u r . . . " ] } ], "source": [ - "for character in word:\n", + "for character in text:\n", " print(character, end=\" \")" ] }, @@ -133,7 +133,7 @@ "source": [ "In [Chapter 4](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/04_iteration.ipynb#Containers-vs.-Iterables), we referred to such types as *iterables*. That is *not* a proper [English](https://dictionary.cambridge.org/spellcheck/english-german/?q=iterable) word, even if it may sound like one at first sight. Yet, it is an official term in the Python world formalized with the `Iterable` ABC in the [collections.abc](https://docs.python.org/3/library/collections.abc.html) module.\n", "\n", - "For the data science practitioner, it is worthwhile to know such terms as, for example, the documentation on the [built-ins](https://docs.python.org/3/library/functions.html) uses them extensively: In simple words, any built-in that takes an argument called \"*iterable*\" may be called with *any* object that supports being looped over. Already familiar [built-ins](https://docs.python.org/3/library/functions.html) include, among others, [enumerate()](https://docs.python.org/3/library/functions.html#enumerate), [sum()](https://docs.python.org/3/library/functions.html#sum), or [zip()](https://docs.python.org/3/library/functions.html#zip). So, they do *not* require the argument to be of a certain concrete data type (e.g., `list`); instead, any *iterable* type works." + "For the data science practitioner, it is worthwhile to know such terms as, for example, the documentation on the [built-ins](https://docs.python.org/3/library/functions.html) uses them extensively: In simple words, any built-in that takes an argument called \"*iterable*\" may be called with *any* object that supports being looped over. Already familiar [built-ins](https://docs.python.org/3/library/functions.html) include [enumerate()](https://docs.python.org/3/library/functions.html#enumerate), [sum()](https://docs.python.org/3/library/functions.html#sum), or [zip()](https://docs.python.org/3/library/functions.html#zip). So, they do *not* require the argument to be of a certain data type (e.g., `list`); instead, any *iterable* type works." ] }, { @@ -183,7 +183,7 @@ "source": [ "As in the context of *goose typing* in [Chapter 5](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/05_numbers.ipynb#Goose-Typing), we can use ABCs with the built-in [isinstance()](https://docs.python.org/3/library/functions.html#isinstance) function to check if an object supports a behavior.\n", "\n", - "So, let's \"ask\" Python if it can loop over `numbers` or `word`." + "So, let's \"ask\" Python if it can loop over `numbers` or `text`." ] }, { @@ -231,7 +231,7 @@ } ], "source": [ - "isinstance(word, abc.Iterable)" + "isinstance(text, abc.Iterable)" ] }, { @@ -315,7 +315,7 @@ }, "source": [ "Most of the data types in this and the next chapter exhibit three [orthogonal](https://en.wikipedia.org/wiki/Orthogonality) (i.e., \"independent\") behaviors, formalized by ABCs in the [collections.abc](https://docs.python.org/3/library/collections.abc.html) module as:\n", - "- `Iterable`: An object supports being looped over.\n", + "- `Iterable`: An object may be looped over.\n", "- `Container`: An object \"contains\" references to other objects; a \"whole\" is composed of many \"parts.\"\n", "- `Sized`: The number of references to other objects, the \"parts,\" is *finite*.\n", "\n", @@ -367,7 +367,7 @@ } ], "source": [ - "\"r\" in word" + "\"l\" in text" ] }, { @@ -378,7 +378,7 @@ } }, "source": [ - "Alternatively, we could also check if `numbers` and `word` are `Container` types with the [isinstance()](https://docs.python.org/3/library/functions.html#isinstance) function." + "Alternatively, we could also check if `numbers` and `text` are `Container` types with [isinstance()](https://docs.python.org/3/library/functions.html#isinstance)." ] }, { @@ -426,7 +426,7 @@ } ], "source": [ - "isinstance(word, abc.Container)" + "isinstance(text, abc.Container)" ] }, { @@ -497,7 +497,7 @@ } }, "source": [ - "Analogously, being `Sized` types, we can pass `numbers` and `word` as the argument to the built-in [len()](https://docs.python.org/3/library/functions.html#len) function and obtain \"meaningful\" results. The exact meaning depends on the *concrete* data type: For `numbers`, [len()](https://docs.python.org/3/library/functions.html#len) tells us how many elements are in the `list` object; for `word`, it tells us how many [Unicode characters](https://en.wikipedia.org/wiki/Unicode) make up the `str` object. But, *abstractly* speaking, both data types exhibit the *same* behavior of *finiteness*." + "Analogously, being `Sized` types, we can pass `numbers` and `text` as the argument to the built-in [len()](https://docs.python.org/3/library/functions.html#len) function and obtain \"meaningful\" results. The exact meaning depends on the data type: For `numbers`, [len()](https://docs.python.org/3/library/functions.html#len) tells us how many elements are in the `list` object; for `text`, it tells us how many [Unicode characters](https://en.wikipedia.org/wiki/Unicode) make up the `str` object. *Abstractly* speaking, both data types exhibit the *same* behavior of *finiteness*." ] }, { @@ -536,7 +536,7 @@ { "data": { "text/plain": [ - "6" + "43" ] }, "execution_count": 17, @@ -545,7 +545,7 @@ } ], "source": [ - "len(word)" + "len(text)" ] }, { @@ -593,7 +593,7 @@ } ], "source": [ - "isinstance(word, abc.Sized)" + "isinstance(text, abc.Sized)" ] }, { @@ -666,7 +666,9 @@ "source": [ "These three behaviors are so essential that whenever they coincide for a data type, it is called a **collection**, formalized with the `Collection` ABC. That is where the [collections.abc](https://docs.python.org/3/library/collections.abc.html) module got its name from: It summarizes all ABCs related to collections; in particular, it defines a hierarchy of specialized kinds of collections.\n", "\n", - "So, both `numbers` and `word` are collections." + "Without going into too much detail, one way to read the summary table at the beginning of the [collections.abc](https://docs.python.org/3/library/collections.abc.html) module's documention is as follows: The first column, titled \"ABC\", lists all collection-related ABCs in Python. The second column, titled \"Inherits from,\" indicates if the idea behind the ABC is *original* (e.g., the first row with the `Container` ABC has an empty \"Inherits from\" column) or a *combination* (e.g., the row with the `Collection` ABC has `Sized`, `Iterable`, and `Container` in the \"Inherits from\" column). The third and fourth columns list the methods that come with a data type following an ABC. We keep ignoring the methods named in the dunder style for now.\n", + "\n", + "So, let's confirm that both `numbers` and `text` are collections." ] }, { @@ -714,7 +716,7 @@ } ], "source": [ - "isinstance(word, abc.Collection)" + "isinstance(text, abc.Collection)" ] }, { @@ -727,7 +729,7 @@ "source": [ "They share one more common behavior: When looping over them, we can *predict* the *order* of the elements or characters. The ABC in the [collections.abc](https://docs.python.org/3/library/collections.abc.html) module corresponding to this behavior is `Reversible`. While sounding unintuitive at first, it is evident that if something is reversible, it must have a forward order, to begin with.\n", "\n", - "We add the [reversed()](https://docs.python.org/3/library/functions.html#reversed) built-in to the `for`-loop from above to iterate over the elements or characters in reverse order." + "The [reversed()](https://docs.python.org/3/library/functions.html#reversed) built-in allows us to loop over the elements or characters in reverse order." ] }, { @@ -765,12 +767,12 @@ "name": "stdout", "output_type": "stream", "text": [ - "m o d n a r " + ". . . r u t e t c e s n o c , t e m a t i s r o l o d m u s p i m e r o L " ] } ], "source": [ - "for character in reversed(word):\n", + "for character in reversed(text):\n", " print(character, end=\" \")" ] }, @@ -819,7 +821,7 @@ } ], "source": [ - "isinstance(word, abc.Reversible)" + "isinstance(text, abc.Reversible)" ] }, { @@ -878,7 +880,7 @@ } ], "source": [ - "isinstance(word, abc.Sequence)" + "isinstance(text, abc.Sequence)" ] }, { @@ -976,7 +978,7 @@ } }, "source": [ - "[PythonTutor](http://www.pythontutor.com/visualize.html#code=empty%20%3D%20%5B%5D%0Asimple%20%3D%20%5B40,%2050%5D%0Anested%20%3D%20%5Bempty,%2010,%2020.0,%20%22Thirty%22,%20simple%5D&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false) shows how `nested` holds references to the `empty` and `simple` objects. Technically, it holds three more references pointing to the `10`, `20.0`, and `\"Thirty\"` objects as well. However, to simplify the visualization, these three objects are shown right inside the `nested` object as they are immutable and of \"flat\" data types. In general, the $0$s and $1$s inside a `list` object in memory always constitute pointers to other objects only." + "[PythonTutor](http://www.pythontutor.com/visualize.html#code=empty%20%3D%20%5B%5D%0Asimple%20%3D%20%5B40,%2050%5D%0Anested%20%3D%20%5Bempty,%2010,%2020.0,%20%22Thirty%22,%20simple%5D&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false) shows how `nested` holds references to the `empty` and `simple` objects. Technically, it holds three more references to the `10`, `20.0`, and `\"Thirty\"` objects as well. However, to simplify the visualization, these three objects are shown right inside the `nested` object. That may be done because they are immutable and \"flat\" data types. In general, the $0$s and $1$s inside a `list` object in memory always constitute references to other objects only." ] }, { @@ -1026,7 +1028,7 @@ { "data": { "text/plain": [ - "140322034424136" + "140157873498184" ] }, "execution_count": 34, @@ -1070,7 +1072,7 @@ } }, "source": [ - "Alternatively, we may use the [list()](https://docs.python.org/3/library/functions.html#func-list) built-in to create a `list` object out of an iterable we pass to it as the argument.\n", + "Alternatively, we use the [list()](https://docs.python.org/3/library/functions.html#func-list) built-in to create a `list` object out of an iterable we pass to it as the argument.\n", "\n", "For example, we can wrap the [range()](https://docs.python.org/3/library/functions.html#func-range) built-in with [list()](https://docs.python.org/3/library/functions.html#func-list): As described in [Chapter 4](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/04_iteration.ipynb#Containers-vs.-Iterables), `range` objects, like `range(1, 13)` below, are iterable and generate `int` objects \"on the fly\" (i.e., one by one). The [list()](https://docs.python.org/3/library/functions.html#func-list) around it acts like a `for`-loop and **materializes** twelve `int` objects in memory that then become the elements of the newly created `list` object. [PythonTutor](http://www.pythontutor.com/visualize.html#code=r%20%3D%20range%281,%2013%29%0Al%20%3D%20list%28range%281,%2013%29%29&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false) shows this difference visually." ] @@ -1167,7 +1169,7 @@ } }, "source": [ - "As another example, we may also create a `list` object from a `str` object as the latter is iterable, as well. Then, the individual characters become the elements of the new `list` object!" + "As another example, we create a `list` object from a `str` object, which is iterable, as well. Then, the individual characters become the elements of the new `list` object!" ] }, { @@ -1182,7 +1184,7 @@ { "data": { "text/plain": [ - "['W', 'H', 'U']" + "['i', 't', 'e', 'r', 'a', 'b', 'l', 'e']" ] }, "execution_count": 39, @@ -1191,7 +1193,7 @@ } ], "source": [ - "list(\"WHU\")" + "list(\"iterable\")" ] }, { @@ -1213,7 +1215,7 @@ } }, "source": [ - "`list` objects are *sequences*. To reiterate that concept from above *without* the formal ABCs from the [collections.abc](https://docs.python.org/3/library/collections.abc.html) module, we briefly summarize the *four* behaviors of a sequence and provide some more `list`-specific details below:" + "`list` objects are *sequences*. To reiterate that, we briefly summarize the *four* behaviors of a sequence and provide some more `list`-specific details below:" ] }, { @@ -1224,16 +1226,16 @@ } }, "source": [ - "- **Container**:\n", + "- `Container`:\n", " - holds references to other objects in memory (with their own *identity* and *type*)\n", " - implements membership testing via the `in` operator\n", - "- **Iterable**:\n", + "- `Iterable`:\n", " - supports being looped over\n", " - works with the `for` or `while` statements\n", - "- **Reversible**:\n", + "- `Reversible`:\n", " - the elements come in a *predictable* order that we may traverse in a forward or backward fashion\n", " - works with the [reversed()](https://docs.python.org/3/library/functions.html#reversed) built-in\n", - "- **Sized**:\n", + "- `Sized`:\n", " - the number of elements is finite *and* known in advance\n", " - works with the built-in [len()](https://docs.python.org/3/library/functions.html#len) function" ] @@ -1246,7 +1248,7 @@ } }, "source": [ - "The \"length\" of `nested` is *five* because `simple` counts as only *one* element. In other words, `nested` holds five references to other objects." + "The \"length\" of `nested` is *five* because `empty` and `simple` count as *one* element each. In other words, `nested` holds five references to other objects, two of which are `list` objects." ] }, { @@ -1281,7 +1283,7 @@ } }, "source": [ - "With a `for`-loop, we can traverse all elements in a *predictable* order, forward or backward. As `list` objects only hold references to other objects, these have a *indentity* and may be of *different* types; however, this is rarely, if ever, useful in practice." + "With a `for`-loop, we can iterate over all elements in a *predictable* order, forward or backward. As `list` objects hold *references* to other *objects*, these have an *indentity* and may even be of *different* types; however, the latter observation is rarely, if ever, useful in practice." ] }, { @@ -1297,11 +1299,11 @@ "name": "stdout", "output_type": "stream", "text": [ - "[] \t140322025703432 \t\n", - "10 \t94360180081984 \t\n", - "20.0 \t140322034534104 \t\n", - "Thirty \t140322025251816 \t\n", - "[40, 50] \t140322034424072 \t\n" + "[] \t140157882223304 \t\n", + "10 \t93987402609984 \t\n", + "20.0 \t140157882328912 \t\n", + "Thirty \t140157873509520 \t\n", + "[40, 50] \t140157873031176 \t\n" ] } ], @@ -1340,7 +1342,7 @@ } }, "source": [ - "The `in` operator checks if a given object is \"contained\" in a `list` object. It uses the `==` operator behind the scenes (i.e., *not* the `is` operator) conducting a so-called **[linear search](https://en.wikipedia.org/wiki/Linear_search)**: So, Python implicitly loops over *all* elements and only stops prematurely if an element evaluates equal to the given object. A linear search may, therefore, be relatively *slow* for big `list` objects." + "The `in` operator checks if a given object is \"contained\" in a `list` object. It uses the `==` operator behind the scenes (i.e., *not* the `is` operator) conducting a **[linear search](https://en.wikipedia.org/wiki/Linear_search)**: So, Python implicitly loops over *all* elements and only stops prematurely if an element evaluates equal to the searched object. A linear search may, therefore, be relatively *slow* for big `list` objects." ] }, { @@ -1445,9 +1447,9 @@ } }, "source": [ - "Because of the *predictable* order and the *finiteness*, each element in a sequence can be labeled with a unique *index* (i.e., an `int` object in the range $0 \\leq \\text{index} < \\lvert \\text{sequence} \\rvert$).\n", + "Because of the *predictable* order and the *finiteness*, each element in a sequence can be labeled with a unique *index*, an `int` object in the range $0 \\leq \\text{index} < \\lvert \\text{sequence} \\rvert$.\n", "\n", - "Brackets, `[` and `]`, are the literal syntax for accessing individual elements of any sequence type. In this book, we also call them the *indexing operator*." + "Brackets, `[` and `]`, are the literal syntax for accessing individual elements of any sequence type. In this book, we also call them the *indexing operator* in this context." ] }, { @@ -1482,7 +1484,7 @@ } }, "source": [ - "The last index is one less than `len(nested)` above, and Python raises an `IndexError` if we look up an index that is not in the implied range." + "The last index is one less than `len(nested)`, and Python raises an `IndexError` if we look up an index that is not in the range." ] }, { @@ -1533,7 +1535,7 @@ { "data": { "text/plain": [ - "50" + "[40, 50]" ] }, "execution_count": 48, @@ -1541,6 +1543,30 @@ "output_type": "execute_result" } ], + "source": [ + "nested[-1]" + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "50" + ] + }, + "execution_count": 49, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "nested[-1][1]" ] @@ -1571,7 +1597,7 @@ }, { "cell_type": "code", - "execution_count": 49, + "execution_count": 50, "metadata": { "slideshow": { "slide_type": "slide" @@ -1584,7 +1610,7 @@ "[10, 20.0, 'Thirty']" ] }, - "execution_count": 49, + "execution_count": 50, "metadata": {}, "output_type": "execute_result" } @@ -1601,12 +1627,12 @@ } }, "source": [ - "To obtain \"every other\" element, we slice from beginning to end, defaulting to `0` and `len(nested)`, in steps of `2`." + "To obtain \"every other\" element, we slice from beginning to end, defaulting to `0` and `len(nested)` when omitted, in steps of `2`." ] }, { "cell_type": "code", - "execution_count": 50, + "execution_count": 51, "metadata": { "slideshow": { "slide_type": "-" @@ -1619,7 +1645,7 @@ "[[], 20.0, [40, 50]]" ] }, - "execution_count": 50, + "execution_count": 51, "metadata": {}, "output_type": "execute_result" } @@ -1636,20 +1662,9 @@ } }, "source": [ - "The literal notation with the colons `:` is *syntactic sugar*, and Python provides the [slice()](https://docs.python.org/3/library/functions.html#slice) built-in to slice with `slice` objects. [slice()](https://docs.python.org/3/library/functions.html#slice) takes *start*, *stop*, and *step* arguments in the same way as the already familiar [range()](https://docs.python.org/3/library/functions.html#func-range) built-in." - ] - }, - { - "cell_type": "code", - "execution_count": 51, - "metadata": { - "slideshow": { - "slide_type": "skip" - } - }, - "outputs": [], - "source": [ - "middle = slice(1, 4)" + "The literal notation with the colons `:` is *syntactic sugar*. It saves us from using the [slice()](https://docs.python.org/3/library/functions.html#slice) built-in to create `slice` objects. [slice()](https://docs.python.org/3/library/functions.html#slice) takes *start*, *stop*, and *step* arguments in the same way as the familiar [range()](https://docs.python.org/3/library/functions.html#func-range), and the `slice` objects it creates are used just as *indexes* above.\n", + "\n", + "In most cases, the literal notation is more convenient to use; however, with `slice` objects, we can give names to slices and reuse them across several sequences." ] }, { @@ -1660,31 +1675,9 @@ "slide_type": "skip" } }, - "outputs": [ - { - "data": { - "text/plain": [ - "slice" - ] - }, - "execution_count": 52, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ - "type(middle)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "skip" - } - }, - "source": [ - "In most cases, the literal notation is more convenient to use; however, with `slice` objects, we may give names to slices and re-use them across several sequences." + "middle = slice(1, 4)" ] }, { @@ -1699,7 +1692,7 @@ { "data": { "text/plain": [ - "[10, 20.0, 'Thirty']" + "slice" ] }, "execution_count": 53, @@ -1708,7 +1701,7 @@ } ], "source": [ - "nested[middle]" + "type(middle)" ] }, { @@ -1723,7 +1716,7 @@ { "data": { "text/plain": [ - "[11, 8, 5]" + "[10, 20.0, 'Thirty']" ] }, "execution_count": 54, @@ -1732,7 +1725,7 @@ } ], "source": [ - "numbers[middle]" + "nested[middle]" ] }, { @@ -1747,7 +1740,7 @@ { "data": { "text/plain": [ - "'and'" + "[11, 8, 5]" ] }, "execution_count": 55, @@ -1756,7 +1749,31 @@ } ], "source": [ - "word[middle]" + "numbers[middle]" + ] + }, + { + "cell_type": "code", + "execution_count": 56, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "'ore'" + ] + }, + "execution_count": 56, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "text[middle]" ] }, { @@ -1772,7 +1789,7 @@ }, { "cell_type": "code", - "execution_count": 56, + "execution_count": 57, "metadata": { "slideshow": { "slide_type": "skip" @@ -1785,7 +1802,7 @@ "1" ] }, - "execution_count": 56, + "execution_count": 57, "metadata": {}, "output_type": "execute_result" } @@ -1796,7 +1813,7 @@ }, { "cell_type": "code", - "execution_count": 57, + "execution_count": 58, "metadata": { "slideshow": { "slide_type": "skip" @@ -1809,7 +1826,7 @@ "4" ] }, - "execution_count": 57, + "execution_count": 58, "metadata": {}, "output_type": "execute_result" } @@ -1831,7 +1848,7 @@ }, { "cell_type": "code", - "execution_count": 58, + "execution_count": 59, "metadata": { "slideshow": { "slide_type": "skip" @@ -1855,7 +1872,7 @@ }, { "cell_type": "code", - "execution_count": 59, + "execution_count": 60, "metadata": { "slideshow": { "slide_type": "slide" @@ -1868,7 +1885,7 @@ }, { "cell_type": "code", - "execution_count": 60, + "execution_count": 61, "metadata": { "slideshow": { "slide_type": "-" @@ -1881,7 +1898,7 @@ "[[], 10, 20.0, 'Thirty', [40, 50]]" ] }, - "execution_count": 60, + "execution_count": 61, "metadata": {}, "output_type": "execute_result" } @@ -1898,14 +1915,14 @@ } }, "source": [ - "At first glance, `nested` and `nested_copy` seem to cause no pain. For `list` objects, the comparison operator `==` goes over the elements in both operands in a pairwise fashion and checks if they all evaluate equal.\n", + "At first glance, `nested` and `nested_copy` seem to cause no pain. For `list` objects, the comparison operator `==` goes over the elements in both operands in a pairwise fashion and checks if they all evaluate equal (cf., the \"*List Comparison*\" section below for more details).\n", "\n", - "We confirm that `nested` and `nested_copy` compare equal as expected but also that they are *different* objects." + "We confirm that `nested` and `nested_copy` compare equal as could be expected but also that they are *different* objects." ] }, { "cell_type": "code", - "execution_count": 61, + "execution_count": 62, "metadata": { "slideshow": { "slide_type": "fragment" @@ -1918,7 +1935,7 @@ "True" ] }, - "execution_count": 61, + "execution_count": 62, "metadata": {}, "output_type": "execute_result" } @@ -1929,7 +1946,7 @@ }, { "cell_type": "code", - "execution_count": 62, + "execution_count": 63, "metadata": { "slideshow": { "slide_type": "-" @@ -1942,7 +1959,7 @@ "False" ] }, - "execution_count": 62, + "execution_count": 63, "metadata": {}, "output_type": "execute_result" } @@ -1959,14 +1976,14 @@ } }, "source": [ - "However, as [PythonTutor](http://pythontutor.com/visualize.html#code=nested%20%3D%20%5B%5B%5D,%2010,%2020.0,%20%22Thirty%22,%20%5B40,%2050%5D%5D%0Anested_copy%20%3D%20nested%5B%3A%5D&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false) reveals, only the *pointers* to the elements are copied! That concept is called a **[shallow copy](https://en.wikipedia.org/wiki/Object_copying#Shallow_copy)**.\n", + "However, as [PythonTutor](http://pythontutor.com/visualize.html#code=nested%20%3D%20%5B%5B%5D,%2010,%2020.0,%20%22Thirty%22,%20%5B40,%2050%5D%5D%0Anested_copy%20%3D%20nested%5B%3A%5D&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false) reveals, only the *references* to the elements are copied, and not the objects in `nested` themselves! Because of that, `nested_copy` is a so-called **[shallow copy](https://en.wikipedia.org/wiki/Object_copying#Shallow_copy)** of `nested`.\n", "\n", - "We could also see this with the [id()](https://docs.python.org/3/library/functions.html#id) function: The respective first elements in both `nested` and `nested_copy` are the *same* `empty` object." + "We could also see this with the [id()](https://docs.python.org/3/library/functions.html#id) function: The respective first elements in both `nested` and `nested_copy` are the *same* object, namely `empty`. So, we have three ways of accessing the *same* address in memory. Also, we say that `nested` and `nested_copy` partially share the *same* state." ] }, { "cell_type": "code", - "execution_count": 63, + "execution_count": 64, "metadata": { "slideshow": { "slide_type": "fragment" @@ -1979,7 +1996,7 @@ "True" ] }, - "execution_count": 63, + "execution_count": 64, "metadata": {}, "output_type": "execute_result" } @@ -1990,7 +2007,7 @@ }, { "cell_type": "code", - "execution_count": 64, + "execution_count": 65, "metadata": { "slideshow": { "slide_type": "skip" @@ -2000,44 +2017,16 @@ { "data": { "text/plain": [ - "[]" + "140157882223304" ] }, - "execution_count": 64, + "execution_count": 65, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "nested[0]" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "skip" - } - }, - "source": [ - "Knowing this becomes critical if the elements in a `list` object are mutable objects. Then, because of the original `list` object and its copy both pointing at the *same* objects in memory, if some of them are mutated, these changes are visible to both! We already saw a similar kind of confusion in [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb#Who-am-I?-And-how-many?) in a \"simpler\" setting and look into this in detail in the next section.\n", - "\n", - "Instead of a shallow copy, we could also create a so-called **[deep copy](https://en.wikipedia.org/wiki/Object_copying#Deep_copy)** of `nested`: That concept recursively follows every pointer in a possible nested data structure and creates copies of *every* involved object.\n", - "\n", - "To explicitly create shallow or deep copies, the [copy](https://docs.python.org/3/library/copy.html) module in the [standard library](https://docs.python.org/3/library/index.html) provides two functions, [copy()](https://docs.python.org/3/library/copy.html#copy.copy) and [deepcopy()](https://docs.python.org/3/library/copy.html#copy.deepcopy). We must always remember that slicing creates *shallow* copies only." - ] - }, - { - "cell_type": "code", - "execution_count": 65, - "metadata": { - "slideshow": { - "slide_type": "skip" - } - }, - "outputs": [], - "source": [ - "import copy" + "id(nested[0])" ] }, { @@ -2048,9 +2037,20 @@ "slide_type": "skip" } }, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "140157882223304" + ] + }, + "execution_count": 66, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "nested_deep_copy = copy.deepcopy(nested)" + "id(nested_copy[0])" ] }, { @@ -2065,7 +2065,7 @@ { "data": { "text/plain": [ - "True" + "140157882223304" ] }, "execution_count": 67, @@ -2073,6 +2073,73 @@ "output_type": "execute_result" } ], + "source": [ + "id(empty)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Knowing this becomes critical if the elements in a `list` object are mutable objects (i.e., we can change them *in place*), and this is the case with `nested` and `nested_copy`, as we see in the next section on \"*Mutability*\".\n", + "\n", + "As both the original `nested` object and its copy reference the *same* `list` objects in memory, any changes made to them are visible to both! Because of that, working with shallow copies can easily become confusing.\n", + "\n", + "Instead of a shallow copy, we could also create a so-called **[deep copy](https://en.wikipedia.org/wiki/Object_copying#Deep_copy)** of `nested`: Then, the copying process recursively follows every reference in a nested data structure and creates copies of *every* object found.\n", + "\n", + "To explicitly create shallow or deep copies, the [copy](https://docs.python.org/3/library/copy.html) module in the [standard library](https://docs.python.org/3/library/index.html) provides two functions, [copy()](https://docs.python.org/3/library/copy.html#copy.copy) and [deepcopy()](https://docs.python.org/3/library/copy.html#copy.deepcopy). We must always remember that slicing creates *shallow* copies only." + ] + }, + { + "cell_type": "code", + "execution_count": 68, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [], + "source": [ + "import copy" + ] + }, + { + "cell_type": "code", + "execution_count": 69, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [], + "source": [ + "nested_deep_copy = copy.deepcopy(nested)" + ] + }, + { + "cell_type": "code", + "execution_count": 70, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 70, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "nested == nested_deep_copy" ] @@ -2090,7 +2157,7 @@ }, { "cell_type": "code", - "execution_count": 68, + "execution_count": 71, "metadata": { "slideshow": { "slide_type": "skip" @@ -2103,7 +2170,7 @@ "False" ] }, - "execution_count": 68, + "execution_count": 71, "metadata": {}, "output_type": "execute_result" } @@ -2112,6 +2179,54 @@ "nested[0] is nested_deep_copy[0]" ] }, + { + "cell_type": "code", + "execution_count": 72, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "140157882223304" + ] + }, + "execution_count": 72, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "id(nested[0])" + ] + }, + { + "cell_type": "code", + "execution_count": 73, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "140157873029448" + ] + }, + "execution_count": 73, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "id(nested_deep_copy[0])" + ] + }, { "cell_type": "markdown", "metadata": { @@ -2142,12 +2257,12 @@ } }, "source": [ - "In contrast to `str` objects, `list` objects are *mutable*: We may assign new elements to indices or slices and also remove elements. That changes *parts* of a `list` object in memory." + "In contrast to `str` objects, `list` objects are *mutable*: We may assign new elements to indices or slices and also remove elements. That changes the *references* in a `list` object. In general, if an object is *mutable*, we say that it may be changed *in place*." ] }, { "cell_type": "code", - "execution_count": 69, + "execution_count": 74, "metadata": { "slideshow": { "slide_type": "slide" @@ -2160,7 +2275,7 @@ }, { "cell_type": "code", - "execution_count": 70, + "execution_count": 75, "metadata": { "slideshow": { "slide_type": "-" @@ -2173,7 +2288,7 @@ "[0, 10, 20.0, 'Thirty', [40, 50]]" ] }, - "execution_count": 70, + "execution_count": 75, "metadata": {}, "output_type": "execute_result" } @@ -2195,7 +2310,7 @@ }, { "cell_type": "code", - "execution_count": 71, + "execution_count": 76, "metadata": { "slideshow": { "slide_type": "fragment" @@ -2208,7 +2323,7 @@ }, { "cell_type": "code", - "execution_count": 72, + "execution_count": 77, "metadata": { "slideshow": { "slide_type": "-" @@ -2221,7 +2336,7 @@ "[100, 100, 100, [40, 50]]" ] }, - "execution_count": 72, + "execution_count": 77, "metadata": {}, "output_type": "execute_result" } @@ -2232,7 +2347,7 @@ }, { "cell_type": "code", - "execution_count": 73, + "execution_count": 78, "metadata": { "slideshow": { "slide_type": "-" @@ -2245,7 +2360,7 @@ "4" ] }, - "execution_count": 73, + "execution_count": 78, "metadata": {}, "output_type": "execute_result" } @@ -2262,12 +2377,12 @@ } }, "source": [ - "The `list` object's identity does *not* change. That is the whole point behind mutable objects." + "The `list` object's identity does *not* change. That is the main point behind mutable objects." ] }, { "cell_type": "code", - "execution_count": 74, + "execution_count": 79, "metadata": { "slideshow": { "slide_type": "skip" @@ -2277,10 +2392,10 @@ { "data": { "text/plain": [ - "140322034424136" + "140157873498184" ] }, - "execution_count": 74, + "execution_count": 79, "metadata": {}, "output_type": "execute_result" } @@ -2297,12 +2412,12 @@ } }, "source": [ - "`nested_copy` is still unchanged!" + "`nested_copy` is unchanged!" ] }, { "cell_type": "code", - "execution_count": 75, + "execution_count": 80, "metadata": { "slideshow": { "slide_type": "slide" @@ -2315,7 +2430,7 @@ "[[], 10, 20.0, 'Thirty', [40, 50]]" ] }, - "execution_count": 75, + "execution_count": 80, "metadata": {}, "output_type": "execute_result" } @@ -2337,7 +2452,7 @@ }, { "cell_type": "code", - "execution_count": 76, + "execution_count": 81, "metadata": { "slideshow": { "slide_type": "-" @@ -2350,7 +2465,7 @@ }, { "cell_type": "code", - "execution_count": 77, + "execution_count": 82, "metadata": { "slideshow": { "slide_type": "-" @@ -2363,7 +2478,7 @@ "[[], 10, 20.0, 'Thirty', [1, 2, 3]]" ] }, - "execution_count": 77, + "execution_count": 82, "metadata": {}, "output_type": "execute_result" } @@ -2380,12 +2495,12 @@ } }, "source": [ - "This has a surprising side effect on `nested`!" + "That has a surprising side effect on `nested`." ] }, { "cell_type": "code", - "execution_count": 78, + "execution_count": 83, "metadata": { "slideshow": { "slide_type": "fragment" @@ -2398,7 +2513,7 @@ "[100, 100, 100, [1, 2, 3]]" ] }, - "execution_count": 78, + "execution_count": 83, "metadata": {}, "output_type": "execute_result" } @@ -2415,14 +2530,14 @@ } }, "source": [ - "That is because `nested_copy` is a shallow copy of `nested`. [PythonTutor](http://pythontutor.com/visualize.html#code=nested%20%3D%20%5B%5B%5D,%2010,%2020.0,%20%22Thirty%22,%20%5B40,%2050%5D%5D%0Anested_copy%20%3D%20nested%5B%3A%5D%0Anested%5B%3A4%5D%20%3D%20%5B100,%20100,%20100%5D%0Anested_copy%5B-1%5D%5B%3A%5D%20%3D%20%5B1,%202,%203%5D&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false) shows how both point to the *same* nested `list` object.\n", + "That is precisely the confusion we talked about above when we said that `nested_copy` is a *shallow* copy of `nested`. [PythonTutor](http://pythontutor.com/visualize.html#code=nested%20%3D%20%5B%5B%5D,%2010,%2020.0,%20%22Thirty%22,%20%5B40,%2050%5D%5D%0Anested_copy%20%3D%20nested%5B%3A%5D%0Anested%5B%3A4%5D%20%3D%20%5B100,%20100,%20100%5D%0Anested_copy%5B-1%5D%5B%3A%5D%20%3D%20%5B1,%202,%203%5D&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false) shows how both reference the *same* nested `list` object that is changed *in place* from `[40, 50]` into `[1, 2, 3]`.\n", "\n", "Lastly, we use the `del` statement to remove an element." ] }, { "cell_type": "code", - "execution_count": 79, + "execution_count": 84, "metadata": { "slideshow": { "slide_type": "slide" @@ -2433,138 +2548,6 @@ "del nested[-1]" ] }, - { - "cell_type": "code", - "execution_count": 80, - "metadata": { - "slideshow": { - "slide_type": "-" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "[100, 100, 100]" - ] - }, - "execution_count": 80, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "nested" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "skip" - } - }, - "source": [ - "The `del` statement, of course, also works for slices." - ] - }, - { - "cell_type": "code", - "execution_count": 81, - "metadata": { - "slideshow": { - "slide_type": "fragment" - } - }, - "outputs": [], - "source": [ - "del nested[:2]" - ] - }, - { - "cell_type": "code", - "execution_count": 82, - "metadata": { - "slideshow": { - "slide_type": "-" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "[100]" - ] - }, - "execution_count": 82, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "nested" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, - "source": [ - "### List Operations" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "skip" - } - }, - "source": [ - "As with `str` objects, the `+` and `*` operators are overloaded for concatenation and always return *new* `list` objects." - ] - }, - { - "cell_type": "code", - "execution_count": 83, - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, - "outputs": [], - "source": [ - "first = [10, 20, 30]\n", - "second = [40, 50, 60]" - ] - }, - { - "cell_type": "code", - "execution_count": 84, - "metadata": { - "slideshow": { - "slide_type": "-" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "[10, 20, 30, 40, 50, 60]" - ] - }, - "execution_count": 84, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "first + second" - ] - }, { "cell_type": "code", "execution_count": 85, @@ -2577,7 +2560,7 @@ { "data": { "text/plain": [ - "[10, 20, 30, 10, 20, 30]" + "[100, 100, 100]" ] }, "execution_count": 85, @@ -2586,31 +2569,7 @@ } ], "source": [ - "2 * first" - ] - }, - { - "cell_type": "code", - "execution_count": 86, - "metadata": { - "slideshow": { - "slide_type": "skip" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "[40, 50, 60, 40, 50, 60, 40, 50, 60]" - ] - }, - "execution_count": 86, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "second * 3" + "nested" ] }, { @@ -2621,9 +2580,20 @@ } }, "source": [ - "Besides being an operator, the `*` symbol has a second syntactical use, as explained in [PEP 3132](https://www.python.org/dev/peps/pep-3132/) and [PEP 448](https://www.python.org/dev/peps/pep-0448/): It implements what is called **iterable unpacking**. It is *not* an operator syntactically but a notation that Python processes as a literal.\n", - "\n", - "In the example, Python interprets the expression as if the elements of the iterable `second` were placed between `30` and `70` one by one. So, we do not obtain a nested but a *flat* list." + "The `del` statement also works for slices. Here, we remove all references `nested` holds." + ] + }, + { + "cell_type": "code", + "execution_count": 86, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "del nested[:]" ] }, { @@ -2631,14 +2601,14 @@ "execution_count": 87, "metadata": { "slideshow": { - "slide_type": "fragment" + "slide_type": "-" } }, "outputs": [ { "data": { "text/plain": [ - "[30, 40, 50, 60, 70]" + "[]" ] }, "execution_count": 87, @@ -2647,7 +2617,44 @@ } ], "source": [ - "[30, *second, 70]" + "nested" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Mutability for sequences is formalized by the `MutableSequence` ABC in the [collections.abc](https://docs.python.org/3/library/collections.abc.html) module.\n", + "\n", + "So, we can als \"ask\" Python if `nested` is mutable." + ] + }, + { + "cell_type": "code", + "execution_count": 88, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 88, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "isinstance(nested, abc.MutableSequence)" ] }, { @@ -2669,16 +2676,16 @@ } }, "source": [ - "The `list` type is an essential data structure in any real-world Python application, and many typical `list` related algorithms from computer science theory are already built into it at the C level (cf., the [documentation](https://docs.python.org/3/tutorial/datastructures.html#more-on-lists) for a full overview). So, understanding and applying the built-in methods of the `list` type not only speeds up the development process but also makes programs significantly faster.\n", + "The `list` type is an essential data structure in any real-world Python application, and many typical `list`-related algorithms from computer science theory are already built into it at the C level (cf., the [documentation](https://docs.python.org/3/library/stdtypes.html#mutable-sequence-types) or the [tutorial](https://docs.python.org/3/tutorial/datastructures.html#more-on-lists) for a full overview; unfortunately, not all methods have direct links). So, understanding and applying the built-in methods of the `list` type not only speeds up the development process but also makes programs significantly faster.\n", "\n", - "In contrast to the `str` type's methods, the `list` type's methods *always* mutate (i.e., \"change\") an object *in place*. They do *not* create a *new* `list` object and return `None` to indicate that. So, we must *never* assign the return value of `list` methods to the variable holding the list!\n", + "In contrast to the `str` type's methods in [Chapter 6](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/06_text.ipynb#String-Methods) (e.g., [upper()](https://docs.python.org/3/library/stdtypes.html#str.upper) or [lower()](https://docs.python.org/3/library/stdtypes.html#str.lower)), the `list` type's methods that mutate an object do so *in place*. That means they *never* create *new* `list` objects and return `None` to indicate that. So, we must *never* assign the return value of `list` methods to the variable holding the list!\n", "\n", "Let's look at the following `names` example." ] }, { "cell_type": "code", - "execution_count": 88, + "execution_count": 89, "metadata": { "slideshow": { "slide_type": "slide" @@ -2686,7 +2693,7 @@ }, "outputs": [], "source": [ - "names = [\"Carl\", \"Berthold\", \"Achim\", \"Xavier\", \"Peter\"]" + "names = [\"Carl\", \"Peter\"]" ] }, { @@ -2697,12 +2704,12 @@ } }, "source": [ - "To add an object to the end of `names`, we use the append() method. The code cell shows no output indicating that `None` must have been returned." + "To add an object to the end of `names`, we use the `append()` method. The code cell shows no output indicating that `None` must be the return value." ] }, { "cell_type": "code", - "execution_count": 89, + "execution_count": 90, "metadata": { "slideshow": { "slide_type": "fragment" @@ -2715,7 +2722,7 @@ }, { "cell_type": "code", - "execution_count": 90, + "execution_count": 91, "metadata": { "slideshow": { "slide_type": "-" @@ -2725,10 +2732,10 @@ { "data": { "text/plain": [ - "['Carl', 'Berthold', 'Achim', 'Xavier', 'Peter', 'Eckardt']" + "['Carl', 'Peter', 'Eckardt']" ] }, - "execution_count": 90, + "execution_count": 91, "metadata": {}, "output_type": "execute_result" } @@ -2745,12 +2752,12 @@ } }, "source": [ - "With the extend() method, we may also append multiple elements provided by an iterable at once. Here, the iterable is a `list` object itself holding two `str` objects." + "With the `extend()` method, we may also append multiple elements provided by an iterable. Here, the iterable is a `list` object itself holding two `str` objects." ] }, { "cell_type": "code", - "execution_count": 91, + "execution_count": 92, "metadata": { "slideshow": { "slide_type": "fragment" @@ -2763,7 +2770,7 @@ }, { "cell_type": "code", - "execution_count": 92, + "execution_count": 93, "metadata": { "slideshow": { "slide_type": "-" @@ -2773,10 +2780,10 @@ { "data": { "text/plain": [ - "['Carl', 'Berthold', 'Achim', 'Xavier', 'Peter', 'Eckardt', 'Karl', 'Oliver']" + "['Carl', 'Peter', 'Eckardt', 'Karl', 'Oliver']" ] }, - "execution_count": 92, + "execution_count": 93, "metadata": {}, "output_type": "execute_result" } @@ -2793,36 +2800,25 @@ } }, "source": [ - "`list` objects may be sorted *in place* with the [sort()](https://docs.python.org/3/library/stdtypes.html#list.sort) method. That is different from the built-in [sorted()](https://docs.python.org/3/library/functions.html#sorted) function that takes any *finite* and *iterable* object and returns a *new* `list` object with the iterable's elements sorted." - ] - }, - { - "cell_type": "code", - "execution_count": 93, - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "['Achim', 'Berthold', 'Carl', 'Eckardt', 'Karl', 'Oliver', 'Peter', 'Xavier']" - ] - }, - "execution_count": 93, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "sorted(names)" + "Similar to `append()`, we may add a new element at an arbitrary position with the `insert()` method. `insert()` takes two arguments, an *index* and the element to be inserted." ] }, { "cell_type": "code", "execution_count": 94, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "names.insert(1, \"Berthold\")" + ] + }, + { + "cell_type": "code", + "execution_count": 95, "metadata": { "slideshow": { "slide_type": "-" @@ -2832,10 +2828,10 @@ { "data": { "text/plain": [ - "['Carl', 'Berthold', 'Achim', 'Xavier', 'Peter', 'Eckardt', 'Karl', 'Oliver']" + "['Carl', 'Berthold', 'Peter', 'Eckardt', 'Karl', 'Oliver']" ] }, - "execution_count": 94, + "execution_count": 95, "metadata": {}, "output_type": "execute_result" } @@ -2844,9 +2840,90 @@ "names" ] }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "`list` objects may be sorted *in place* with the [sort()](https://docs.python.org/3/library/stdtypes.html#list.sort) method. That is different from the built-in [sorted()](https://docs.python.org/3/library/functions.html#sorted) function that takes any *finite* and *iterable* object and returns a *new* `list` object with the iterable's elements sorted!" + ] + }, { "cell_type": "code", - "execution_count": 95, + "execution_count": 96, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "['Berthold', 'Carl', 'Eckardt', 'Karl', 'Oliver', 'Peter']" + ] + }, + "execution_count": 96, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "sorted(names)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "As the previous code cell created a *new* `list` object, `names` is still unsorted." + ] + }, + { + "cell_type": "code", + "execution_count": 97, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "['Carl', 'Berthold', 'Peter', 'Eckardt', 'Karl', 'Oliver']" + ] + }, + "execution_count": 97, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "names" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Let's sort the elements in `names` instead." + ] + }, + { + "cell_type": "code", + "execution_count": 98, "metadata": { "slideshow": { "slide_type": "fragment" @@ -2859,7 +2936,7 @@ }, { "cell_type": "code", - "execution_count": 96, + "execution_count": 99, "metadata": { "slideshow": { "slide_type": "-" @@ -2869,10 +2946,10 @@ { "data": { "text/plain": [ - "['Achim', 'Berthold', 'Carl', 'Eckardt', 'Karl', 'Oliver', 'Peter', 'Xavier']" + "['Berthold', 'Carl', 'Eckardt', 'Karl', 'Oliver', 'Peter']" ] }, - "execution_count": 96, + "execution_count": 99, "metadata": {}, "output_type": "execute_result" } @@ -2889,12 +2966,12 @@ } }, "source": [ - "To sort in reverse order, we pass a keyword-only `reverse=True` argument to either the [sort()](https://docs.python.org/3/library/stdtypes.html#list.sort) method or the [sorted()](https://docs.python.org/3/library/functions.html#sorted) function. In the latter case, we could also use the [reversed()](https://docs.python.org/3/library/functions.html#reversed) built-in instead; however, that *neither* returns a new `list` object *nor* changes the existing one in place. We revisit it at the end of this chapter." + "To sort in reverse order, we pass a keyword-only `reverse=True` argument to either the [sort()](https://docs.python.org/3/library/stdtypes.html#list.sort) method or the [sorted()](https://docs.python.org/3/library/functions.html#sorted) function." ] }, { "cell_type": "code", - "execution_count": 97, + "execution_count": 100, "metadata": { "slideshow": { "slide_type": "slide" @@ -2907,7 +2984,7 @@ }, { "cell_type": "code", - "execution_count": 98, + "execution_count": 101, "metadata": { "slideshow": { "slide_type": "-" @@ -2917,10 +2994,10 @@ { "data": { "text/plain": [ - "['Xavier', 'Peter', 'Oliver', 'Karl', 'Eckardt', 'Carl', 'Berthold', 'Achim']" + "['Peter', 'Oliver', 'Karl', 'Eckardt', 'Carl', 'Berthold']" ] }, - "execution_count": 98, + "execution_count": 101, "metadata": {}, "output_type": "execute_result" } @@ -2937,14 +3014,18 @@ } }, "source": [ - "Both, the [sort()](https://docs.python.org/3/library/stdtypes.html#list.sort) method and the [sorted()](https://docs.python.org/3/library/functions.html#sorted) function, also accept a keyword-only `key` argument that must be a reference to a `function` object accepting one positional argument. Then, the elements in the `list` object are passed to that on a one-by-one basis, and the return values are used as the **sort keys**.\n", + "The [sort()](https://docs.python.org/3/library/stdtypes.html#list.sort) method and the [sorted()](https://docs.python.org/3/library/functions.html#sorted) function sort the elements in `names` in alphabetical order, forward or backward. However, that does *not* hold in general.\n", + "\n", + "We mention above that `list` objects may contain objects of *any* type and even of *mixed* types. Because of that, the sorting is **[delegated](https://en.wikipedia.org/wiki/Delegation_(object-oriented_programming))** to the elements in a `list` object. In a way, Python \"asks\" the elements in a `list` object to sort themselves. As `names` contains only `str` objects, they are sorted according the the comparison rules explained in [Chapter 6](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/06_text.ipynb#String-Comparison).\n", + "\n", + "To customize the sorting, we pass a keyword-only `key` argument to [sort()](https://docs.python.org/3/library/stdtypes.html#list.sort) or [sorted()](https://docs.python.org/3/library/functions.html#sorted), which must be a `function` object accepting *one* positional argument. Then, the elements in the `list` object are passed to that one by one, and the return values are used as the **sort keys**. The `key` argument is also a popular use case for `lambda` expressions.\n", "\n", "For example, to sort `names` not by alphabet but by the names' lengths, we pass in a reference to the built-in [len()](https://docs.python.org/3/library/functions.html#len) function as `key=len`. Note that there are *no* parentheses after `len`!" ] }, { "cell_type": "code", - "execution_count": 99, + "execution_count": 102, "metadata": { "slideshow": { "slide_type": "fragment" @@ -2963,103 +3044,9 @@ } }, "source": [ - "If two names have the same length, their relative order is kept as is. A [sorting algorithm](https://en.wikipedia.org/wiki/Sorting_algorithm) is called **[stable](https://en.wikipedia.org/wiki/Sorting_algorithm#Stability)** if it has that property. That is why `\"Karl\"` comes before `\"Carl\" ` below.\n", + "If two names have the same length, their relative order is kept as is. That is why `\"Karl\"` comes before `\"Carl\" ` below. A [sorting algorithm](https://en.wikipedia.org/wiki/Sorting_algorithm) with that property is called **[stable](https://en.wikipedia.org/wiki/Sorting_algorithm#Stability)**.\n", "\n", - "Sorting is an important topic in programming and we refer to the official [HOWTO](https://docs.python.org/3/howto/sorting.html) for a more comprehensive introduction." - ] - }, - { - "cell_type": "code", - "execution_count": 100, - "metadata": { - "slideshow": { - "slide_type": "-" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "['Karl', 'Carl', 'Peter', 'Achim', 'Xavier', 'Oliver', 'Eckardt', 'Berthold']" - ] - }, - "execution_count": 100, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "names" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "skip" - } - }, - "source": [ - "The pop() method removes the last element from a `list` object *and* returns it." - ] - }, - { - "cell_type": "code", - "execution_count": 101, - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "'Berthold'" - ] - }, - "execution_count": 101, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "names.pop()" - ] - }, - { - "cell_type": "code", - "execution_count": 102, - "metadata": { - "slideshow": { - "slide_type": "-" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "['Karl', 'Carl', 'Peter', 'Achim', 'Xavier', 'Oliver', 'Eckardt']" - ] - }, - "execution_count": 102, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "names" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "skip" - } - }, - "source": [ - "It takes an optional index argument and removes that instead." + "Sorting is an important topic in programming, and we refer to the official [HOWTO](https://docs.python.org/3/howto/sorting.html) for a more comprehensive introduction." ] }, { @@ -3067,14 +3054,14 @@ "execution_count": 103, "metadata": { "slideshow": { - "slide_type": "fragment" + "slide_type": "-" } }, "outputs": [ { "data": { "text/plain": [ - "'Carl'" + "['Karl', 'Carl', 'Peter', 'Oliver', 'Eckardt', 'Berthold']" ] }, "execution_count": 103, @@ -3083,12 +3070,36 @@ } ], "source": [ - "names.pop(1)" + "names" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "`sort(reverse=True)` is different from the `reverse()` method. Whereas the former applies some sorting rule in reverse order, the latter simply reverses the elements in a `list` object." ] }, { "cell_type": "code", "execution_count": 104, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "names.reverse()" + ] + }, + { + "cell_type": "code", + "execution_count": 105, "metadata": { "slideshow": { "slide_type": "-" @@ -3098,10 +3109,10 @@ { "data": { "text/plain": [ - "['Karl', 'Peter', 'Achim', 'Xavier', 'Oliver', 'Eckardt']" + "['Berthold', 'Eckardt', 'Oliver', 'Peter', 'Carl', 'Karl']" ] }, - "execution_count": 104, + "execution_count": 105, "metadata": {}, "output_type": "execute_result" } @@ -3118,12 +3129,158 @@ } }, "source": [ - "Instead of removing an element by its index, we can remove it by its value with the remove() method. Behind the scenes, Python then compares the object passed as its argument, `\"Peter\"` in the example, sequentially to each element with the `==` operator and removes the first one that evaluates equal." + "The `pop()` method removes the *last* element from a `list` object *and* returns it. Below we **capture** the `removed` element to show that the return value is not `None` as with all the methods introduced so far." ] }, { "cell_type": "code", - "execution_count": 105, + "execution_count": 106, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "removed = names.pop()" + ] + }, + { + "cell_type": "code", + "execution_count": 107, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "'Karl'" + ] + }, + "execution_count": 107, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "removed" + ] + }, + { + "cell_type": "code", + "execution_count": 108, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "['Berthold', 'Eckardt', 'Oliver', 'Peter', 'Carl']" + ] + }, + "execution_count": 108, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "names" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "`pop()` takes an optional *index* argument and removes that instead.\n", + "\n", + "So, to remove the second element, `\"Eckhardt\"`, from `names`, we write this." + ] + }, + { + "cell_type": "code", + "execution_count": 109, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "removed = names.pop(1)" + ] + }, + { + "cell_type": "code", + "execution_count": 110, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "'Eckardt'" + ] + }, + "execution_count": 110, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "removed" + ] + }, + { + "cell_type": "code", + "execution_count": 111, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "['Berthold', 'Oliver', 'Peter', 'Carl']" + ] + }, + "execution_count": 111, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "names" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Instead of removing an element by its index, we can also remove it by its value with the `remove()` method. Behind the scenes, Python then compares the object to be removed, `\"Peter\"` in the example, sequentially to each element with the `==` operator and removes the *first* one that evaluates equal to it. `remove()` does *not* return the removed element." + ] + }, + { + "cell_type": "code", + "execution_count": 112, "metadata": { "slideshow": { "slide_type": "slide" @@ -3136,7 +3293,7 @@ }, { "cell_type": "code", - "execution_count": 106, + "execution_count": 113, "metadata": { "slideshow": { "slide_type": "-" @@ -3146,10 +3303,10 @@ { "data": { "text/plain": [ - "['Karl', 'Achim', 'Xavier', 'Oliver', 'Eckardt']" + "['Berthold', 'Oliver', 'Carl']" ] }, - "execution_count": 106, + "execution_count": 113, "metadata": {}, "output_type": "execute_result" } @@ -3166,12 +3323,12 @@ } }, "source": [ - "remove() raises a `ValueError` if the value is not found." + "Also, `remove()` raises a `ValueError` if the value is not found." ] }, { "cell_type": "code", - "execution_count": 107, + "execution_count": 114, "metadata": { "slideshow": { "slide_type": "skip" @@ -3185,7 +3342,7 @@ "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mValueError\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[0mnames\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mremove\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Peter\"\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\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mnames\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mremove\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Peter\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mValueError\u001b[0m: list.remove(x): x not in list" ] } @@ -3202,211 +3359,7 @@ } }, "source": [ - "`list` objects implement an index() method that returns the position of the first occurrence of an element. It fails loudly with a `ValueError` if the element cannot be found by value." - ] - }, - { - "cell_type": "code", - "execution_count": 108, - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "['Karl', 'Achim', 'Xavier', 'Oliver', 'Eckardt']" - ] - }, - "execution_count": 108, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "names" - ] - }, - { - "cell_type": "code", - "execution_count": 109, - "metadata": { - "slideshow": { - "slide_type": "-" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "3" - ] - }, - "execution_count": 109, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "names.index(\"Oliver\")" - ] - }, - { - "cell_type": "code", - "execution_count": 110, - "metadata": { - "slideshow": { - "slide_type": "-" - } - }, - "outputs": [ - { - "ename": "ValueError", - "evalue": "'Carl' is not in list", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mValueError\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[0mnames\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mindex\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Carl\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", - "\u001b[0;31mValueError\u001b[0m: 'Carl' is not in list" - ] - } - ], - "source": [ - "names.index(\"Carl\")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "skip" - } - }, - "source": [ - "The count() method returns the number of occurrences of a value." - ] - }, - { - "cell_type": "code", - "execution_count": 111, - "metadata": { - "slideshow": { - "slide_type": "fragment" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "1" - ] - }, - "execution_count": 111, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "names.count(\"Xavier\")" - ] - }, - { - "cell_type": "code", - "execution_count": 112, - "metadata": { - "slideshow": { - "slide_type": "-" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "0" - ] - }, - "execution_count": 112, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "names.count(\"Yves\")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, - "source": [ - "### List Comparison" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "skip" - } - }, - "source": [ - "The relational operators also work with `list` objects; yet another example of operator overloading.\n", - "\n", - "Comparison is made in a pairwise fashion until the first pair of elements does not evaluate equal or one of the `list` objects ends. The exact comparison rules depend on the elements and not the `list` object. We say that comparison is **[delegated](https://en.wikipedia.org/wiki/Delegation_(object-oriented_programming))** to the objects to be compared. Usually, all elements are of the *same* type. Then, the comparison is straightforward and conceptually the same as for string comparison in [Chapter 6](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/06_text.ipynb#String-Comparison)." - ] - }, - { - "cell_type": "code", - "execution_count": 113, - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "['Karl', 'Achim', 'Xavier', 'Oliver', 'Eckardt']" - ] - }, - "execution_count": 113, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "names" - ] - }, - { - "cell_type": "code", - "execution_count": 114, - "metadata": { - "slideshow": { - "slide_type": "-" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "False" - ] - }, - "execution_count": 114, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "names < [\"Karl\", \"Achim\", \"Oliver\", \"Xavier\", \"Eckardt\"]" + "`list` objects implement an `index()` method that returns the position of the first element that compares equal to its argument. It fails *loudly* with a `ValueError` if no element compares equal." ] }, { @@ -3414,14 +3367,14 @@ "execution_count": 115, "metadata": { "slideshow": { - "slide_type": "-" + "slide_type": "slide" } }, "outputs": [ { "data": { "text/plain": [ - "True" + "['Berthold', 'Oliver', 'Carl']" ] }, "execution_count": 115, @@ -3430,18 +3383,7 @@ } ], "source": [ - "names < [\"Karl\", \"Xavier\", \"Achim\", \"Oliver\", \"Eckardt\"]" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "skip" - } - }, - "source": [ - "The shorter `list` object is considered \"smaller,\" and vice versa." + "names" ] }, { @@ -3456,7 +3398,7 @@ { "data": { "text/plain": [ - "False" + "1" ] }, "execution_count": 116, @@ -3465,7 +3407,7 @@ } ], "source": [ - "names < [\"Karl\", \"Achim\", \"Xavier\", \"Oliver\"]" + "names.index(\"Oliver\")" ] }, { @@ -3476,20 +3418,597 @@ "slide_type": "-" } }, + "outputs": [ + { + "ename": "ValueError", + "evalue": "'Karl' is not in list", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mValueError\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[0mnames\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mindex\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Karl\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;31mValueError\u001b[0m: 'Karl' is not in list" + ] + } + ], + "source": [ + "names.index(\"Karl\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "The `count()` method returns the number of elements that compare equal to its argument." + ] + }, + { + "cell_type": "code", + "execution_count": 118, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, "outputs": [ { "data": { "text/plain": [ - "True" + "1" ] }, - "execution_count": 117, + "execution_count": 118, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "names < [\"Karl\", \"Achim\", \"Xavier\", \"Oliver\", \"Eckardt\", \"Peter\"]" + "names.count(\"Carl\")" + ] + }, + { + "cell_type": "code", + "execution_count": 119, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "0" + ] + }, + "execution_count": 119, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "names.count(\"Karl\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Two more methods, `copy()` and `clear()`, are *syntactic sugar* and replace working with slices.\n", + "\n", + "`copy()` creates a *shallow* copy. So, `names.copy()` below does the same as taking a full slice with `names[:]`, and the caveats from above apply, too." + ] + }, + { + "cell_type": "code", + "execution_count": 120, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "names_copy = names.copy()" + ] + }, + { + "cell_type": "code", + "execution_count": 121, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "['Berthold', 'Oliver', 'Carl']" + ] + }, + "execution_count": 121, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "names_copy" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "`clear()` removes all references from a `list` object. So, `names_copy.clear()` is the same as `del names_copy[:]`." + ] + }, + { + "cell_type": "code", + "execution_count": 122, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "names_copy.clear()" + ] + }, + { + "cell_type": "code", + "execution_count": 123, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "[]" + ] + }, + "execution_count": 123, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "names_copy" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Many methods introduced in this section are mentioned in the [collections.abc](https://docs.python.org/3/library/collections.abc.html) module's documentation as well: While the `index()` and `count()` methods come with any data type that is a `Sequence`, the `append()`, `extend()`, `insert()`, `reverse()`, `pop()`, and `remove()` methods are part of any `MutableSequence` type. The `sort()`, `copy()`, and `clear()` methods are `list`-specific.\n", + "\n", + "So, being a sequence does not only imply the four *behaviors* specified above, but also means that a data type comes with certain standardized methods." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "### List Operations" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "As with `str` objects, the `+` and `*` operators are overloaded for concatenation and always return a *new* `list` object. The references in this newly created `list` object reference the *same* objects as the two original `list` objects. So, the same caveat as with *shallow* copies from above applies!" + ] + }, + { + "cell_type": "code", + "execution_count": 124, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "['Berthold', 'Oliver', 'Carl']" + ] + }, + "execution_count": 124, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "names" + ] + }, + { + "cell_type": "code", + "execution_count": 125, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [], + "source": [ + "more_names = [\"Diedrich\", \"Yves\"]" + ] + }, + { + "cell_type": "code", + "execution_count": 126, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "['Berthold', 'Oliver', 'Carl', 'Diedrich', 'Yves']" + ] + }, + "execution_count": 126, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "names + more_names" + ] + }, + { + "cell_type": "code", + "execution_count": 127, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "['Berthold', 'Oliver', 'Carl', 'Berthold', 'Oliver', 'Carl']" + ] + }, + "execution_count": 127, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "2 * names" + ] + }, + { + "cell_type": "code", + "execution_count": 128, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "['Diedrich', 'Yves', 'Diedrich', 'Yves', 'Diedrich', 'Yves']" + ] + }, + "execution_count": 128, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "more_names * 3" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Besides being an operator, the `*` symbol has a second syntactical use, as explained in [PEP 3132](https://www.python.org/dev/peps/pep-3132/) and [PEP 448](https://www.python.org/dev/peps/pep-0448/): It implements what is called **iterable unpacking**. It is *not* an operator syntactically but a notation that Python reads as a literal.\n", + "\n", + "In the example, Python interprets the expression as if the elements of the iterable `names` were placed between `\"Achim\"` and `\"Xavier\"` one by one. So, we do not obtain a nested but a *flat* list." + ] + }, + { + "cell_type": "code", + "execution_count": 129, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "['Achim', 'Berthold', 'Oliver', 'Carl', 'Xavier']" + ] + }, + "execution_count": 129, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "[\"Achim\", *names, \"Xavier\"]" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Effectively, Python reads that as if we wrote the following." + ] + }, + { + "cell_type": "code", + "execution_count": 130, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "['Achim', 'Berthold', 'Oliver', 'Carl', 'Xavier']" + ] + }, + "execution_count": 130, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "[\"Achim\", names[0], names[1], names[2], \"Xavier\"]" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "#### List Comparison" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "The relational operators also work with `list` objects; yet another example of operator overloading.\n", + "\n", + "Comparison is made in a pairwise fashion until the first pair of elements does not evaluate equal or one of the `list` objects ends. The exact comparison rules depend on the elements and not the `list` objects. As with [sort()](https://docs.python.org/3/library/stdtypes.html#list.sort) or [sorted()](https://docs.python.org/3/library/functions.html#sorted) above, comparison is *delegated* to the objects to be compared, and Python \"asks\" the elements in the two `list` objects to compare themselves. Usually, all elements are of the *same* type, and comparison is straightforward." + ] + }, + { + "cell_type": "code", + "execution_count": 131, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "['Berthold', 'Oliver', 'Carl']" + ] + }, + "execution_count": 131, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "names" + ] + }, + { + "cell_type": "code", + "execution_count": 132, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 132, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "names == [\"Berthold\", \"Oliver\", \"Carl\"]" + ] + }, + { + "cell_type": "code", + "execution_count": 133, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 133, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "names != [\"Berthold\", \"Oliver\", \"Karl\"]" + ] + }, + { + "cell_type": "code", + "execution_count": 134, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 134, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "names < [\"Berthold\", \"Oliver\", \"Karl\"]" + ] + }, + { + "cell_type": "code", + "execution_count": 135, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 135, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "[\"Achim\", \"Oliver\", \"Carl\"] < names" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "If two `list` objects have a different number of elements and all overlapping elements compare equal, the shorter `list` object is considered \"smaller.\" That rule is a common cause for *semantic* errors in a program." + ] + }, + { + "cell_type": "code", + "execution_count": 136, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 136, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "[\"Berthold\", \"Oliver\"] < names" + ] + }, + { + "cell_type": "code", + "execution_count": 137, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 137, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "names < [\"Berthold\", \"Oliver\", \"Carl\", \"Xavier\"]" ] }, { @@ -3518,7 +4037,7 @@ }, { "cell_type": "code", - "execution_count": 118, + "execution_count": 138, "metadata": { "slideshow": { "slide_type": "slide" @@ -3531,7 +4050,7 @@ }, { "cell_type": "code", - "execution_count": 119, + "execution_count": 139, "metadata": { "slideshow": { "slide_type": "-" @@ -3553,14 +4072,14 @@ } }, "source": [ - "While this function is being executed, two variables, namely `letters` in the global scope and `arg` inside the function's local scope, point to the *same* `list` object in memory. Furthermore, the passed in `arg` is also the return value.\n", + "While this function is being executed, two variables, namely `letters` in the global scope and `arg` inside the function's local scope, reference the *same* `list` object in memory. Furthermore, the passed in `arg` is also the return value.\n", "\n", - "So, after the function call, `letters_with_xyz` and `letters` are **aliases** as well, pointing to the *same* object." + "So, after the function call, `letters_with_xyz` and `letters` are **aliases** as well, referencing the *same* object." ] }, { "cell_type": "code", - "execution_count": 120, + "execution_count": 140, "metadata": { "slideshow": { "slide_type": "fragment" @@ -3573,7 +4092,7 @@ }, { "cell_type": "code", - "execution_count": 121, + "execution_count": 141, "metadata": { "slideshow": { "slide_type": "-" @@ -3586,7 +4105,7 @@ "['a', 'b', 'c', 'x', 'y', 'z']" ] }, - "execution_count": 121, + "execution_count": 141, "metadata": {}, "output_type": "execute_result" } @@ -3597,7 +4116,7 @@ }, { "cell_type": "code", - "execution_count": 122, + "execution_count": 142, "metadata": { "slideshow": { "slide_type": "-" @@ -3610,7 +4129,7 @@ "['a', 'b', 'c', 'x', 'y', 'z']" ] }, - "execution_count": 122, + "execution_count": 142, "metadata": {}, "output_type": "execute_result" } @@ -3636,7 +4155,7 @@ }, { "cell_type": "code", - "execution_count": 123, + "execution_count": 143, "metadata": { "slideshow": { "slide_type": "slide" @@ -3649,7 +4168,7 @@ }, { "cell_type": "code", - "execution_count": 124, + "execution_count": 144, "metadata": { "slideshow": { "slide_type": "-" @@ -3666,7 +4185,7 @@ }, { "cell_type": "code", - "execution_count": 125, + "execution_count": 145, "metadata": { "slideshow": { "slide_type": "fragment" @@ -3679,7 +4198,7 @@ }, { "cell_type": "code", - "execution_count": 126, + "execution_count": 146, "metadata": { "scrolled": true, "slideshow": { @@ -3693,7 +4212,7 @@ "['a', 'b', 'c', 'x', 'y', 'z']" ] }, - "execution_count": 126, + "execution_count": 146, "metadata": {}, "output_type": "execute_result" } @@ -3704,7 +4223,7 @@ }, { "cell_type": "code", - "execution_count": 127, + "execution_count": 147, "metadata": { "slideshow": { "slide_type": "-" @@ -3717,7 +4236,7 @@ "['a', 'b', 'c']" ] }, - "execution_count": 127, + "execution_count": 147, "metadata": {}, "output_type": "execute_result" } @@ -3739,7 +4258,7 @@ }, { "cell_type": "code", - "execution_count": 128, + "execution_count": 148, "metadata": { "slideshow": { "slide_type": "slide" @@ -3752,7 +4271,7 @@ }, { "cell_type": "code", - "execution_count": 129, + "execution_count": 149, "metadata": { "slideshow": { "slide_type": "-" @@ -3768,7 +4287,7 @@ }, { "cell_type": "code", - "execution_count": 130, + "execution_count": 150, "metadata": { "slideshow": { "slide_type": "fragment" @@ -3781,7 +4300,7 @@ }, { "cell_type": "code", - "execution_count": 131, + "execution_count": 151, "metadata": { "slideshow": { "slide_type": "-" @@ -3794,7 +4313,7 @@ "['a', 'b', 'c', 'x', 'y', 'z']" ] }, - "execution_count": 131, + "execution_count": 151, "metadata": {}, "output_type": "execute_result" } @@ -3816,7 +4335,7 @@ }, { "cell_type": "code", - "execution_count": 132, + "execution_count": 152, "metadata": { "slideshow": { "slide_type": "skip" @@ -3829,7 +4348,7 @@ }, { "cell_type": "code", - "execution_count": 133, + "execution_count": 153, "metadata": { "slideshow": { "slide_type": "skip" @@ -3842,7 +4361,7 @@ "['a', 'b', 'c', 'x', 'y', 'z', 'x', 'y', 'z']" ] }, - "execution_count": 133, + "execution_count": 153, "metadata": {}, "output_type": "execute_result" } @@ -3886,7 +4405,7 @@ }, { "cell_type": "code", - "execution_count": 134, + "execution_count": 154, "metadata": { "slideshow": { "slide_type": "slide" @@ -3899,7 +4418,7 @@ }, { "cell_type": "code", - "execution_count": 135, + "execution_count": 155, "metadata": { "slideshow": { "slide_type": "-" @@ -3912,7 +4431,7 @@ "(7, 11, 8, 5, 3, 12, 2, 6, 9, 10, 1, 4)" ] }, - "execution_count": 135, + "execution_count": 155, "metadata": {}, "output_type": "execute_result" } @@ -3934,7 +4453,7 @@ }, { "cell_type": "code", - "execution_count": 136, + "execution_count": 156, "metadata": { "slideshow": { "slide_type": "fragment" @@ -3947,7 +4466,7 @@ }, { "cell_type": "code", - "execution_count": 137, + "execution_count": 157, "metadata": { "slideshow": { "slide_type": "-" @@ -3960,7 +4479,7 @@ "(7, 11, 8, 5, 3, 12, 2, 6, 9, 10, 1, 4)" ] }, - "execution_count": 137, + "execution_count": 157, "metadata": {}, "output_type": "execute_result" } @@ -3982,20 +4501,20 @@ }, { "cell_type": "code", - "execution_count": 138, + "execution_count": 158, "metadata": { "slideshow": { - "slide_type": "fragment" + "slide_type": "skip" } }, "outputs": [ { "data": { "text/plain": [ - "140322025210648" + "140157873009144" ] }, - "execution_count": 138, + "execution_count": 158, "metadata": {}, "output_type": "execute_result" } @@ -4006,10 +4525,10 @@ }, { "cell_type": "code", - "execution_count": 139, + "execution_count": 159, "metadata": { "slideshow": { - "slide_type": "-" + "slide_type": "skip" } }, "outputs": [ @@ -4019,7 +4538,7 @@ "tuple" ] }, - "execution_count": 139, + "execution_count": 159, "metadata": {}, "output_type": "execute_result" } @@ -4041,10 +4560,10 @@ }, { "cell_type": "code", - "execution_count": 140, + "execution_count": 160, "metadata": { "slideshow": { - "slide_type": "slide" + "slide_type": "skip" } }, "outputs": [], @@ -4054,10 +4573,10 @@ }, { "cell_type": "code", - "execution_count": 141, + "execution_count": 161, "metadata": { "slideshow": { - "slide_type": "-" + "slide_type": "skip" } }, "outputs": [ @@ -4067,7 +4586,7 @@ "()" ] }, - "execution_count": 141, + "execution_count": 161, "metadata": {}, "output_type": "execute_result" } @@ -4078,10 +4597,10 @@ }, { "cell_type": "code", - "execution_count": 142, + "execution_count": 162, "metadata": { "slideshow": { - "slide_type": "-" + "slide_type": "skip" } }, "outputs": [ @@ -4091,7 +4610,7 @@ "tuple" ] }, - "execution_count": 142, + "execution_count": 162, "metadata": {}, "output_type": "execute_result" } @@ -4113,10 +4632,10 @@ }, { "cell_type": "code", - "execution_count": 143, + "execution_count": 163, "metadata": { "slideshow": { - "slide_type": "slide" + "slide_type": "skip" } }, "outputs": [], @@ -4126,10 +4645,10 @@ }, { "cell_type": "code", - "execution_count": 144, + "execution_count": 164, "metadata": { "slideshow": { - "slide_type": "-" + "slide_type": "skip" } }, "outputs": [ @@ -4139,7 +4658,7 @@ "(1,)" ] }, - "execution_count": 144, + "execution_count": 164, "metadata": {}, "output_type": "execute_result" } @@ -4150,10 +4669,10 @@ }, { "cell_type": "code", - "execution_count": 145, + "execution_count": 165, "metadata": { "slideshow": { - "slide_type": "-" + "slide_type": "skip" } }, "outputs": [ @@ -4163,7 +4682,7 @@ "tuple" ] }, - "execution_count": 145, + "execution_count": 165, "metadata": {}, "output_type": "execute_result" } @@ -4174,7 +4693,7 @@ }, { "cell_type": "code", - "execution_count": 146, + "execution_count": 166, "metadata": { "slideshow": { "slide_type": "skip" @@ -4187,7 +4706,7 @@ }, { "cell_type": "code", - "execution_count": 147, + "execution_count": 167, "metadata": { "slideshow": { "slide_type": "skip" @@ -4200,7 +4719,7 @@ "1" ] }, - "execution_count": 147, + "execution_count": 167, "metadata": {}, "output_type": "execute_result" } @@ -4211,7 +4730,7 @@ }, { "cell_type": "code", - "execution_count": 148, + "execution_count": 168, "metadata": { "slideshow": { "slide_type": "skip" @@ -4224,7 +4743,7 @@ "int" ] }, - "execution_count": 148, + "execution_count": 168, "metadata": {}, "output_type": "execute_result" } @@ -4246,10 +4765,10 @@ }, { "cell_type": "code", - "execution_count": 149, + "execution_count": 169, "metadata": { "slideshow": { - "slide_type": "fragment" + "slide_type": "skip" } }, "outputs": [ @@ -4259,7 +4778,7 @@ "(1,)" ] }, - "execution_count": 149, + "execution_count": 169, "metadata": {}, "output_type": "execute_result" } @@ -4268,6 +4787,30 @@ "tuple([1])" ] }, + { + "cell_type": "code", + "execution_count": 170, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "('i', 't', 'e', 'r', 'a', 'b', 'l', 'e')" + ] + }, + "execution_count": 170, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "tuple(\"iterable\")" + ] + }, { "cell_type": "markdown", "metadata": { @@ -4294,10 +4837,10 @@ }, { "cell_type": "code", - "execution_count": 150, + "execution_count": 171, "metadata": { "slideshow": { - "slide_type": "slide" + "slide_type": "skip" } }, "outputs": [ @@ -4307,7 +4850,7 @@ "True" ] }, - "execution_count": 150, + "execution_count": 171, "metadata": {}, "output_type": "execute_result" } @@ -4329,10 +4872,10 @@ }, { "cell_type": "code", - "execution_count": 151, + "execution_count": 172, "metadata": { "slideshow": { - "slide_type": "-" + "slide_type": "slide" } }, "outputs": [ @@ -4342,7 +4885,7 @@ "12" ] }, - "execution_count": 151, + "execution_count": 172, "metadata": {}, "output_type": "execute_result" } @@ -4364,7 +4907,7 @@ }, { "cell_type": "code", - "execution_count": 152, + "execution_count": 173, "metadata": { "slideshow": { "slide_type": "fragment" @@ -4386,7 +4929,7 @@ }, { "cell_type": "code", - "execution_count": 153, + "execution_count": 174, "metadata": { "slideshow": { "slide_type": "-" @@ -4419,7 +4962,7 @@ }, { "cell_type": "code", - "execution_count": 154, + "execution_count": 175, "metadata": { "slideshow": { "slide_type": "fragment" @@ -4432,7 +4975,7 @@ "False" ] }, - "execution_count": 154, + "execution_count": 175, "metadata": {}, "output_type": "execute_result" } @@ -4443,7 +4986,7 @@ }, { "cell_type": "code", - "execution_count": 155, + "execution_count": 176, "metadata": { "slideshow": { "slide_type": "-" @@ -4456,7 +4999,7 @@ "True" ] }, - "execution_count": 155, + "execution_count": 176, "metadata": {}, "output_type": "execute_result" } @@ -4467,7 +5010,7 @@ }, { "cell_type": "code", - "execution_count": 156, + "execution_count": 177, "metadata": { "slideshow": { "slide_type": "skip" @@ -4480,7 +5023,7 @@ "True" ] }, - "execution_count": 156, + "execution_count": 177, "metadata": {}, "output_type": "execute_result" } @@ -4502,7 +5045,7 @@ }, { "cell_type": "code", - "execution_count": 157, + "execution_count": 178, "metadata": { "slideshow": { "slide_type": "slide" @@ -4515,7 +5058,7 @@ "7" ] }, - "execution_count": 157, + "execution_count": 178, "metadata": {}, "output_type": "execute_result" } @@ -4526,7 +5069,7 @@ }, { "cell_type": "code", - "execution_count": 158, + "execution_count": 179, "metadata": { "slideshow": { "slide_type": "-" @@ -4539,7 +5082,7 @@ "4" ] }, - "execution_count": 158, + "execution_count": 179, "metadata": {}, "output_type": "execute_result" } @@ -4550,7 +5093,7 @@ }, { "cell_type": "code", - "execution_count": 159, + "execution_count": 180, "metadata": { "slideshow": { "slide_type": "-" @@ -4563,7 +5106,7 @@ "(2, 6, 9, 10, 1, 4)" ] }, - "execution_count": 159, + "execution_count": 180, "metadata": {}, "output_type": "execute_result" } @@ -4585,7 +5128,7 @@ }, { "cell_type": "code", - "execution_count": 160, + "execution_count": 181, "metadata": { "slideshow": { "slide_type": "slide" @@ -4599,7 +5142,7 @@ "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[0mnumbers\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m-\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;36m99\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mnumbers\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m-\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;36m99\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mTypeError\u001b[0m: 'tuple' object does not support item assignment" ] } @@ -4616,44 +5159,31 @@ } }, "source": [ - "If we need to \"modify\" the `tuple` object, we must create a *new* `tuple` object, for example, like so: We take a slice of the elements we want to keep and use the overloaded `+` operator to concatenate the slice with another `tuple` object." + "We can verify the immutability with the `MutableSequence` ABC from the [collections.abc](https://docs.python.org/3/library/collections.abc.html) module: [isinstance()](https://docs.python.org/3/library/functions.html#isinstance) returns `False`. So, a data type that is a `Sequence` may be mutable or not. If it is a `MutableSequence`, it is mutable. If it is *not* a `MutableSequence`, it is *immutable*. There is *no* `ImmutableSequence` ABC." ] }, { "cell_type": "code", - "execution_count": 161, + "execution_count": 182, "metadata": { "slideshow": { - "slide_type": "fragment" - } - }, - "outputs": [], - "source": [ - "new_numbers = numbers[:-1] + (99,)" - ] - }, - { - "cell_type": "code", - "execution_count": 162, - "metadata": { - "slideshow": { - "slide_type": "-" + "slide_type": "skip" } }, "outputs": [ { "data": { "text/plain": [ - "(7, 11, 8, 5, 3, 12, 2, 6, 9, 10, 1, 99)" + "False" ] }, - "execution_count": 162, + "execution_count": 182, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "new_numbers" + "isinstance(numbers, abc.MutableSequence)" ] }, { @@ -4664,17 +5194,44 @@ } }, "source": [ - "The `*` operator works as well." + "The `+` and `*` operators work with `tuple` objects as well. However, we should *not* do that as the whole point of immutability is to *not* mutate an object.\n", + "\n", + "So, instead of writing something like below, we should use a `list` object and call its `append()` method." ] }, { "cell_type": "code", - "execution_count": 163, + "execution_count": 183, "metadata": { "slideshow": { "slide_type": "skip" } }, + "outputs": [ + { + "data": { + "text/plain": [ + "(7, 11, 8, 5, 3, 12, 2, 6, 9, 10, 1, 4, 99)" + ] + }, + "execution_count": 183, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "numbers + (99,) " + ] + }, + { + "cell_type": "code", + "execution_count": 184, + "metadata": { + "scrolled": false, + "slideshow": { + "slide_type": "skip" + } + }, "outputs": [ { "data": { @@ -4682,7 +5239,7 @@ "(7, 11, 8, 5, 3, 12, 2, 6, 9, 10, 1, 4, 7, 11, 8, 5, 3, 12, 2, 6, 9, 10, 1, 4)" ] }, - "execution_count": 163, + "execution_count": 184, "metadata": {}, "output_type": "execute_result" } @@ -4699,12 +5256,12 @@ } }, "source": [ - "Being immutable, `tuple` objects only provide the count() and index() methods." + "Being immutable, `tuple` objects only provide the `count()` and `index()` methods of `Sequence` types. The `append()`, `extend()`, `insert()`, `reverse()`, `pop()`, and `remove()` methods of `MutableSequence` types are *not* available. The same holds for the `list`-specific methods `sort()`, `copy()`, and `clear()`." ] }, { "cell_type": "code", - "execution_count": 164, + "execution_count": 185, "metadata": { "slideshow": { "slide_type": "skip" @@ -4717,7 +5274,7 @@ "0" ] }, - "execution_count": 164, + "execution_count": 185, "metadata": {}, "output_type": "execute_result" } @@ -4728,7 +5285,7 @@ }, { "cell_type": "code", - "execution_count": 165, + "execution_count": 186, "metadata": { "slideshow": { "slide_type": "skip" @@ -4741,7 +5298,7 @@ "10" ] }, - "execution_count": 165, + "execution_count": 186, "metadata": {}, "output_type": "execute_result" } @@ -4758,15 +5315,39 @@ } }, "source": [ - "The relational operators compare the elements of two `tuple` objects in a pairwise fashion as above." + "The relational operators work in the *same* way as for `list` objects." ] }, { "cell_type": "code", - "execution_count": 166, + "execution_count": 187, "metadata": { "slideshow": { - "slide_type": "slide" + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(7, 11, 8, 5, 3, 12, 2, 6, 9, 10, 1, 4)" + ] + }, + "execution_count": 187, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "numbers" + ] + }, + { + "cell_type": "code", + "execution_count": 188, + "metadata": { + "slideshow": { + "slide_type": "skip" } }, "outputs": [ @@ -4776,13 +5357,85 @@ "True" ] }, - "execution_count": 166, + "execution_count": 188, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "numbers < new_numbers" + "numbers == (7, 11, 8, 5, 3, 12, 2, 6, 9, 10, 1, 4)" + ] + }, + { + "cell_type": "code", + "execution_count": 189, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 189, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "numbers != (99, 11, 8, 5, 3, 12, 2, 6, 9, 10, 1, 4)" + ] + }, + { + "cell_type": "code", + "execution_count": 190, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 190, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "numbers < (99, 11, 8, 5, 3, 12, 2, 6, 9, 10, 1, 4)" + ] + }, + { + "cell_type": "code", + "execution_count": 191, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 191, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "(0, 11, 8, 5, 3, 12, 2, 6, 9, 10, 1, 4) < numbers" ] }, { @@ -4795,15 +5448,15 @@ "source": [ "While `tuple` objects are immutable, this only relates to the references they hold. If a `tuple` object contains mutable objects, the entire nested structure is *not* immutable as a whole.\n", "\n", - "Consider the following stylized example `not_immutable`: It contains *three* elements, `1`, `[2, ..., 11]`, and `12`, and the elements of the nested `list` object may be changed. While it is not practical to mix data types in a `tuple` object that is used as an \"immutable list,\" we want to make the point that the mere usage of the `tuple` type does *not* guarantee a nested object to be immutable." + "Consider the following stylized example `not_immutable`: It contains *three* elements, `1`, `[2, ..., 11]`, and `12`, and the elements of the nested `list` object may be changed. While it is not practical to mix data types in a `tuple` object that is used as an \"immutable list,\" we want to make the point that the mere usage of the `tuple` type does *not* guarantee a nested object to be immutable as a whole." ] }, { "cell_type": "code", - "execution_count": 167, + "execution_count": 192, "metadata": { "slideshow": { - "slide_type": "skip" + "slide_type": "slide" } }, "outputs": [], @@ -4813,10 +5466,34 @@ }, { "cell_type": "code", - "execution_count": 168, + "execution_count": 193, "metadata": { "slideshow": { - "slide_type": "skip" + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(1, [2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 12)" + ] + }, + "execution_count": 193, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "not_immutable" + ] + }, + { + "cell_type": "code", + "execution_count": 194, + "metadata": { + "slideshow": { + "slide_type": "fragment" } }, "outputs": [], @@ -4826,10 +5503,10 @@ }, { "cell_type": "code", - "execution_count": 169, + "execution_count": 195, "metadata": { "slideshow": { - "slide_type": "skip" + "slide_type": "-" } }, "outputs": [ @@ -4839,7 +5516,7 @@ "(1, [99, 99, 99], 12)" ] }, - "execution_count": 169, + "execution_count": 195, "metadata": {}, "output_type": "execute_result" } @@ -4874,7 +5551,7 @@ }, { "cell_type": "code", - "execution_count": 170, + "execution_count": 196, "metadata": { "slideshow": { "slide_type": "slide" @@ -4887,7 +5564,7 @@ }, { "cell_type": "code", - "execution_count": 171, + "execution_count": 197, "metadata": { "slideshow": { "slide_type": "fragment" @@ -4900,7 +5577,7 @@ "7" ] }, - "execution_count": 171, + "execution_count": 197, "metadata": {}, "output_type": "execute_result" } @@ -4911,7 +5588,7 @@ }, { "cell_type": "code", - "execution_count": 172, + "execution_count": 198, "metadata": { "slideshow": { "slide_type": "-" @@ -4924,7 +5601,7 @@ "11" ] }, - "execution_count": 172, + "execution_count": 198, "metadata": {}, "output_type": "execute_result" } @@ -4935,7 +5612,7 @@ }, { "cell_type": "code", - "execution_count": 173, + "execution_count": 199, "metadata": { "slideshow": { "slide_type": "-" @@ -4948,7 +5625,7 @@ "8" ] }, - "execution_count": 173, + "execution_count": 199, "metadata": {}, "output_type": "execute_result" } @@ -4970,7 +5647,7 @@ }, { "cell_type": "code", - "execution_count": 174, + "execution_count": 200, "metadata": { "slideshow": { "slide_type": "skip" @@ -4984,7 +5661,7 @@ "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mValueError\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[0mn1\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn2\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn3\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn4\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn5\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn6\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn7\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn8\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn9\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn10\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn11\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mnumbers\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mn1\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn2\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn3\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn4\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn5\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn6\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn7\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn8\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn9\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn10\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn11\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mnumbers\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mValueError\u001b[0m: too many values to unpack (expected 11)" ] } @@ -4995,7 +5672,7 @@ }, { "cell_type": "code", - "execution_count": 175, + "execution_count": 201, "metadata": { "slideshow": { "slide_type": "skip" @@ -5009,7 +5686,7 @@ "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mValueError\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[0mn1\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn2\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn3\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn4\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn5\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn6\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn7\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn8\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn9\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn10\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn11\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn12\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn13\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mnumbers\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mn1\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn2\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn3\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn4\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn5\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn6\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn7\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn8\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn9\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn10\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn11\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn12\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn13\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mnumbers\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mValueError\u001b[0m: not enough values to unpack (expected 13, got 12)" ] } @@ -5033,7 +5710,7 @@ }, { "cell_type": "code", - "execution_count": 176, + "execution_count": 202, "metadata": { "slideshow": { "slide_type": "slide" @@ -5046,7 +5723,7 @@ }, { "cell_type": "code", - "execution_count": 177, + "execution_count": 203, "metadata": { "slideshow": { "slide_type": "-" @@ -5059,7 +5736,7 @@ "7" ] }, - "execution_count": 177, + "execution_count": 203, "metadata": {}, "output_type": "execute_result" } @@ -5070,7 +5747,7 @@ }, { "cell_type": "code", - "execution_count": 178, + "execution_count": 204, "metadata": { "slideshow": { "slide_type": "-" @@ -5083,7 +5760,7 @@ "[11, 8, 5, 3, 12, 2, 6, 9, 10, 1]" ] }, - "execution_count": 178, + "execution_count": 204, "metadata": {}, "output_type": "execute_result" } @@ -5094,7 +5771,7 @@ }, { "cell_type": "code", - "execution_count": 179, + "execution_count": 205, "metadata": { "slideshow": { "slide_type": "-" @@ -5107,7 +5784,7 @@ "4" ] }, - "execution_count": 179, + "execution_count": 205, "metadata": {}, "output_type": "execute_result" } @@ -5129,7 +5806,7 @@ }, { "cell_type": "code", - "execution_count": 180, + "execution_count": 206, "metadata": { "slideshow": { "slide_type": "skip" @@ -5142,7 +5819,7 @@ }, { "cell_type": "code", - "execution_count": 181, + "execution_count": 207, "metadata": { "slideshow": { "slide_type": "skip" @@ -5155,7 +5832,7 @@ "7" ] }, - "execution_count": 181, + "execution_count": 207, "metadata": {}, "output_type": "execute_result" } @@ -5166,7 +5843,7 @@ }, { "cell_type": "code", - "execution_count": 182, + "execution_count": 208, "metadata": { "slideshow": { "slide_type": "skip" @@ -5179,7 +5856,7 @@ "4" ] }, - "execution_count": 182, + "execution_count": 208, "metadata": {}, "output_type": "execute_result" } @@ -5203,7 +5880,7 @@ }, { "cell_type": "code", - "execution_count": 183, + "execution_count": 209, "metadata": { "slideshow": { "slide_type": "slide" @@ -5216,7 +5893,7 @@ }, { "cell_type": "code", - "execution_count": 184, + "execution_count": 210, "metadata": { "slideshow": { "slide_type": "-" @@ -5227,11 +5904,9 @@ "name": "stdout", "output_type": "stream", "text": [ - "Karl is a goalkeeper\n", - "Achim is a defender\n", - "Xavier is a midfielder\n", - "Oliver is a striker\n", - "Eckardt is a coach\n" + "Berthold is a goalkeeper\n", + "Oliver is a defender\n", + "Carl is a midfielder\n" ] } ], @@ -5253,7 +5928,7 @@ }, { "cell_type": "code", - "execution_count": 185, + "execution_count": 211, "metadata": { "slideshow": { "slide_type": "fragment" @@ -5264,11 +5939,9 @@ "name": "stdout", "output_type": "stream", "text": [ - " ('Karl', 'goalkeeper')\n", - " ('Achim', 'defender')\n", - " ('Xavier', 'midfielder')\n", - " ('Oliver', 'striker')\n", - " ('Eckardt', 'coach')\n" + " ('Berthold', 'goalkeeper')\n", + " ('Oliver', 'defender')\n", + " ('Carl', 'midfielder')\n" ] } ], @@ -5290,7 +5963,7 @@ }, { "cell_type": "code", - "execution_count": 186, + "execution_count": 212, "metadata": { "slideshow": { "slide_type": "skip" @@ -5301,11 +5974,9 @@ "name": "stdout", "output_type": "stream", "text": [ - "0 -> Karl is a goalkeeper\n", - "1 -> Achim is a defender\n", - "2 -> Xavier is a midfielder\n", - "3 -> Oliver is a striker\n", - "4 -> Eckardt is a coach\n" + "0 -> Berthold is a goalkeeper\n", + "1 -> Oliver is a defender\n", + "2 -> Carl is a midfielder\n" ] } ], @@ -5340,7 +6011,7 @@ }, { "cell_type": "code", - "execution_count": 187, + "execution_count": 213, "metadata": { "slideshow": { "slide_type": "slide" @@ -5365,7 +6036,7 @@ }, { "cell_type": "code", - "execution_count": 188, + "execution_count": 214, "metadata": { "slideshow": { "slide_type": "fragment" @@ -5380,7 +6051,7 @@ }, { "cell_type": "code", - "execution_count": 189, + "execution_count": 215, "metadata": { "slideshow": { "slide_type": "-" @@ -5393,7 +6064,7 @@ "(1, 0)" ] }, - "execution_count": 189, + "execution_count": 215, "metadata": {}, "output_type": "execute_result" } @@ -5415,7 +6086,7 @@ }, { "cell_type": "code", - "execution_count": 190, + "execution_count": 216, "metadata": { "slideshow": { "slide_type": "slide" @@ -5429,7 +6100,7 @@ }, { "cell_type": "code", - "execution_count": 191, + "execution_count": 217, "metadata": { "slideshow": { "slide_type": "-" @@ -5442,7 +6113,7 @@ }, { "cell_type": "code", - "execution_count": 192, + "execution_count": 218, "metadata": { "slideshow": { "slide_type": "-" @@ -5455,7 +6126,7 @@ "(1, 0)" ] }, - "execution_count": 192, + "execution_count": 218, "metadata": {}, "output_type": "execute_result" } @@ -5488,7 +6159,7 @@ }, { "cell_type": "code", - "execution_count": 193, + "execution_count": 219, "metadata": { "slideshow": { "slide_type": "skip" @@ -5501,7 +6172,7 @@ }, { "cell_type": "code", - "execution_count": 194, + "execution_count": 220, "metadata": { "slideshow": { "slide_type": "skip" @@ -5519,12 +6190,17 @@ " ith_fibonacci (int)\n", "\n", " Raises:\n", - " TypeError: if i is not an integer\n", + " TypeError: if i is not an integer or not integer-like\n", " ValueError: if i is not positive\n", " \"\"\"\n", " if not isinstance(i, numbers.Integral):\n", - " raise TypeError(\"i must be an integer\")\n", - " elif i < 0:\n", + " if isinstance(i, numbers.Real):\n", + " if i != int(i):\n", + " raise TypeError(\"i is not an integer-like value; it has decimals\")\n", + " i = int(i)\n", + " else:\n", + " raise TypeError(\"i must be an integer\")\n", + " if i < 0:\n", " raise ValueError(\"i must be non-negative\")\n", "\n", " a, b = 0, 1\n", @@ -5537,7 +6213,7 @@ }, { "cell_type": "code", - "execution_count": 195, + "execution_count": 221, "metadata": { "slideshow": { "slide_type": "skip" @@ -5550,7 +6226,7 @@ "144" ] }, - "execution_count": 195, + "execution_count": 221, "metadata": {}, "output_type": "execute_result" } @@ -5559,6 +6235,67 @@ "fibonacci(12)" ] }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Because of the *goose typing*, we may pass `float` objects to `fibonacci()` as long as they contain no decimals." + ] + }, + { + "cell_type": "code", + "execution_count": 222, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "144" + ] + }, + "execution_count": 222, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fibonacci(12.0)" + ] + }, + { + "cell_type": "code", + "execution_count": 223, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "ename": "TypeError", + "evalue": "i is not an integer-like value; it has decimals", + "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[0mfibonacci\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m12.3\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\u001b[0m in \u001b[0;36mfibonacci\u001b[0;34m(i)\u001b[0m\n\u001b[1;32m 15\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0misinstance\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mi\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mnumbers\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mReal\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 16\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mi\u001b[0m \u001b[0;34m!=\u001b[0m \u001b[0mint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mi\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---> 17\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mTypeError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"i is not an integer-like value; it has decimals\"\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[0mi\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mi\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 19\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mTypeError\u001b[0m: i is not an integer-like value; it has decimals" + ] + } + ], + "source": [ + "fibonacci(12.3)" + ] + }, { "cell_type": "markdown", "metadata": { @@ -5587,7 +6324,7 @@ }, { "cell_type": "code", - "execution_count": 196, + "execution_count": 224, "metadata": { "slideshow": { "slide_type": "slide" @@ -5620,7 +6357,7 @@ }, { "cell_type": "code", - "execution_count": 197, + "execution_count": 225, "metadata": { "slideshow": { "slide_type": "-" @@ -5633,7 +6370,7 @@ "42" ] }, - "execution_count": 197, + "execution_count": 225, "metadata": {}, "output_type": "execute_result" } @@ -5655,7 +6392,7 @@ }, { "cell_type": "code", - "execution_count": 198, + "execution_count": 226, "metadata": { "slideshow": { "slide_type": "-" @@ -5668,7 +6405,7 @@ "100" ] }, - "execution_count": 198, + "execution_count": 226, "metadata": {}, "output_type": "execute_result" } @@ -5690,7 +6427,7 @@ }, { "cell_type": "code", - "execution_count": 199, + "execution_count": 227, "metadata": { "slideshow": { "slide_type": "slide" @@ -5704,8 +6441,8 @@ "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mIndexError\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[0mproduct\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;32m\u001b[0m in \u001b[0;36mproduct\u001b[0;34m(*args)\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mproduct\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[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[0;34m\"\"\"Multiply all arguments.\"\"\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 3\u001b[0;31m \u001b[0mresult\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0margs\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\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 4\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0marg\u001b[0m \u001b[0;32min\u001b[0m \u001b[0margs\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m1\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[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mproduct\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;32m\u001b[0m in \u001b[0;36mproduct\u001b[0;34m(*args)\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mproduct\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[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[0;34m\"\"\"Multiply all arguments.\"\"\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 3\u001b[0;31m \u001b[0mresult\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0margs\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\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 4\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0marg\u001b[0m \u001b[0;32min\u001b[0m \u001b[0margs\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m1\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[0;31mIndexError\u001b[0m: tuple index out of range" ] } @@ -5727,7 +6464,7 @@ }, { "cell_type": "code", - "execution_count": 200, + "execution_count": 228, "metadata": { "slideshow": { "slide_type": "slide" @@ -5740,7 +6477,7 @@ }, { "cell_type": "code", - "execution_count": 201, + "execution_count": 229, "metadata": { "slideshow": { "slide_type": "-" @@ -5753,7 +6490,7 @@ "[2, 5, 10]" ] }, - "execution_count": 201, + "execution_count": 229, "metadata": {}, "output_type": "execute_result" } @@ -5775,7 +6512,7 @@ }, { "cell_type": "code", - "execution_count": 202, + "execution_count": 230, "metadata": { "slideshow": { "slide_type": "fragment" @@ -5788,7 +6525,7 @@ "100" ] }, - "execution_count": 202, + "execution_count": 230, "metadata": {}, "output_type": "execute_result" } @@ -5810,7 +6547,7 @@ }, { "cell_type": "code", - "execution_count": 203, + "execution_count": 231, "metadata": { "slideshow": { "slide_type": "fragment" @@ -5823,7 +6560,7 @@ "100" ] }, - "execution_count": 203, + "execution_count": 231, "metadata": {}, "output_type": "execute_result" } @@ -5840,6 +6577,8 @@ } }, "source": [ + "In the \"*Packing & Unpacking with Functions*\" [exercise](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/07_sequences_review_and_exercises.ipynb#Packing-&-Unpacking-with-Functions) at the end of this chapter, we look at `product()` in more detail.\n", + "\n", "While we needed to unpack `one_hundred` above to avoid the semantic error, unpacking an argument in a function call may also be a convenience in general.\n", "\n", "For example, to print the elements of `one_hundred` in one line, we need to use a `for` statement, until now. With unpacking, we get away *without* a loop." @@ -5847,7 +6586,7 @@ }, { "cell_type": "code", - "execution_count": 204, + "execution_count": 232, "metadata": { "slideshow": { "slide_type": "skip" @@ -5868,7 +6607,7 @@ }, { "cell_type": "code", - "execution_count": 205, + "execution_count": 233, "metadata": { "slideshow": { "slide_type": "skip" @@ -5890,7 +6629,7 @@ }, { "cell_type": "code", - "execution_count": 206, + "execution_count": 234, "metadata": { "slideshow": { "slide_type": "skip" @@ -5906,7 +6645,7 @@ } ], "source": [ - "print(*one_hundred)" + "print(*one_hundred) # replaces the for-loop" ] }, { @@ -5917,7 +6656,7 @@ } }, "source": [ - "## The `namedtuple` Type" + "### The `namedtuple` Type" ] }, { @@ -5939,7 +6678,7 @@ }, { "cell_type": "code", - "execution_count": 207, + "execution_count": 235, "metadata": { "slideshow": { "slide_type": "slide" @@ -5965,7 +6704,7 @@ }, { "cell_type": "code", - "execution_count": 208, + "execution_count": 236, "metadata": { "slideshow": { "slide_type": "fragment" @@ -5989,7 +6728,7 @@ }, { "cell_type": "code", - "execution_count": 209, + "execution_count": 237, "metadata": { "slideshow": { "slide_type": "-" @@ -6013,7 +6752,7 @@ }, { "cell_type": "code", - "execution_count": 210, + "execution_count": 238, "metadata": { "slideshow": { "slide_type": "fragment" @@ -6023,10 +6762,10 @@ { "data": { "text/plain": [ - "140321614839000" + "140157466523464" ] }, - "execution_count": 210, + "execution_count": 238, "metadata": {}, "output_type": "execute_result" } @@ -6037,7 +6776,7 @@ }, { "cell_type": "code", - "execution_count": 211, + "execution_count": 239, "metadata": { "slideshow": { "slide_type": "-" @@ -6050,7 +6789,7 @@ "type" ] }, - "execution_count": 211, + "execution_count": 239, "metadata": {}, "output_type": "execute_result" } @@ -6067,12 +6806,47 @@ } }, "source": [ - "To create a `Point` object, we use the same *literal syntax* as for `current_position` above and prepend it with `Point`." + "The value of `Point` is just itself in a *literal notation*." ] }, { "cell_type": "code", - "execution_count": 212, + "execution_count": 240, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "__main__.Point" + ] + }, + "execution_count": 240, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "Point" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "We write `Point(4, 2)` to create a *new* object of type `Point`." + ] + }, + { + "cell_type": "code", + "execution_count": 241, "metadata": { "slideshow": { "slide_type": "slide" @@ -6096,7 +6870,7 @@ }, { "cell_type": "code", - "execution_count": 213, + "execution_count": 242, "metadata": { "slideshow": { "slide_type": "-" @@ -6109,7 +6883,7 @@ "Point(x=4, y=2)" ] }, - "execution_count": 213, + "execution_count": 242, "metadata": {}, "output_type": "execute_result" } @@ -6131,7 +6905,7 @@ }, { "cell_type": "code", - "execution_count": 214, + "execution_count": 243, "metadata": { "slideshow": { "slide_type": "fragment" @@ -6141,10 +6915,10 @@ { "data": { "text/plain": [ - "140322025282656" + "140157872590424" ] }, - "execution_count": 214, + "execution_count": 243, "metadata": {}, "output_type": "execute_result" } @@ -6155,7 +6929,7 @@ }, { "cell_type": "code", - "execution_count": 215, + "execution_count": 244, "metadata": { "slideshow": { "slide_type": "-" @@ -6168,7 +6942,7 @@ "__main__.Point" ] }, - "execution_count": 215, + "execution_count": 244, "metadata": {}, "output_type": "execute_result" } @@ -6190,7 +6964,7 @@ }, { "cell_type": "code", - "execution_count": 216, + "execution_count": 245, "metadata": { "slideshow": { "slide_type": "slide" @@ -6203,7 +6977,7 @@ "4" ] }, - "execution_count": 216, + "execution_count": 245, "metadata": {}, "output_type": "execute_result" } @@ -6214,7 +6988,7 @@ }, { "cell_type": "code", - "execution_count": 217, + "execution_count": 246, "metadata": { "slideshow": { "slide_type": "-" @@ -6227,7 +7001,7 @@ "2" ] }, - "execution_count": 217, + "execution_count": 246, "metadata": {}, "output_type": "execute_result" } @@ -6249,7 +7023,7 @@ }, { "cell_type": "code", - "execution_count": 218, + "execution_count": 247, "metadata": { "slideshow": { "slide_type": "skip" @@ -6263,7 +7037,7 @@ "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mAttributeError\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[0mcurrent_position\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mz\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mcurrent_position\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mz\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mAttributeError\u001b[0m: 'Point' object has no attribute 'z'" ] } @@ -6287,7 +7061,7 @@ }, { "cell_type": "code", - "execution_count": 219, + "execution_count": 248, "metadata": { "slideshow": { "slide_type": "slide" @@ -6300,7 +7074,7 @@ "True" ] }, - "execution_count": 219, + "execution_count": 248, "metadata": {}, "output_type": "execute_result" } @@ -6311,7 +7085,7 @@ }, { "cell_type": "code", - "execution_count": 220, + "execution_count": 249, "metadata": { "slideshow": { "slide_type": "fragment" @@ -6324,7 +7098,7 @@ "4" ] }, - "execution_count": 220, + "execution_count": 249, "metadata": {}, "output_type": "execute_result" } @@ -6335,7 +7109,7 @@ }, { "cell_type": "code", - "execution_count": 221, + "execution_count": 250, "metadata": { "slideshow": { "slide_type": "-" @@ -6348,7 +7122,7 @@ "2" ] }, - "execution_count": 221, + "execution_count": 250, "metadata": {}, "output_type": "execute_result" } @@ -6359,7 +7133,7 @@ }, { "cell_type": "code", - "execution_count": 222, + "execution_count": 251, "metadata": { "slideshow": { "slide_type": "fragment" @@ -6382,7 +7156,7 @@ }, { "cell_type": "code", - "execution_count": 223, + "execution_count": 252, "metadata": { "slideshow": { "slide_type": "skip" @@ -6429,7 +7203,7 @@ }, { "cell_type": "code", - "execution_count": 224, + "execution_count": 253, "metadata": { "slideshow": { "slide_type": "slide" @@ -6466,7 +7240,7 @@ }, { "cell_type": "code", - "execution_count": 225, + "execution_count": 254, "metadata": { "slideshow": { "slide_type": "slide" @@ -6492,7 +7266,7 @@ }, { "cell_type": "code", - "execution_count": 226, + "execution_count": 255, "metadata": { "slideshow": { "slide_type": "fragment" @@ -6509,7 +7283,7 @@ }, { "cell_type": "code", - "execution_count": 227, + "execution_count": 256, "metadata": { "slideshow": { "slide_type": "-" @@ -6522,7 +7296,7 @@ "[50, 122, 65, 26, 10, 145, 5, 37, 82, 101, 2, 17]" ] }, - "execution_count": 227, + "execution_count": 256, "metadata": {}, "output_type": "execute_result" } @@ -6546,7 +7320,7 @@ }, { "cell_type": "code", - "execution_count": 228, + "execution_count": 257, "metadata": { "slideshow": { "slide_type": "slide" @@ -6570,7 +7344,7 @@ }, { "cell_type": "code", - "execution_count": 229, + "execution_count": 258, "metadata": { "slideshow": { "slide_type": "-" @@ -6580,10 +7354,10 @@ { "data": { "text/plain": [ - "" + "" ] }, - "execution_count": 229, + "execution_count": 258, "metadata": {}, "output_type": "execute_result" } @@ -6594,7 +7368,7 @@ }, { "cell_type": "code", - "execution_count": 230, + "execution_count": 259, "metadata": { "slideshow": { "slide_type": "-" @@ -6607,7 +7381,7 @@ "map" ] }, - "execution_count": 230, + "execution_count": 259, "metadata": {}, "output_type": "execute_result" } @@ -6631,7 +7405,7 @@ }, { "cell_type": "code", - "execution_count": 231, + "execution_count": 260, "metadata": { "slideshow": { "slide_type": "fragment" @@ -6644,7 +7418,7 @@ "50" ] }, - "execution_count": 231, + "execution_count": 260, "metadata": {}, "output_type": "execute_result" } @@ -6655,7 +7429,7 @@ }, { "cell_type": "code", - "execution_count": 232, + "execution_count": 261, "metadata": { "slideshow": { "slide_type": "skip" @@ -6668,7 +7442,7 @@ "122" ] }, - "execution_count": 232, + "execution_count": 261, "metadata": {}, "output_type": "execute_result" } @@ -6679,7 +7453,7 @@ }, { "cell_type": "code", - "execution_count": 233, + "execution_count": 262, "metadata": { "slideshow": { "slide_type": "skip" @@ -6692,7 +7466,7 @@ "65" ] }, - "execution_count": 233, + "execution_count": 262, "metadata": {}, "output_type": "execute_result" } @@ -6716,7 +7490,7 @@ }, { "cell_type": "code", - "execution_count": 234, + "execution_count": 263, "metadata": { "slideshow": { "slide_type": "slide" @@ -6729,7 +7503,7 @@ }, { "cell_type": "code", - "execution_count": 235, + "execution_count": 264, "metadata": { "slideshow": { "slide_type": "-" @@ -6742,7 +7516,7 @@ "[50, 122, 65, 26, 10, 145, 5, 37, 82, 101, 2, 17]" ] }, - "execution_count": 235, + "execution_count": 264, "metadata": {}, "output_type": "execute_result" } @@ -6777,7 +7551,7 @@ }, { "cell_type": "code", - "execution_count": 236, + "execution_count": 265, "metadata": { "slideshow": { "slide_type": "slide" @@ -6805,7 +7579,7 @@ }, { "cell_type": "code", - "execution_count": 237, + "execution_count": 266, "metadata": { "slideshow": { "slide_type": "fragment" @@ -6822,7 +7596,7 @@ }, { "cell_type": "code", - "execution_count": 238, + "execution_count": 267, "metadata": { "slideshow": { "slide_type": "-" @@ -6835,7 +7609,7 @@ "[50, 122, 26, 10, 82, 2]" ] }, - "execution_count": 238, + "execution_count": 267, "metadata": {}, "output_type": "execute_result" } @@ -6857,7 +7631,7 @@ }, { "cell_type": "code", - "execution_count": 239, + "execution_count": 268, "metadata": { "slideshow": { "slide_type": "slide" @@ -6870,7 +7644,7 @@ }, { "cell_type": "code", - "execution_count": 240, + "execution_count": 269, "metadata": { "slideshow": { "slide_type": "-" @@ -6880,10 +7654,10 @@ { "data": { "text/plain": [ - "" + "" ] }, - "execution_count": 240, + "execution_count": 269, "metadata": {}, "output_type": "execute_result" } @@ -6894,7 +7668,7 @@ }, { "cell_type": "code", - "execution_count": 241, + "execution_count": 270, "metadata": { "slideshow": { "slide_type": "-" @@ -6907,7 +7681,7 @@ "filter" ] }, - "execution_count": 241, + "execution_count": 270, "metadata": {}, "output_type": "execute_result" } @@ -6929,7 +7703,7 @@ }, { "cell_type": "code", - "execution_count": 242, + "execution_count": 271, "metadata": { "slideshow": { "slide_type": "fragment" @@ -6942,7 +7716,7 @@ "[50, 122, 65, 26, 10, 145, 5, 37, 82, 101, 2, 17]" ] }, - "execution_count": 242, + "execution_count": 271, "metadata": {}, "output_type": "execute_result" } @@ -6953,7 +7727,7 @@ }, { "cell_type": "code", - "execution_count": 243, + "execution_count": 272, "metadata": { "slideshow": { "slide_type": "-" @@ -6966,7 +7740,7 @@ "50" ] }, - "execution_count": 243, + "execution_count": 272, "metadata": {}, "output_type": "execute_result" } @@ -6977,7 +7751,7 @@ }, { "cell_type": "code", - "execution_count": 244, + "execution_count": 273, "metadata": { "slideshow": { "slide_type": "skip" @@ -6990,7 +7764,7 @@ "122" ] }, - "execution_count": 244, + "execution_count": 273, "metadata": {}, "output_type": "execute_result" } @@ -7001,7 +7775,7 @@ }, { "cell_type": "code", - "execution_count": 245, + "execution_count": 274, "metadata": { "slideshow": { "slide_type": "skip" @@ -7014,7 +7788,7 @@ "26" ] }, - "execution_count": 245, + "execution_count": 274, "metadata": {}, "output_type": "execute_result" } @@ -7036,7 +7810,7 @@ }, { "cell_type": "code", - "execution_count": 246, + "execution_count": 275, "metadata": { "slideshow": { "slide_type": "slide" @@ -7049,7 +7823,7 @@ "[50, 122, 26, 10, 82, 2]" ] }, - "execution_count": 246, + "execution_count": 275, "metadata": {}, "output_type": "execute_result" } @@ -7071,7 +7845,7 @@ }, { "cell_type": "code", - "execution_count": 247, + "execution_count": 276, "metadata": { "slideshow": { "slide_type": "fragment" @@ -7084,7 +7858,7 @@ "[50, 122, 26, 10, 82, 2]" ] }, - "execution_count": 247, + "execution_count": 276, "metadata": {}, "output_type": "execute_result" } @@ -7106,7 +7880,7 @@ }, { "cell_type": "code", - "execution_count": 248, + "execution_count": 277, "metadata": { "slideshow": { "slide_type": "fragment" @@ -7119,7 +7893,7 @@ "[65, 145, 5, 37, 101, 17]" ] }, - "execution_count": 248, + "execution_count": 277, "metadata": {}, "output_type": "execute_result" } @@ -7154,7 +7928,7 @@ }, { "cell_type": "code", - "execution_count": 249, + "execution_count": 278, "metadata": { "slideshow": { "slide_type": "slide" @@ -7167,7 +7941,7 @@ "370" ] }, - "execution_count": 249, + "execution_count": 278, "metadata": {}, "output_type": "execute_result" } @@ -7189,7 +7963,7 @@ }, { "cell_type": "code", - "execution_count": 250, + "execution_count": 279, "metadata": { "slideshow": { "slide_type": "fragment" @@ -7202,7 +7976,7 @@ "5" ] }, - "execution_count": 250, + "execution_count": 279, "metadata": {}, "output_type": "execute_result" } @@ -7213,7 +7987,7 @@ }, { "cell_type": "code", - "execution_count": 251, + "execution_count": 280, "metadata": { "slideshow": { "slide_type": "-" @@ -7226,7 +8000,7 @@ "145" ] }, - "execution_count": 251, + "execution_count": 280, "metadata": {}, "output_type": "execute_result" } @@ -7252,7 +8026,7 @@ }, { "cell_type": "code", - "execution_count": 252, + "execution_count": 281, "metadata": { "slideshow": { "slide_type": "slide" @@ -7278,7 +8052,7 @@ }, { "cell_type": "code", - "execution_count": 253, + "execution_count": 282, "metadata": { "slideshow": { "slide_type": "fragment" @@ -7304,7 +8078,7 @@ }, { "cell_type": "code", - "execution_count": 254, + "execution_count": 283, "metadata": { "slideshow": { "slide_type": "fragment" @@ -7340,7 +8114,7 @@ }, { "cell_type": "code", - "execution_count": 255, + "execution_count": 284, "metadata": { "slideshow": { "slide_type": "-" @@ -7353,7 +8127,7 @@ "370" ] }, - "execution_count": 255, + "execution_count": 284, "metadata": {}, "output_type": "execute_result" } @@ -7377,7 +8151,7 @@ }, { "cell_type": "code", - "execution_count": 256, + "execution_count": 285, "metadata": { "slideshow": { "slide_type": "slide" @@ -7390,7 +8164,7 @@ }, { "cell_type": "code", - "execution_count": 257, + "execution_count": 286, "metadata": { "slideshow": { "slide_type": "-" @@ -7403,7 +8177,7 @@ "370" ] }, - "execution_count": 257, + "execution_count": 286, "metadata": {}, "output_type": "execute_result" } @@ -7433,16 +8207,16 @@ "source": [ "[map()](https://docs.python.org/3/library/functions.html#map), [filter()](https://docs.python.org/3/library/functions.html#filter), and [reduce()](https://docs.python.org/3/library/functools.html#functools.reduce) take a `function` object as their first argument, and we defined `transform()`, `is_even()`, and `add()` to be used precisely for that.\n", "\n", - "Often, such functions are used *only once* in a program. However, the primary purpose of functions is to *re-use* them. In such cases, it makes more sense to define them \"anonymously\" right at the position where the first argument goes.\n", + "Often, such functions are used *only once* in a program. However, the primary purpose of functions is to *reuse* them. In such cases, it makes more sense to define them \"anonymously\" right at the position where the first argument goes.\n", "\n", - "As mentioned in [Chapter 2](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/02_functions.ipynb#Anonymous-Functions), Python provides `lambda` expressions to create `function` objects *without* a variable pointing to them.\n", + "As mentioned in [Chapter 2](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/02_functions.ipynb#Anonymous-Functions), Python provides `lambda` expressions to create `function` objects *without* a name referencing them.\n", "\n", "So, the above `add()` function could be rewritten as a `lambda` expression like so ..." ] }, { "cell_type": "code", - "execution_count": 258, + "execution_count": 287, "metadata": { "slideshow": { "slide_type": "slide" @@ -7455,7 +8229,7 @@ "(sum_so_far, next_number)>" ] }, - "execution_count": 258, + "execution_count": 287, "metadata": {}, "output_type": "execute_result" } @@ -7477,7 +8251,7 @@ }, { "cell_type": "code", - "execution_count": 259, + "execution_count": 288, "metadata": { "slideshow": { "slide_type": "fragment" @@ -7490,7 +8264,7 @@ "(x, y)>" ] }, - "execution_count": 259, + "execution_count": 288, "metadata": {}, "output_type": "execute_result" } @@ -7512,7 +8286,7 @@ }, { "cell_type": "code", - "execution_count": 260, + "execution_count": 289, "metadata": { "slideshow": { "slide_type": "slide" @@ -7525,7 +8299,7 @@ "370" ] }, - "execution_count": 260, + "execution_count": 289, "metadata": {}, "output_type": "execute_result" } @@ -7550,7 +8324,7 @@ }, { "cell_type": "code", - "execution_count": 261, + "execution_count": 290, "metadata": { "slideshow": { "slide_type": "fragment" @@ -7563,7 +8337,7 @@ "370" ] }, - "execution_count": 261, + "execution_count": 290, "metadata": {}, "output_type": "execute_result" } @@ -7588,7 +8362,7 @@ }, { "cell_type": "code", - "execution_count": 262, + "execution_count": 291, "metadata": { "slideshow": { "slide_type": "fragment" @@ -7601,7 +8375,7 @@ "370" ] }, - "execution_count": 262, + "execution_count": 291, "metadata": {}, "output_type": "execute_result" } @@ -7654,7 +8428,7 @@ }, { "cell_type": "code", - "execution_count": 263, + "execution_count": 292, "metadata": { "slideshow": { "slide_type": "slide" @@ -7667,7 +8441,7 @@ }, { "cell_type": "code", - "execution_count": 264, + "execution_count": 293, "metadata": { "slideshow": { "slide_type": "-" @@ -7684,7 +8458,7 @@ }, { "cell_type": "code", - "execution_count": 265, + "execution_count": 294, "metadata": { "slideshow": { "slide_type": "-" @@ -7697,7 +8471,7 @@ "[65, 145, 5, 37, 101, 17]" ] }, - "execution_count": 265, + "execution_count": 294, "metadata": {}, "output_type": "execute_result" } @@ -7719,7 +8493,7 @@ }, { "cell_type": "code", - "execution_count": 266, + "execution_count": 295, "metadata": { "slideshow": { "slide_type": "slide" @@ -7732,7 +8506,7 @@ "[65, 145, 5, 37, 101, 17]" ] }, - "execution_count": 266, + "execution_count": 295, "metadata": {}, "output_type": "execute_result" } @@ -7756,7 +8530,7 @@ }, { "cell_type": "code", - "execution_count": 267, + "execution_count": 296, "metadata": { "slideshow": { "slide_type": "fragment" @@ -7769,7 +8543,7 @@ "370" ] }, - "execution_count": 267, + "execution_count": 296, "metadata": {}, "output_type": "execute_result" } @@ -7804,7 +8578,7 @@ }, { "cell_type": "code", - "execution_count": 268, + "execution_count": 297, "metadata": { "slideshow": { "slide_type": "skip" @@ -7817,7 +8591,7 @@ }, { "cell_type": "code", - "execution_count": 269, + "execution_count": 298, "metadata": { "slideshow": { "slide_type": "skip" @@ -7830,7 +8604,7 @@ "[[1, 2, 3, 4, 5, 6, 7], [2, 3, 4, 5, 6, 7, 8], [3, 4, 5, 6, 7, 8, 9]]" ] }, - "execution_count": 269, + "execution_count": 298, "metadata": {}, "output_type": "execute_result" } @@ -7854,7 +8628,7 @@ }, { "cell_type": "code", - "execution_count": 270, + "execution_count": 299, "metadata": { "slideshow": { "slide_type": "skip" @@ -7871,7 +8645,7 @@ }, { "cell_type": "code", - "execution_count": 271, + "execution_count": 300, "metadata": { "slideshow": { "slide_type": "skip" @@ -7884,7 +8658,7 @@ "[1, 2, 3, 4, 5, 6, 7, 2, 3, 4, 5, 6, 7, 8, 3, 4, 5, 6, 7, 8, 9]" ] }, - "execution_count": 271, + "execution_count": 300, "metadata": {}, "output_type": "execute_result" } @@ -7906,7 +8680,7 @@ }, { "cell_type": "code", - "execution_count": 272, + "execution_count": 301, "metadata": { "slideshow": { "slide_type": "skip" @@ -7919,7 +8693,7 @@ "[1, 2, 3, 4, 5, 6, 7, 2, 3, 4, 5, 6, 7, 8, 3, 4, 5, 6, 7, 8, 9]" ] }, - "execution_count": 272, + "execution_count": 301, "metadata": {}, "output_type": "execute_result" } @@ -7943,7 +8717,7 @@ }, { "cell_type": "code", - "execution_count": 273, + "execution_count": 302, "metadata": { "slideshow": { "slide_type": "skip" @@ -7956,7 +8730,7 @@ "105" ] }, - "execution_count": 273, + "execution_count": 302, "metadata": {}, "output_type": "execute_result" } @@ -7978,7 +8752,7 @@ }, { "cell_type": "code", - "execution_count": 274, + "execution_count": 303, "metadata": { "slideshow": { "slide_type": "skip" @@ -7991,7 +8765,7 @@ "105" ] }, - "execution_count": 274, + "execution_count": 303, "metadata": {}, "output_type": "execute_result" } @@ -8034,7 +8808,7 @@ }, { "cell_type": "code", - "execution_count": 275, + "execution_count": 304, "metadata": { "slideshow": { "slide_type": "skip" @@ -8059,7 +8833,7 @@ }, { "cell_type": "code", - "execution_count": 276, + "execution_count": 305, "metadata": { "slideshow": { "slide_type": "skip" @@ -8072,7 +8846,7 @@ "[1.25, 1.2, 1.1666666666666667, 1.5, 1.4, 1.3333333333333333, 1.75, 1.6, 1.5]" ] }, - "execution_count": 276, + "execution_count": 305, "metadata": {}, "output_type": "execute_result" } @@ -8101,7 +8875,7 @@ }, { "cell_type": "code", - "execution_count": 277, + "execution_count": 306, "metadata": { "slideshow": { "slide_type": "skip" @@ -8114,7 +8888,7 @@ "[1.25, 1.2, 1.1666666666666667, 1.5, 1.4, 1.3333333333333333, 1.75, 1.6, 1.5]" ] }, - "execution_count": 277, + "execution_count": 306, "metadata": {}, "output_type": "execute_result" } @@ -8136,7 +8910,7 @@ }, { "cell_type": "code", - "execution_count": 278, + "execution_count": 307, "metadata": { "slideshow": { "slide_type": "skip" @@ -8149,7 +8923,7 @@ "[5.0, 3.0, 2.333333333333333, 6.0, 3.5, 2.666666666666667, 7.0, 4.0, 3.0]" ] }, - "execution_count": 278, + "execution_count": 307, "metadata": {}, "output_type": "execute_result" } @@ -8171,7 +8945,7 @@ }, { "cell_type": "code", - "execution_count": 279, + "execution_count": 308, "metadata": { "slideshow": { "slide_type": "skip" @@ -8184,7 +8958,7 @@ "20.58" ] }, - "execution_count": 279, + "execution_count": 308, "metadata": {}, "output_type": "execute_result" } @@ -8206,7 +8980,7 @@ }, { "cell_type": "code", - "execution_count": 280, + "execution_count": 309, "metadata": { "slideshow": { "slide_type": "skip" @@ -8219,7 +8993,7 @@ "20.58" ] }, - "execution_count": 280, + "execution_count": 309, "metadata": {}, "output_type": "execute_result" } @@ -8265,7 +9039,7 @@ }, { "cell_type": "code", - "execution_count": 281, + "execution_count": 310, "metadata": { "slideshow": { "slide_type": "slide" @@ -8289,7 +9063,7 @@ }, { "cell_type": "code", - "execution_count": 282, + "execution_count": 311, "metadata": { "slideshow": { "slide_type": "-" @@ -8302,7 +9076,7 @@ "[65, 145, 5, 37, 101, 17]" ] }, - "execution_count": 282, + "execution_count": 311, "metadata": {}, "output_type": "execute_result" } @@ -8324,7 +9098,7 @@ }, { "cell_type": "code", - "execution_count": 283, + "execution_count": 312, "metadata": { "slideshow": { "slide_type": "fragment" @@ -8334,10 +9108,10 @@ { "data": { "text/plain": [ - " at 0x7f9f447618b8>" + " at 0x7f790c3a55e8>" ] }, - "execution_count": 283, + "execution_count": 312, "metadata": {}, "output_type": "execute_result" } @@ -8361,7 +9135,7 @@ }, { "cell_type": "code", - "execution_count": 284, + "execution_count": 313, "metadata": { "slideshow": { "slide_type": "fragment" @@ -8374,7 +9148,7 @@ "[65, 145, 5, 37, 101, 17]" ] }, - "execution_count": 284, + "execution_count": 313, "metadata": {}, "output_type": "execute_result" } @@ -8396,7 +9170,7 @@ }, { "cell_type": "code", - "execution_count": 285, + "execution_count": 314, "metadata": { "slideshow": { "slide_type": "fragment" @@ -8409,7 +9183,7 @@ "[65, 145, 5, 37, 101, 17]" ] }, - "execution_count": 285, + "execution_count": 314, "metadata": {}, "output_type": "execute_result" } @@ -8431,7 +9205,7 @@ }, { "cell_type": "code", - "execution_count": 286, + "execution_count": 315, "metadata": { "slideshow": { "slide_type": "fragment" @@ -8444,7 +9218,7 @@ "370" ] }, - "execution_count": 286, + "execution_count": 315, "metadata": {}, "output_type": "execute_result" } @@ -8466,7 +9240,7 @@ }, { "cell_type": "code", - "execution_count": 287, + "execution_count": 316, "metadata": { "slideshow": { "slide_type": "slide" @@ -8479,7 +9253,7 @@ }, { "cell_type": "code", - "execution_count": 288, + "execution_count": 317, "metadata": { "slideshow": { "slide_type": "-" @@ -8489,10 +9263,10 @@ { "data": { "text/plain": [ - " at 0x7f9f44761d68>" + " at 0x7f790c3a5a20>" ] }, - "execution_count": 288, + "execution_count": 317, "metadata": {}, "output_type": "execute_result" } @@ -8514,7 +9288,7 @@ }, { "cell_type": "code", - "execution_count": 289, + "execution_count": 318, "metadata": { "slideshow": { "slide_type": "-" @@ -8527,7 +9301,7 @@ "generator" ] }, - "execution_count": 289, + "execution_count": 318, "metadata": {}, "output_type": "execute_result" } @@ -8549,7 +9323,7 @@ }, { "cell_type": "code", - "execution_count": 290, + "execution_count": 319, "metadata": { "slideshow": { "slide_type": "fragment" @@ -8562,7 +9336,7 @@ "65" ] }, - "execution_count": 290, + "execution_count": 319, "metadata": {}, "output_type": "execute_result" } @@ -8573,7 +9347,7 @@ }, { "cell_type": "code", - "execution_count": 291, + "execution_count": 320, "metadata": { "slideshow": { "slide_type": "skip" @@ -8586,7 +9360,7 @@ "145" ] }, - "execution_count": 291, + "execution_count": 320, "metadata": {}, "output_type": "execute_result" } @@ -8597,7 +9371,7 @@ }, { "cell_type": "code", - "execution_count": 292, + "execution_count": 321, "metadata": { "slideshow": { "slide_type": "skip" @@ -8610,7 +9384,7 @@ "5" ] }, - "execution_count": 292, + "execution_count": 321, "metadata": {}, "output_type": "execute_result" } @@ -8621,7 +9395,7 @@ }, { "cell_type": "code", - "execution_count": 293, + "execution_count": 322, "metadata": { "slideshow": { "slide_type": "skip" @@ -8634,7 +9408,7 @@ "37" ] }, - "execution_count": 293, + "execution_count": 322, "metadata": {}, "output_type": "execute_result" } @@ -8645,7 +9419,7 @@ }, { "cell_type": "code", - "execution_count": 294, + "execution_count": 323, "metadata": { "slideshow": { "slide_type": "skip" @@ -8658,7 +9432,7 @@ "101" ] }, - "execution_count": 294, + "execution_count": 323, "metadata": {}, "output_type": "execute_result" } @@ -8669,7 +9443,7 @@ }, { "cell_type": "code", - "execution_count": 295, + "execution_count": 324, "metadata": { "slideshow": { "slide_type": "skip" @@ -8682,7 +9456,7 @@ "17" ] }, - "execution_count": 295, + "execution_count": 324, "metadata": {}, "output_type": "execute_result" } @@ -8704,7 +9478,7 @@ }, { "cell_type": "code", - "execution_count": 296, + "execution_count": 325, "metadata": { "slideshow": { "slide_type": "skip" @@ -8718,7 +9492,7 @@ "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mStopIteration\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[0mnext\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mgen\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\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mnext\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mgen\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mStopIteration\u001b[0m: " ] } @@ -8729,7 +9503,7 @@ }, { "cell_type": "code", - "execution_count": 297, + "execution_count": 326, "metadata": { "slideshow": { "slide_type": "skip" @@ -8743,7 +9517,7 @@ "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mStopIteration\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[0mnext\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mgen\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\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mnext\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mgen\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mStopIteration\u001b[0m: " ] } @@ -8765,7 +9539,7 @@ }, { "cell_type": "code", - "execution_count": 298, + "execution_count": 327, "metadata": { "slideshow": { "slide_type": "skip" @@ -8813,7 +9587,7 @@ }, { "cell_type": "code", - "execution_count": 299, + "execution_count": 328, "metadata": { "slideshow": { "slide_type": "skip" @@ -8826,7 +9600,7 @@ }, { "cell_type": "code", - "execution_count": 300, + "execution_count": 329, "metadata": { "slideshow": { "slide_type": "skip" @@ -8839,7 +9613,7 @@ "[[1, 2, 3, 4, 5, 6, 7], [2, 3, 4, 5, 6, 7, 8], [3, 4, 5, 6, 7, 8, 9]]" ] }, - "execution_count": 300, + "execution_count": 329, "metadata": {}, "output_type": "execute_result" } @@ -8861,7 +9635,7 @@ }, { "cell_type": "code", - "execution_count": 301, + "execution_count": 330, "metadata": { "slideshow": { "slide_type": "skip" @@ -8874,7 +9648,7 @@ "105" ] }, - "execution_count": 301, + "execution_count": 330, "metadata": {}, "output_type": "execute_result" } @@ -8896,7 +9670,7 @@ }, { "cell_type": "code", - "execution_count": 302, + "execution_count": 331, "metadata": { "slideshow": { "slide_type": "skip" @@ -8909,7 +9683,7 @@ "105" ] }, - "execution_count": 302, + "execution_count": 331, "metadata": {}, "output_type": "execute_result" } @@ -8931,7 +9705,7 @@ }, { "cell_type": "code", - "execution_count": 303, + "execution_count": 332, "metadata": { "slideshow": { "slide_type": "skip" @@ -8944,7 +9718,7 @@ }, { "cell_type": "code", - "execution_count": 304, + "execution_count": 333, "metadata": { "slideshow": { "slide_type": "skip" @@ -8954,10 +9728,10 @@ { "data": { "text/plain": [ - " at 0x7f9f44761e58>" + " at 0x7f790c3a5b10>" ] }, - "execution_count": 304, + "execution_count": 333, "metadata": {}, "output_type": "execute_result" } @@ -8968,7 +9742,7 @@ }, { "cell_type": "code", - "execution_count": 305, + "execution_count": 334, "metadata": { "slideshow": { "slide_type": "skip" @@ -8981,7 +9755,7 @@ "105" ] }, - "execution_count": 305, + "execution_count": 334, "metadata": {}, "output_type": "execute_result" } @@ -9003,7 +9777,7 @@ }, { "cell_type": "code", - "execution_count": 306, + "execution_count": 335, "metadata": { "slideshow": { "slide_type": "skip" @@ -9016,7 +9790,7 @@ "0" ] }, - "execution_count": 306, + "execution_count": 335, "metadata": {}, "output_type": "execute_result" } @@ -9051,7 +9825,7 @@ }, { "cell_type": "code", - "execution_count": 307, + "execution_count": 336, "metadata": { "slideshow": { "slide_type": "skip" @@ -9078,7 +9852,7 @@ }, { "cell_type": "code", - "execution_count": 308, + "execution_count": 337, "metadata": { "slideshow": { "slide_type": "skip" @@ -9091,7 +9865,7 @@ "20.58" ] }, - "execution_count": 308, + "execution_count": 337, "metadata": {}, "output_type": "execute_result" } @@ -9113,7 +9887,7 @@ }, { "cell_type": "code", - "execution_count": 309, + "execution_count": 338, "metadata": { "slideshow": { "slide_type": "skip" @@ -9126,7 +9900,7 @@ "20.58" ] }, - "execution_count": 309, + "execution_count": 338, "metadata": {}, "output_type": "execute_result" } @@ -9143,7 +9917,7 @@ } }, "source": [ - "In summary, we learn from this example that unpacking generator expressions may be a *bad* idea." + "In summary, we learn from this example that unpacking generator expressions *may* be a *bad* idea." ] }, { @@ -9172,7 +9946,7 @@ }, { "cell_type": "code", - "execution_count": 310, + "execution_count": 339, "metadata": { "slideshow": { "slide_type": "skip" @@ -9185,7 +9959,7 @@ "(65, 145, 5, 37, 101, 17)" ] }, - "execution_count": 310, + "execution_count": 339, "metadata": {}, "output_type": "execute_result" } @@ -9220,7 +9994,7 @@ }, { "cell_type": "code", - "execution_count": 311, + "execution_count": 340, "metadata": { "slideshow": { "slide_type": "slide" @@ -9246,7 +10020,7 @@ }, { "cell_type": "code", - "execution_count": 312, + "execution_count": 341, "metadata": { "slideshow": { "slide_type": "fragment" @@ -9259,7 +10033,7 @@ "False" ] }, - "execution_count": 312, + "execution_count": 341, "metadata": {}, "output_type": "execute_result" } @@ -9270,7 +10044,7 @@ }, { "cell_type": "code", - "execution_count": 313, + "execution_count": 342, "metadata": { "slideshow": { "slide_type": "-" @@ -9283,7 +10057,7 @@ "True" ] }, - "execution_count": 313, + "execution_count": 342, "metadata": {}, "output_type": "execute_result" } @@ -9305,7 +10079,7 @@ }, { "cell_type": "code", - "execution_count": 314, + "execution_count": 343, "metadata": { "slideshow": { "slide_type": "skip" @@ -9318,7 +10092,7 @@ "False" ] }, - "execution_count": 314, + "execution_count": 343, "metadata": {}, "output_type": "execute_result" } @@ -9349,7 +10123,7 @@ }, { "cell_type": "code", - "execution_count": 315, + "execution_count": 344, "metadata": { "slideshow": { "slide_type": "skip" @@ -9367,7 +10141,7 @@ }, { "cell_type": "code", - "execution_count": 316, + "execution_count": 345, "metadata": { "slideshow": { "slide_type": "skip" @@ -9380,7 +10154,7 @@ "False" ] }, - "execution_count": 316, + "execution_count": 345, "metadata": {}, "output_type": "execute_result" } @@ -9391,7 +10165,7 @@ }, { "cell_type": "code", - "execution_count": 317, + "execution_count": 346, "metadata": { "slideshow": { "slide_type": "skip" @@ -9404,7 +10178,7 @@ "True" ] }, - "execution_count": 317, + "execution_count": 346, "metadata": {}, "output_type": "execute_result" } @@ -9428,7 +10202,7 @@ }, { "cell_type": "code", - "execution_count": 318, + "execution_count": 347, "metadata": { "slideshow": { "slide_type": "fragment" @@ -9441,7 +10215,7 @@ "True" ] }, - "execution_count": 318, + "execution_count": 347, "metadata": {}, "output_type": "execute_result" } @@ -9452,7 +10226,7 @@ }, { "cell_type": "code", - "execution_count": 319, + "execution_count": 348, "metadata": { "slideshow": { "slide_type": "-" @@ -9465,7 +10239,7 @@ "False" ] }, - "execution_count": 319, + "execution_count": 348, "metadata": {}, "output_type": "execute_result" } @@ -9487,7 +10261,7 @@ }, { "cell_type": "code", - "execution_count": 320, + "execution_count": 349, "metadata": { "slideshow": { "slide_type": "skip" @@ -9500,7 +10274,7 @@ "True" ] }, - "execution_count": 320, + "execution_count": 349, "metadata": {}, "output_type": "execute_result" } @@ -9531,7 +10305,7 @@ }, { "cell_type": "code", - "execution_count": 321, + "execution_count": 350, "metadata": { "slideshow": { "slide_type": "skip" @@ -9549,7 +10323,7 @@ }, { "cell_type": "code", - "execution_count": 322, + "execution_count": 351, "metadata": { "slideshow": { "slide_type": "skip" @@ -9562,7 +10336,7 @@ "True" ] }, - "execution_count": 322, + "execution_count": 351, "metadata": {}, "output_type": "execute_result" } @@ -9573,7 +10347,7 @@ }, { "cell_type": "code", - "execution_count": 323, + "execution_count": 352, "metadata": { "slideshow": { "slide_type": "skip" @@ -9586,7 +10360,7 @@ "False" ] }, - "execution_count": 323, + "execution_count": 352, "metadata": {}, "output_type": "execute_result" } @@ -9625,7 +10399,7 @@ }, { "cell_type": "code", - "execution_count": 324, + "execution_count": 353, "metadata": { "slideshow": { "slide_type": "skip" @@ -9653,7 +10427,7 @@ }, { "cell_type": "code", - "execution_count": 325, + "execution_count": 354, "metadata": { "slideshow": { "slide_type": "skip" @@ -9666,7 +10440,7 @@ "7.0" ] }, - "execution_count": 325, + "execution_count": 354, "metadata": {}, "output_type": "execute_result" } @@ -9688,7 +10462,7 @@ }, { "cell_type": "code", - "execution_count": 326, + "execution_count": 355, "metadata": { "slideshow": { "slide_type": "skip" @@ -9701,7 +10475,7 @@ "7.0" ] }, - "execution_count": 326, + "execution_count": 355, "metadata": {}, "output_type": "execute_result" } @@ -9723,7 +10497,7 @@ }, { "cell_type": "code", - "execution_count": 327, + "execution_count": 356, "metadata": { "slideshow": { "slide_type": "skip" @@ -9736,7 +10510,7 @@ }, { "cell_type": "code", - "execution_count": 328, + "execution_count": 357, "metadata": { "slideshow": { "slide_type": "skip" @@ -9749,7 +10523,7 @@ }, { "cell_type": "code", - "execution_count": 329, + "execution_count": 358, "metadata": { "slideshow": { "slide_type": "skip" @@ -9762,7 +10536,7 @@ "49.994081434519636" ] }, - "execution_count": 329, + "execution_count": 358, "metadata": {}, "output_type": "execute_result" } @@ -9784,7 +10558,7 @@ }, { "cell_type": "code", - "execution_count": 330, + "execution_count": 359, "metadata": { "slideshow": { "slide_type": "skip" @@ -9798,8 +10572,8 @@ "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[0maverage_evens\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m2\u001b[0m \u001b[0;34m*\u001b[0m \u001b[0mrandom\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrandint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m49\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0;36m1\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0m_\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mrange\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m10_000_000\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;32m\u001b[0m in \u001b[0;36maverage_evens\u001b[0;34m(numbers)\u001b[0m\n\u001b[1;32m 12\u001b[0m total, count = reduce(\n\u001b[1;32m 13\u001b[0m \u001b[0;32mlambda\u001b[0m \u001b[0mx\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0my\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0my\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mx\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0my\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m1\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[0;32m---> 14\u001b[0;31m \u001b[0;34m(\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mn\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mn\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mintegers\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mn\u001b[0m \u001b[0;34m%\u001b[0m \u001b[0;36m2\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;36m0\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 15\u001b[0m )\n\u001b[1;32m 16\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mtotal\u001b[0m \u001b[0;34m/\u001b[0m \u001b[0mcount\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0maverage_evens\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m2\u001b[0m \u001b[0;34m*\u001b[0m \u001b[0mrandom\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrandint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m49\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0;36m1\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0m_\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mrange\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m10_000_000\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;32m\u001b[0m in \u001b[0;36maverage_evens\u001b[0;34m(numbers)\u001b[0m\n\u001b[1;32m 12\u001b[0m total, count = reduce(\n\u001b[1;32m 13\u001b[0m \u001b[0;32mlambda\u001b[0m \u001b[0mx\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0my\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0my\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mx\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0my\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m1\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[0;32m---> 14\u001b[0;31m \u001b[0;34m(\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mn\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mn\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mintegers\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mn\u001b[0m \u001b[0;34m%\u001b[0m \u001b[0;36m2\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;36m0\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 15\u001b[0m )\n\u001b[1;32m 16\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mtotal\u001b[0m \u001b[0;34m/\u001b[0m \u001b[0mcount\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;31mTypeError\u001b[0m: reduce() of empty sequence with no initial value" ] } @@ -9831,12 +10605,12 @@ "\n", "Similarly, we have introduced data types in this chapter that all share the \"behavior\" of modeling some \"rule\" in memory to generate objects \"on the fly:\" They are the `map`, `filter`, and `generator` types. Their main commonality is supporting the built-in [next()](https://docs.python.org/3/library/functions.html#next) function. In computer science terminology, such data types are called **[iterators](https://en.wikipedia.org/wiki/Iterator)**, and the [collections.abc](https://docs.python.org/3/library/collections.abc.html) module formalizes them with the `Iterator` ABC.\n", "\n", - "So, an example of an iterator is `evens_transformed` below, an object of type `map`." + "So, an example of an iterator is `evens_transformed` below, an object of type `generator`." ] }, { "cell_type": "code", - "execution_count": 331, + "execution_count": 360, "metadata": { "slideshow": { "slide_type": "slide" @@ -9849,7 +10623,7 @@ }, { "cell_type": "code", - "execution_count": 332, + "execution_count": 361, "metadata": { "slideshow": { "slide_type": "-" @@ -9857,7 +10631,7 @@ }, "outputs": [], "source": [ - "evens_transformed = map(lambda x: (x ** 2) + 1, filter(lambda x: x % 2 == 0, numbers))" + "evens_transformed = ((x ** 2) + 1 for x in numbers if x % 2 == 0)" ] }, { @@ -9873,7 +10647,7 @@ }, { "cell_type": "code", - "execution_count": 333, + "execution_count": 362, "metadata": { "slideshow": { "slide_type": "fragment" @@ -9886,7 +10660,7 @@ "True" ] }, - "execution_count": 333, + "execution_count": 362, "metadata": {}, "output_type": "execute_result" } @@ -9908,7 +10682,7 @@ }, { "cell_type": "code", - "execution_count": 334, + "execution_count": 363, "metadata": { "slideshow": { "slide_type": "fragment" @@ -9921,7 +10695,7 @@ "True" ] }, - "execution_count": 334, + "execution_count": 363, "metadata": {}, "output_type": "execute_result" } @@ -9949,7 +10723,7 @@ }, { "cell_type": "code", - "execution_count": 335, + "execution_count": 364, "metadata": { "slideshow": { "slide_type": "slide" @@ -9962,7 +10736,7 @@ }, { "cell_type": "code", - "execution_count": 336, + "execution_count": 365, "metadata": { "slideshow": { "slide_type": "-" @@ -9986,7 +10760,7 @@ }, { "cell_type": "code", - "execution_count": 337, + "execution_count": 366, "metadata": { "slideshow": { "slide_type": "fragment" @@ -9999,7 +10773,7 @@ "list_iterator" ] }, - "execution_count": 337, + "execution_count": 366, "metadata": {}, "output_type": "execute_result" } @@ -10023,7 +10797,7 @@ }, { "cell_type": "code", - "execution_count": 338, + "execution_count": 367, "metadata": { "slideshow": { "slide_type": "fragment" @@ -10036,7 +10810,7 @@ "(7, 11, 8)" ] }, - "execution_count": 338, + "execution_count": 367, "metadata": {}, "output_type": "execute_result" } @@ -10058,7 +10832,7 @@ }, { "cell_type": "code", - "execution_count": 339, + "execution_count": 368, "metadata": { "slideshow": { "slide_type": "fragment" @@ -10071,7 +10845,7 @@ "(5, 7)" ] }, - "execution_count": 339, + "execution_count": 368, "metadata": {}, "output_type": "execute_result" } @@ -10093,7 +10867,7 @@ }, { "cell_type": "code", - "execution_count": 340, + "execution_count": 369, "metadata": { "slideshow": { "slide_type": "slide" @@ -10106,7 +10880,7 @@ }, { "cell_type": "code", - "execution_count": 341, + "execution_count": 370, "metadata": { "slideshow": { "slide_type": "-" @@ -10119,7 +10893,7 @@ "(99, 99)" ] }, - "execution_count": 341, + "execution_count": 370, "metadata": {}, "output_type": "execute_result" } @@ -10141,7 +10915,7 @@ }, { "cell_type": "code", - "execution_count": 342, + "execution_count": 371, "metadata": { "slideshow": { "slide_type": "fragment" @@ -10154,7 +10928,7 @@ }, { "cell_type": "code", - "execution_count": 343, + "execution_count": 372, "metadata": { "slideshow": { "slide_type": "-" @@ -10167,7 +10941,7 @@ "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]" ] }, - "execution_count": 343, + "execution_count": 372, "metadata": {}, "output_type": "execute_result" } @@ -10178,7 +10952,7 @@ }, { "cell_type": "code", - "execution_count": 344, + "execution_count": 373, "metadata": { "slideshow": { "slide_type": "fragment" @@ -10191,7 +10965,7 @@ "(6, 3)" ] }, - "execution_count": 344, + "execution_count": 373, "metadata": {}, "output_type": "execute_result" } @@ -10217,7 +10991,7 @@ }, { "cell_type": "code", - "execution_count": 345, + "execution_count": 374, "metadata": { "slideshow": { "slide_type": "slide" @@ -10230,7 +11004,7 @@ }, { "cell_type": "code", - "execution_count": 346, + "execution_count": 375, "metadata": { "slideshow": { "slide_type": "-" @@ -10240,10 +11014,10 @@ { "data": { "text/plain": [ - "" + "" ] }, - "execution_count": 346, + "execution_count": 375, "metadata": {}, "output_type": "execute_result" } @@ -10254,7 +11028,7 @@ }, { "cell_type": "code", - "execution_count": 347, + "execution_count": 376, "metadata": { "slideshow": { "slide_type": "-" @@ -10267,7 +11041,7 @@ "zip" ] }, - "execution_count": 347, + "execution_count": 376, "metadata": {}, "output_type": "execute_result" } @@ -10278,7 +11052,7 @@ }, { "cell_type": "code", - "execution_count": 348, + "execution_count": 377, "metadata": { "slideshow": { "slide_type": "-" @@ -10291,7 +11065,7 @@ "True" ] }, - "execution_count": 348, + "execution_count": 377, "metadata": {}, "output_type": "execute_result" } @@ -10313,7 +11087,7 @@ }, { "cell_type": "code", - "execution_count": 349, + "execution_count": 378, "metadata": { "slideshow": { "slide_type": "slide" @@ -10326,7 +11100,7 @@ }, { "cell_type": "code", - "execution_count": 350, + "execution_count": 379, "metadata": { "slideshow": { "slide_type": "-" @@ -10336,10 +11110,10 @@ { "data": { "text/plain": [ - "" + "" ] }, - "execution_count": 350, + "execution_count": 379, "metadata": {}, "output_type": "execute_result" } @@ -10356,12 +11130,12 @@ } }, "source": [ - "`zipper_iterator` points to the *same* object as `zipper`! That holds for *iterators* in general: Any *iterator* created from an existing *iterator* with [iter()](https://docs.python.org/3/library/functions.html#iter) is the *iterator* itself." + "`zipper_iterator` references the *same* object as `zipper`! That holds for *iterators* in general: Any *iterator* created from an existing *iterator* with [iter()](https://docs.python.org/3/library/functions.html#iter) is the *iterator* itself." ] }, { "cell_type": "code", - "execution_count": 351, + "execution_count": 380, "metadata": { "slideshow": { "slide_type": "-" @@ -10374,7 +11148,7 @@ "True" ] }, - "execution_count": 351, + "execution_count": 380, "metadata": {}, "output_type": "execute_result" } @@ -10393,12 +11167,12 @@ "source": [ "The Python core developers made that design decision so that *iterators* may also be looped over.\n", "\n", - "The `for`-loop below prints out *six* more `tuple` objects derived from the re-ordered `numbers` because the `iterator1` object hidden inside `zipper` already returned the first *six* elements. So, the respective first elements of the `tuple` objects printed range from `7` to `12`. Similarly, as `iterator2` already provided *three* elements from `numbers`, we see the respective second elements in the range from `4` to `9`." + "The `for`-loop below prints out *six* more `tuple` objects derived from the now ordered `numbers` because the `iterator1` object hidden inside `zipper` already returned the first *six* elements. So, the respective first elements of the `tuple` objects printed range from `7` to `12`. Similarly, as `iterator2` already provided *three* elements from `numbers`, we see the respective second elements in the range from `4` to `9`." ] }, { "cell_type": "code", - "execution_count": 352, + "execution_count": 381, "metadata": { "slideshow": { "slide_type": "fragment" @@ -10431,7 +11205,7 @@ }, { "cell_type": "code", - "execution_count": 353, + "execution_count": 382, "metadata": { "slideshow": { "slide_type": "skip" @@ -10456,7 +11230,7 @@ }, { "cell_type": "code", - "execution_count": 354, + "execution_count": 383, "metadata": { "slideshow": { "slide_type": "fragment" @@ -10470,7 +11244,7 @@ "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mStopIteration\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[0mnext\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0miterator1\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\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mnext\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0miterator1\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mStopIteration\u001b[0m: " ] } @@ -10492,7 +11266,7 @@ }, { "cell_type": "code", - "execution_count": 355, + "execution_count": 384, "metadata": { "slideshow": { "slide_type": "skip" @@ -10505,7 +11279,7 @@ "10" ] }, - "execution_count": 355, + "execution_count": 384, "metadata": {}, "output_type": "execute_result" } @@ -10514,6 +11288,17 @@ "next(iterator2)" ] }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Understanding *iterators* and *iterables* is helpful for any data science practitioner that deals with large amounts of data. Even without that, these two terms occur everywhere in Python-related texts and documentation." + ] + }, { "cell_type": "markdown", "metadata": { @@ -10540,7 +11325,7 @@ }, { "cell_type": "code", - "execution_count": 356, + "execution_count": 385, "metadata": { "slideshow": { "slide_type": "slide" @@ -10553,7 +11338,7 @@ }, { "cell_type": "code", - "execution_count": 357, + "execution_count": 386, "metadata": { "slideshow": { "slide_type": "-" @@ -10586,10 +11371,10 @@ }, { "cell_type": "code", - "execution_count": 358, + "execution_count": 387, "metadata": { "slideshow": { - "slide_type": "fragment" + "slide_type": "skip" } }, "outputs": [ @@ -10627,10 +11412,10 @@ }, { "cell_type": "code", - "execution_count": 359, + "execution_count": 388, "metadata": { "slideshow": { - "slide_type": "slide" + "slide_type": "fragment" } }, "outputs": [ @@ -10654,6 +11439,17 @@ " print(element, end=\" \")" ] }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "### sorted() vs. reversed()" + ] + }, { "cell_type": "markdown", "metadata": { @@ -10662,7 +11458,703 @@ } }, "source": [ - "Understanding *iterators* and *iterables* is helpful for any data science practitioner that deals with large amounts of data. Even without that, these two terms occur everywhere in Python-related texts and documentation." + "Now that we know the concept of an *iterator*, let's compare some of the built-ins introduced in this chapter in detail and make sure we understand what is going on in memory. This sub-section is thus a great summary of this chapter as well.\n", + "\n", + "We use two simple examples, `numbers` and `memoryless`, to guide us through the discussion. `numbers` creates *thirteen* objects in memory and `memoryless` only *one* (cf., [PythonTutor](http://www.pythontutor.com/visualize.html#code=numbers%20%3D%20%5B7,%2011,%208,%205,%203,%2012,%202,%206,%209,%2010,%201,%204%5D%0Amemoryless%20%3D%20range%281,%2013%29&cumulative=false&curInstr=2&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false))" + ] + }, + { + "cell_type": "code", + "execution_count": 389, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "numbers = [7, 11, 8, 5, 3, 12, 2, 6, 9, 10, 1, 4]" + ] + }, + { + "cell_type": "code", + "execution_count": 390, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [], + "source": [ + "memoryless = range(1, 13)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "The [sorted()](https://docs.python.org/3/library/functions.html#sorted) function takes a *finite* and *iterable* object as its argument and *materializes* its elements into a *new* `list` object that is returned.\n", + "\n", + "The argument may already be materialized, as is the case with `numbers`, but could also be an *iterator* that generates *new* objects, such as `memoryless`. In both cases, we end up with materialized `list` objects with the elements sorted in *forward* order (cf., [PythonTutor](http://www.pythontutor.com/visualize.html#code=numbers%20%3D%20%5B7,%2011,%208,%205,%203,%2012,%202,%206,%209,%2010,%201,%204%5D%0Amemoryless%20%3D%20range%281,%2013%29%0Aresult1%20%3D%20sorted%28numbers%29%0Aresult2%20%3D%20sorted%28memoryless%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false))." + ] + }, + { + "cell_type": "code", + "execution_count": 391, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]" + ] + }, + "execution_count": 391, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "sorted(numbers)" + ] + }, + { + "cell_type": "code", + "execution_count": 392, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]" + ] + }, + "execution_count": 392, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "sorted(memoryless)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "By adding a keyword-only argument `reverse=True`, the materialized `list` objects are sorted in *reverse* order (cf., [PythonTutor](http://www.pythontutor.com/visualize.html#code=numbers%20%3D%20%5B7,%2011,%208,%205,%203,%2012,%202,%206,%209,%2010,%201,%204%5D%0Amemoryless%20%3D%20range%281,%2013%29%0Aresult1%20%3D%20sorted%28numbers,%20reverse%3DTrue%29%0Aresult2%20%3D%20sorted%28memoryless,%20reverse%3DTrue%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false))." + ] + }, + { + "cell_type": "code", + "execution_count": 393, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "[12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]" + ] + }, + "execution_count": 393, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "sorted(numbers, reverse=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 394, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "[12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]" + ] + }, + "execution_count": 394, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "sorted(memoryless, reverse=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "The order in `numbers` remains *unchanged*, and `memoryless` is still *not* materialized." + ] + }, + { + "cell_type": "code", + "execution_count": 395, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "[7, 11, 8, 5, 3, 12, 2, 6, 9, 10, 1, 4]" + ] + }, + "execution_count": 395, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "numbers" + ] + }, + { + "cell_type": "code", + "execution_count": 396, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "range(1, 13)" + ] + }, + "execution_count": 396, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "memoryless" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "The [reversed()](https://docs.python.org/3/library/functions.html#reversed) built-in takes a *sequence* object as its argument and returns an *iterator*. The argument must be *finite* and *reversible* (i.e., *iterable* in *reverse* order) as otherwise [reversed()](https://docs.python.org/3/library/functions.html#reversed) could neither determine the last element that becomes the first nor loop in a *predictable* backward fashion. [PythonTutor](http://www.pythontutor.com/visualize.html#code=numbers%20%3D%20%5B7,%2011,%208,%205,%203,%2012,%202,%206,%209,%2010,%201,%204%5D%0Amemoryless%20%3D%20range%281,%2013%29%0Aiterator1%20%3D%20reversed%28numbers%29%0Aiterator2%20%3D%20reversed%28memoryless%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false) confirms that [reversed()](https://docs.python.org/3/library/functions.html#reversed) does *not* materialize any elements but only returns an *iterator*.\n", + "\n", + "**Side Note**: Even though `range` objects, like `memoryless` here, do *not* \"contain\" references to other objects, they count as *sequence* types, and as such, they are also *container* types. The `in` operator works with `range` objects because we can always cast the object to be checked as an `int` and check if that lies within the `range` object's *start* and *stop* values, taking a potential *step* value into account (cf., this [blog post](https://treyhunner.com/2018/02/python-range-is-not-an-iterator/) for more details on the [range()](https://docs.python.org/3/library/functions.html#func-range) built-in)." + ] + }, + { + "cell_type": "code", + "execution_count": 397, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 397, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "reversed(numbers)" + ] + }, + { + "cell_type": "code", + "execution_count": 398, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 398, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "reversed(memoryless)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "To materialize the elements, we can pass the returned *iterators* to, for example, the [list()](https://docs.python.org/3/library/functions.html#func-list) or [tuple()](https://docs.python.org/3/library/functions.html#func-tuple) built-ins. That creates *new* `list` and `tuple` objects (cf., [PythonTutor](http://www.pythontutor.com/visualize.html#code=numbers%20%3D%20%5B7,%2011,%208,%205,%203,%2012,%202,%206,%209,%2010,%201,%204%5D%0Amemoryless%20%3D%20range%281,%2013%29%0Aresult1%20%3D%20list%28reversed%28numbers%29%29%0Aresult2%20%3D%20tuple%28reversed%28memoryless%29%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false)).\n", + "\n", + "To reiterate some more new terminology from this chapter, we describe [reversed()](https://docs.python.org/3/library/functions.html#reversed) as *lazy*, whereas [list()](https://docs.python.org/3/library/functions.html#func-list) and [tuple()](https://docs.python.org/3/library/functions.html#func-tuple) are *eager*. The former has no significant side effect in memory, while the latter may require a lot of memory." + ] + }, + { + "cell_type": "code", + "execution_count": 399, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "[4, 1, 10, 9, 6, 2, 12, 3, 5, 8, 11, 7]" + ] + }, + "execution_count": 399, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "list(reversed(numbers))" + ] + }, + { + "cell_type": "code", + "execution_count": 400, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)" + ] + }, + "execution_count": 400, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "tuple(reversed(memoryless))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Of course, we can also loop over the returned *iterators* instead.\n", + "\n", + "That works because *iterators* are always *iterables*; in particular, as the previous \"*The for Statement (revisited)*\" sub-section explains, the `for`-loops below call `iter(reversed(numbers))` and `iter(reversed(memoryless))` behind the scenes. However, the *iterators* returned by [iter()](https://docs.python.org/3/library/functions.html#iter) are the *same* as the `reversed(numbers)` and `reversed(memoryless)` iterators passed in! In summary, the `for`-loops below involve many subtleties that together make Python the expressive language it is." + ] + }, + { + "cell_type": "code", + "execution_count": 401, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "4 1 10 9 6 2 12 3 5 8 11 7 " + ] + } + ], + "source": [ + "for number in reversed(numbers):\n", + " print(number, end=\" \")" + ] + }, + { + "cell_type": "code", + "execution_count": 402, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "12 11 10 9 8 7 6 5 4 3 2 1 " + ] + } + ], + "source": [ + "for element in reversed(memoryless):\n", + " print(element, end=\" \")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "As with [sorted()](https://docs.python.org/3/library/functions.html#sorted), the [reversed()](https://docs.python.org/3/library/functions.html#reversed) built-in does *not* mutate its argument." + ] + }, + { + "cell_type": "code", + "execution_count": 403, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "[7, 11, 8, 5, 3, 12, 2, 6, 9, 10, 1, 4]" + ] + }, + "execution_count": 403, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "numbers" + ] + }, + { + "cell_type": "code", + "execution_count": 404, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "range(1, 13)" + ] + }, + "execution_count": 404, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "memoryless" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "To point out the potentially obvious, we compare the results of *sorting* `numbers` in *reverse* order with *reversing* it: These are *different* concepts!" + ] + }, + { + "cell_type": "code", + "execution_count": 405, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "[7, 11, 8, 5, 3, 12, 2, 6, 9, 10, 1, 4]" + ] + }, + "execution_count": 405, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "numbers" + ] + }, + { + "cell_type": "code", + "execution_count": 406, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "[12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]" + ] + }, + "execution_count": 406, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "sorted(numbers, reverse=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 407, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "[4, 1, 10, 9, 6, 2, 12, 3, 5, 8, 11, 7]" + ] + }, + "execution_count": 407, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "list(reversed(numbers))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Whereas both [sorted()](https://docs.python.org/3/library/functions.html#sorted) and [reversed()](https://docs.python.org/3/library/functions.html#reversed) do *not* mutate their arguments, the *mutable* `list` type comes with two methods, [sort()](https://docs.python.org/3/library/stdtypes.html#list.sort) and `reverse()`, that implement the same logic but mutate an object, like `numbers` below, *in place*. To indicate that all changes occur *in place*, the [sort()](https://docs.python.org/3/library/stdtypes.html#list.sort) and `reverse()` methods always return `None`, which is not shown." + ] + }, + { + "cell_type": "code", + "execution_count": 408, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "[7, 11, 8, 5, 3, 12, 2, 6, 9, 10, 1, 4]" + ] + }, + "execution_count": 408, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "numbers" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "The `reverse()` method is *eager*, as opposed to the *lazy* [reversed()](https://docs.python.org/3/library/functions.html#reversed) built-in. That means the mutations causes by the `reverse()` method are written into memory right away." + ] + }, + { + "cell_type": "code", + "execution_count": 409, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [], + "source": [ + "numbers.reverse()" + ] + }, + { + "cell_type": "code", + "execution_count": 410, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "[4, 1, 10, 9, 6, 2, 12, 3, 5, 8, 11, 7]" + ] + }, + "execution_count": 410, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "numbers" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "*Sorting* `numbers` in *reverse* order below is of course still *different* from simply *reversing* it above." + ] + }, + { + "cell_type": "code", + "execution_count": 411, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "numbers.sort(reverse=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 412, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "[12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]" + ] + }, + "execution_count": 412, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "numbers" + ] + }, + { + "cell_type": "code", + "execution_count": 413, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [], + "source": [ + "numbers.sort()" + ] + }, + { + "cell_type": "code", + "execution_count": 414, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]" + ] + }, + "execution_count": 414, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "numbers" ] }, { @@ -10684,13 +12176,27 @@ } }, "source": [ - "**Sequences** are an abstract idea that summarizes *four* behaviors an object may or may not exhibit: We describe them as **finite** and **ordered** **containers** that we may **loop over**. Examples are objects of type `list`, `tuple`, but also `str`. Objects that exhibit all behaviors *except* being ordered are referred to as **collections**. The objects inside a sequence are labeled with a unique *index*, an `int` object in the range $0 \\leq \\text{index} < \\lvert \\text{sequence} \\rvert$, and called its **elements**.\n", + "**Sequences** are an *abstract* concept that summarizes *four* behaviors an object may or may not exhibit. Sequences are\n", + "- **finite** and\n", + "- **ordered**\n", + "- **containers** that we may\n", + "- **loop over**.\n", "\n", - "`list` objects are **mutable**. That means we can change the references to other objects it contains, and, in particular, re-assign them. On the contrary, `tuple` objects are like **immutable** lists: We can use them in place of any `list` object as long as we do *not* mutate it.\n", + "Examples are the `list`, `tuple`, but also the `str` types.\n", "\n", - "Often, the work we do with sequences follows the **map-filter-reduce paradigm**: We apply the same transformation to all elements, filter some of them out, and calculate summary statistics from the remaining ones.\n", + "Objects that exhibit all behaviors *except* being ordered are referred to as **collections**.\n", "\n", - "An essential idea in this chapter is that, in many situations, we need *not* work with all the data **materialized** in memory. Instead, **iterators** allow us to process sequential data on a one-by-one basis." + "The objects inside a sequence are called its **elements** and may be labeled with a unique **index**, an `int` object in the range $0 \\leq \\text{index} < \\lvert \\text{sequence} \\rvert$.\n", + "\n", + "`list` objects are **mutable**. That means we can change the references to the other objects it contains, and, in particular, re-assign them.\n", + "\n", + "On the contrary, `tuple` objects are like **immutable** lists: We can use them in place of any `list` object as long as we do *not* need to mutate it. Often, `tuple` objects are also used to model **records** of related **fields**.\n", + "\n", + "The tasks we do with sequential data follow the **map-filter-reduce paradigm**: We apply the same transformation to all elements, filter some of them out, and calculate summary statistics from the remaining ones.\n", + "\n", + "An essential idea in this chapter is that, in many situations, we need *not* have all the data **materialized** in memory. Instead, **iterators** allow us to process sequential data on a one-by-one basis.\n", + "\n", + "Examples for iterators are the `map`, `filter`, and `generator` types." ] } ], From c69687f463c3ead4d373545aee87fca281fcae04 Mon Sep 17 00:00:00 2001 From: Alexander Hess Date: Wed, 20 Nov 2019 11:01:19 +0100 Subject: [PATCH 4/7] Add exercise on packing/unpacking with functions --- 07_sequences_review_and_exercises.ipynb | 1810 ++++++++++++++++++++++- stream.py | 51 + 2 files changed, 1840 insertions(+), 21 deletions(-) create mode 100644 stream.py diff --git a/07_sequences_review_and_exercises.ipynb b/07_sequences_review_and_exercises.ipynb index 17e1ffc..68582b2 100644 --- a/07_sequences_review_and_exercises.ipynb +++ b/07_sequences_review_and_exercises.ipynb @@ -19,7 +19,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Read [Chapter 7](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/07_sequences.ipynb) of the book. Then work through the ten review questions." + "Read [Chapter 7](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/07_sequences.ipynb) of the book. Then work through the twenty review questions." ] }, { @@ -54,7 +54,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "**Q2**: Explain the difference between a **mutable** and an **immutable** object in Python with an example!" + "**Q2**: What are **abstract base classes**? How can we make use of the ones from the [collections.abc](https://docs.python.org/3/library/collections.abc.html) module in the [standard library](https://docs.python.org/3/library/index.html)?" ] }, { @@ -68,7 +68,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "**Q3**: What is the difference between a **shallow** and a **deep** copy of an object? How can one of them become a problem?" + "**Q3**: How are the *abstract behaviors* of **reversibility** and **finiteness** essential for *indexing* and *slicing* sequences?" ] }, { @@ -82,7 +82,49 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "**Q4.1**: `tuple` objects have *two* primary usages. First, they can be used in place of `list` objects where **mutability** is *not* required. Second, we use them to model **records**.\n", + "**Q4**: Explain the difference between **mutable** and **immutable** objects in Python with the examples of the `list` and `tuple` types!" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Q5**: What is the difference between a **shallow** and a **deep** copy of an object? How can one of them become a \"problem?\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Q6**: Many **list methods** change `list` objects \"**in place**.\" What do we mean by that?" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Q7.1**: `tuple` objects have *two* primary usages. First, they can be used in place of `list` objects where **mutability** is *not* required. Second, we use them to model data **records**.\n", "\n", "Describe why `tuple` objects are a suitable replacement for `list` objects in general!" ] @@ -98,7 +140,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "**Q4.2**: What do we mean by a **record**? How are `tuple` objects suitable to model records? How can we integrate a **semantic** meaning when working with records?" + "**Q7.2**: What do we mean by a **record**? How are `tuple` objects suitable to model records? How can we integrate a **semantic meaning** when working with records into our code?" ] }, { @@ -112,7 +154,104 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "**Q5**: With the [map()](https://docs.python.org/3/library/functions.html#map) and [filter()](https://docs.python.org/3/library/functions.html#filter) built-ins and the [reduce()](https://docs.python.org/3/library/functools.html#functools.reduce) function from the [functools](https://docs.python.org/3/library/functools.html) module in the [standard library](https://docs.python.org/3/library/index.html), we can replace many tedious `for`-loops and `if` statements. What are some advantages of doing so?" + "**Q8**: How is (iterable) **packing** and **unpacking** useful in the context of **function definitions** and **calls**?" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Q9**: With the [map()](https://docs.python.org/3/library/functions.html#map) and [filter()](https://docs.python.org/3/library/functions.html#filter) built-ins and the [reduce()](https://docs.python.org/3/library/functools.html#functools.reduce) function from the [functools](https://docs.python.org/3/library/functools.html) module in the [standard library](https://docs.python.org/3/library/index.html), we can replace many tedious `for`-loops and `if` statements. What are some advantages of doing so?" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Q10**: Looking at the `lambda` expression inside [reduce()](https://docs.python.org/3/library/functools.html#functools.reduce) below, what \"simple\" [built-in function](https://docs.python.org/3/library/functions.html) is mimicked here?" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```python\n", + "from functools import reduce\n", + "\n", + "numbers = [7, 11, 8, 5, 3, 12, 2, 6, 9, 10, 1, 4]\n", + "\n", + "reduce(lambda x, y: x if x > y else y, numbers)\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Q11**: What is the primary use case of **list comprehensions**? Why do we describe them as **eager**?" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Q12**: **Generator expressions** may replace `list` objects and list comprehensions in many scenarios. When evaluated, they create a **lazy** `generator` object that does *not* **materialize** its elements right away. What do we mean by that? What does it mean for a `generator` object to be **exhausted**?" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Q13**: What does it mean for the **boolean reducers**, the built-in [all()](https://docs.python.org/3/library/functions.html#all) and [any()](https://docs.python.org/3/library/functions.html#any) functions, to follow the **short-circuiting** strategy?" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Q14**: What is an **iterator**? How does it relate to an **iterable**?" ] }, { @@ -140,7 +279,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "**Q6**: `sequence` objects are *not* part of core Python but may be imported from the [standard library](https://docs.python.org/3/library/index.html)." + "**Q15**: `sequence` objects are *not* part of core Python but may be imported from the [standard library](https://docs.python.org/3/library/index.html)." ] }, { @@ -154,7 +293,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "**Q7**: Passing **mutable** objects as arguments to functions is not problematic because functions operate in a **local** scope without affecting the **global** scope." + "**Q16**: The built-in [sort()](https://docs.python.org/3/library/stdtypes.html#list.sort) function takes a *finite* **iterable** as its argument an returns a *new* `list` object. On the contrary, the [sorted()](https://docs.python.org/3/library/functions.html#sorted) method on `list` objects *mutates* them *in place*." ] }, { @@ -168,7 +307,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "**Q8**: The **map-filter-reduce** paradigm is an excellent mental concept to organize one's code with. Then, there is a good chance that a program can be **parallelized** if the data input grows." + "**Q17**: Passing **mutable** objects as arguments to functions is not problematic because functions operate in a **local** scope without affecting the **global** scope." ] }, { @@ -182,7 +321,35 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "**Q9**: `lambda` expressions are useful in the context of the **map-filter-reduce** paradigm, where we often do *not* re-use a `function` object more than once." + "**Q18**: `lambda` expressions are useful in the context of the **map-filter-reduce** paradigm, where we often do *not* re-use a `function` object more than once." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Q19**: Using **generator expressions** in place of **list comprehensions** wherever possible is a good practice as it makes our programs use memory more efficiently." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Q20**: Just as **list comprehensions** create `list` objects, **tuple comprehensions** create `tuple` objects." ] }, { @@ -210,7 +377,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "**Q10.1**: Write a function `nested_sum()` that takes a `list` object as its argument, which contains other `list` objects with numbers, and adds up the numbers! Use `nested_numbers` below to test your function!\n", + "**Q21.1**: Write a function `nested_sum()` that takes a `list` object as its argument, which contains other `list` objects with numbers, and adds up the numbers! Use `nested_numbers` below to test your function!\n", "\n", "Hint: You need at least one `for`-loop." ] @@ -230,7 +397,7 @@ "metadata": {}, "outputs": [], "source": [ - "def nested_sum():\n", + "def nested_sum(list_of_lists):\n", " ..." ] }, @@ -247,9 +414,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "**Q10.2**: Provide a one-line expression to obtain the *same* result as `nested_sum()`!\n", + "**Q21.2**: Provide a one-line expression to obtain the *same* result as `nested_sum()`!\n", "\n", - "Hints: Use a *list comprehension*. You may want to use the built-in [sum()](https://docs.python.org/3/library/functions.html#sum) function several times." + "Hints: Use a *list comprehension*, or maybe even a *generator expression*. You may want to use the built-in [sum()](https://docs.python.org/3/library/functions.html#sum) function several times." ] }, { @@ -257,13 +424,15 @@ "execution_count": null, "metadata": {}, "outputs": [], - "source": [] + "source": [ + "..." + ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "**Q10.3**: Generalize `nested_sum()` into a function `mixed_sum()` that can process a \"mixed\" `list` object, which contains numbers and other `list` objects with numbers! Use `mixed_numbers` below for testing!\n", + "**Q21.3**: Generalize `nested_sum()` into a function `mixed_sum()` that can process a \"mixed\" `list` object, which contains numbers and other `list` objects with numbers! Use `mixed_numbers` below for testing!\n", "\n", "Hints: Use the built-in [isinstance()](https://docs.python.org/3/library/functions.html#isinstance) function to check how an element is to be processed. Get extra credit for adhering to *goose typing*, as explained in [Chapter 5](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/05_numbers.ipynb#Goose-Typing)." ] @@ -283,7 +452,16 @@ "metadata": {}, "outputs": [], "source": [ - "def mixed_sum():\n", + "import collections.abc as abc" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def mixed_sum(list_of_lists):\n", " ..." ] }, @@ -293,14 +471,14 @@ "metadata": {}, "outputs": [], "source": [ - "mixed_sum(nums)" + "mixed_sum(mixed_numbers)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "**Q10.4.1**: Write a function `cum_sum()` that takes a `list` object with numbers as its argument and returns a *new* `list` object with the **cumulative sums** of these numbers! So, `sum_up` below, `[1, 2, 3, 4, 5]`, should return `[1, 3, 6, 10, 15]`.\n", + "**Q21.4.1**: Write a function `cum_sum()` that takes a `list` object with numbers as its argument and returns a *new* `list` object with the **cumulative sums** of these numbers! So, `sum_up` below, `[1, 2, 3, 4, 5]`, should return `[1, 3, 6, 10, 15]`.\n", "\n", "Hint: The idea behind is similar to the [cumulative distribution function](https://en.wikipedia.org/wiki/Cumulative_distribution_function) from statistics." ] @@ -320,7 +498,7 @@ "metadata": {}, "outputs": [], "source": [ - "def cum_sum():\n", + "def cum_sum(numbers):\n", " ..." ] }, @@ -337,7 +515,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "**Q10.4.2**: We should always make sure that our functions also work in corner cases. What happens if your implementation of `cum_sum()` is called with an empty list `[]`? Make sure it handles that case *without* crashing! What would be a good return value in this corner case? Describe everything in the docstring.\n", + "**Q21.4.2**: We should always make sure that our functions also work in corner cases. What happens if your implementation of `cum_sum()` is called with an empty list `[]`? Make sure it handles that case *without* crashing! What would be a good return value in this corner case? Describe everything in the docstring.\n", "\n", "Hint: It is possible to write this without any extra input validation." ] @@ -350,6 +528,1596 @@ "source": [ "cum_sum([])" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Packing & Unpacking with Functions" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In the \"*Function Definitions & Calls*\" section in [Chapter 7](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/07_sequences.ipynb#Function-Definitions-&-Calls), we define the following function `product()`. In this exercise, you will improve it by making it more \"user-friendly.\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def product(*args):\n", + " \"\"\"Multiply all arguments.\"\"\"\n", + " result = args[0]\n", + "\n", + " for arg in args[1:]:\n", + " result *= arg\n", + "\n", + " return result" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The `*` in the function's header line *packs* all *positional* arguments passed to `product()` into one *iterable* called `args`.\n", + "\n", + "**Q22.1**: What is the data type of `args` within the function's body?" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Because of the packing, we may call `product()` with an abitrary number of *positional* arguments: The product of just `42` remains `42`, while `2`, `5`, and `10` multiplied together result in `100`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "product(42)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "product(2, 5, 10)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "However, \"abitrary\" does not mean that we can pass *no* argument. If we do so, we get an `IndexError`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "product()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Q22.2**: What line in the body of `product()` causes this exception? What is the exact problem?" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In [Chapter 7](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/07_sequences.ipynb#Function-Definitions-&-Calls), we also pass a `list` object, like `one_hundred`, to `product()`, and *no* exception is raised." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "one_hundred = [2, 5, 10]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "product(one_hundred)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Q22.3**: What is wrong with that? What *kind* of error (cf., [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb#Formal-vs.-Natural-Languages)) is that conceptually? Describe precisely what happens to the passed in `one_hundred` in every line within `product()`!" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Of course, one solution is to *unpack* `one_hundred` with the `*` symbol. We look at another solution further below." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "product(*one_hundred)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's continue with the issue when calling `product()` *without* any argument.\n", + "\n", + "This revised version of `product()` avoids the `IndexError` from before." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def product(*args):\n", + " \"\"\"Multiply all arguments.\"\"\"\n", + " result = None\n", + "\n", + " for arg in args:\n", + " result *= arg\n", + "\n", + " return result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "product()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Q22.4**: Describe why no error occurs by going over every line in `product()`!" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Unfortunately, the new version cannot process any arguments we pass in any more." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "product(42)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "product(2, 5, 10)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Q22.5**: What line causes troubles now? What is the exact problem?" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Q22.6**: Replace the `None` in `product()` above with something reasonable that does *not* cause exceptions! Ensure that `product(42)` and `product(2, 5, 10)` return a correct result.\n", + "\n", + "Hints: It is ok if `product()` returns a result *different* from the `None` above. Look at the documentation of the built-in [sum()](https://docs.python.org/3/library/functions.html#sum) function for some inspiration." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def product(*args):\n", + " \"\"\"Multiply all arguments.\"\"\"\n", + " result = ...\n", + "\n", + " for arg in args:\n", + " result *= arg\n", + "\n", + " return result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "product(42)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "product(2, 5, 10)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, calling `product()` without any arguments returns what we would best describe as a *default* or *start* value. To be \"philosophical,\" what is the product of *no* numbers? We know that the product of *one* number is just the number itself, but what could be a reasonable result when multiplying *no* numbers? The answer is what you use as the initial value of `result` above, and there is only *one* way to make `product(42)` and `product(2, 5, 10)` work." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "product()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Q22.7**: Rewrite `product()` so that it takes a *keyword-only* argument `start`, defaulting to the above *default* or *start* value, and use `start` internally instead of `result`!\n", + "\n", + "Hint: Remember that a *keyword-only* argument is any parameter specified in a function's header line after the first (and only) `*` (cf., [Chapter 2](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/02_functions.ipynb#Keyword-only-Arguments))." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def product(*args, ...):\n", + " \"\"\"Multiply all arguments.\"\"\"\n", + " ..." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, we can call `product()` with a truly arbitrary number of *positional* arguments." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "product(42)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "product(2, 5, 10)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "product()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Without any *positional* arguments but only the *keyword* argument `start`, for example, `start=0`, we can adjust the answer to the \"philosophical\" problem of multiplying *no* numbers. Because of the *keyword-only* syntax, there is *no* way to pass in a `start` number *without* naming it." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "product(start=0)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We could use `start` to inject a multiplier, for example, to double the outcomes." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "product(42, start=2)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "product(2, 5, 10, start=2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "There is still one issue left: Because of the function's name, a user of `product()` may assume that it is ok to pass a *collection* of numbers, like `one_hundred`, which are then multiplied." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "product(one_hundred)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Q22.8**: What is a **collection**? How is that different from a **sequence**?" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Q22.9**: Rewrite the latest version of `product()` to check if the *only* positional argument is a *collection* type! If so, its elements are multiplied together. Otherwise, the logic remains the same.\n", + "\n", + "Hints: Use the built-in [len()](https://docs.python.org/3/library/functions.html#len) and [isinstance()](https://docs.python.org/3/library/functions.html#isinstance) functions to check if there is only *one* positional argument and if it is a *collection* type. Use the *abstract base class* `Collection` from the [collections.abc](https://docs.python.org/3/library/collections.abc.html) module in the [standard library](https://docs.python.org/3/library/index.html). You may want to *re-assign* `args` inside the body." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import collections.abc as abc" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def product(*args, ...):\n", + " \"\"\"Multiply all arguments.\"\"\"\n", + " ..." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "All *five* code cells below now return correct results. We may unpack `one_hundred` or not." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "product(42)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "product(2, 5, 10)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "product()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "product(one_hundred)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "product(*one_hundred)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Side Note**: Above, we make `product()` work with a single *collection* type argument instead of a *sequence* type to keep it more generic: For example, we can pass in a `set` object, like `{2, 5, 10}` below, and `product()` continues to work correctly. The `set` type is introducted in [Chapter 8](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/08_mappings.ipynb#The-set-Type), and one essential difference to the `list` type is that objects of type `set` have *no* order regarding their elements. So, even though `[2, 5, 10]` and `{2, 5, 10}` look almost the same, the order implied in the literal notation gets lost in memory!" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "product([2, 5, 10]) # the argument is a special collection type, namely a sequence" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "product({2, 5, 10}) # the argument is a collection that is NOT a sequence" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "isinstance({2, 5, 10}, abc.Sequence) # sets are NO sequences" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's continue to improve `product()` and make it more Pythonic. It is always a good idea to mimic the behavior of built-ins when writing our own functions. And, [sum()](https://docs.python.org/3/library/functions.html#sum), for example, raises a `TypeError` if called *without* any arguments. It does *not* return the \"philosophical\" answer to adding *no* numbers, which would be `0`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "sum()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Q22.10**: Adapt the latest version of `product()` to also raise a `TypeError` if called *without* any *positional* arguments!" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def product(*args, ...):\n", + " \"\"\"Multiply all arguments.\"\"\"\n", + " ..." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "product()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, we have an implementation of `product()` that is convenient to use for the caller of our function. In particular, we can pass it a *collection* with or without *unpacking* it.\n", + "\n", + "However, this version of `product()` suffers from one more flaw: We cannot pass it a *stream* of data, as modeled, for example, with an *iterator* object that produces elements on a one-by-one basis.\n", + "\n", + "Let's look at an example. The [*stream.py*](https://github.com/webartifex/intro-to-python/blob/master/stream.py) module in the repository provides a `make_finite_stream()` function. It is a *factory* function creating objects of type `generator` that we use to model *streaming* data." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from stream import make_finite_stream" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "stream = make_finite_stream()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "stream" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "type(stream)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Being a `generator`, `stream` is also an `Iterator` in the abstract sense." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "isinstance(stream, abc.Iterator)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "*Iterators* are good for only *one* thing: Giving us the \"next\" element in a line of many." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "next(stream)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "They themselves have *no* idea of how many elements they produce eventually: The built-in [len()](https://docs.python.org/3/library/functions.html#len) function raises a `TypeError`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "len(stream)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can use the [list()](https://docs.python.org/3/library/functions.html#func-list) built-in to *materialize* the elements. However, in a real-world scenario, these may *not* fit into our machine's memory!" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "list(stream)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To be more realistic, `make_finite_stream()` creates `generator` objects producing a varying number of elements." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "list(make_finite_stream())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "list(make_finite_stream())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "list(make_finite_stream())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's see what happens if we pass an *iterator*, as created by `make_finite_stream()`, instead of a materialized *collection*, like `one_hundred`, to `product()`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "product(make_finite_stream())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Q22.11**: What line causes the `TypeError`? What line is really the problem in the latest implementation of `product()`? Describe what happens on each line in the function's body until the exception is raised!" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Q22.12**: Adapt `product()` one last time to make it work with *iterators* as well!\n", + "\n", + "Hints: This task is as easy as replacing `Collection` with something else. Which of the three behaviors of *collections* do *iterators* also exhibit? You may want to look at the documentations on the built-in [max()](https://docs.python.org/3/library/functions.html#max), [min()](https://docs.python.org/3/library/functions.html#min), and [sum()](https://docs.python.org/3/library/functions.html#sum) functions: What kind of argument do they take?" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def product(*args, ...):\n", + " \"\"\"Multiply all arguments.\"\"\"\n", + " ..." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The final version of `product()` behaves like built-ins in edge cases, ..." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "product()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "... works with the arguments passed either as independent *positional* arguments, *packed* into a single *collection* argument, or *unpacked*, ..." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "product(42)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "product(2, 5, 10)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "product([2, 5, 10])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "product(*[2, 5, 10])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "... and can handle *streaming* data with *indefinite* \"length.\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "product(make_finite_stream())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In real-world projects, the data science practitioner must decide if it is worthwhile to make a function usable in various different forms as we did in this exercise, or if that is over-engineered.\n", + "\n", + "Yet, two lessons are important to take away:\n", + "- It is always a good idea to *mimic* the behavior of *built-ins* when in doubt.\n", + "- Make functions capable of working with *streaming* data." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Removing Outliers in Streaming Data" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's say we are given a `list` object with random integers like `sample` below, and we want to calculate some basic statistics on them." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "sample = [\n", + " 45, 46, 40, 49, 36, 53, 49, 42, 25, 40, 39, 36, 38, 40, 40, 52, 36, 52, 40, 41,\n", + " 35, 29, 48, 43, 42, 30, 29, 33, 55, 33, 38, 50, 39, 56, 52, 28, 37, 56, 45, 37,\n", + " 41, 41, 37, 30, 51, 32, 23, 40, 53, 40, 45, 39, 99, 42, 34, 42, 34, 39, 39, 53,\n", + " 43, 37, 46, 36, 45, 42, 32, 38, 57, 34, 36, 44, 47, 51, 46, 39, 28, 40, 35, 46,\n", + " 41, 51, 41, 23, 46, 40, 40, 51, 50, 32, 47, 36, 38, 29, 32, 53, 34, 43, 39, 41,\n", + " 40, 34, 44, 40, 41, 43, 47, 57, 50, 42, 38, 25, 45, 41, 58, 37, 45, 55, 44, 53,\n", + " 82, 31, 45, 33, 32, 39, 46, 48, 42, 47, 40, 45, 51, 35, 31, 46, 40, 44, 61, 57,\n", + " 40, 36, 35, 55, 40, 56, 36, 35, 86, 36, 51, 40, 54, 50, 49, 36, 41, 37, 48, 41,\n", + " 42, 44, 40, 43, 51, 47, 46, 50, 40, 23, 40, 39, 28, 38, 42, 46, 46, 42, 46, 31,\n", + " 32, 40, 48, 27, 40, 40, 30, 32, 25, 31, 30, 43, 44, 29, 45, 41, 63, 32, 33, 58,\n", + "]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "len(sample)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Q23.1**: `list` objects are **sequences**. What *four* behaviors do they always come with?" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Q23.2**: Write a function `mean()` that calculates the simple arithmetic mean of a given `sequence` with numbers!\n", + "\n", + "Hints: You can solve this task with [built-in functions](https://docs.python.org/3/library/functions.html) only. A `for`-loop is *not* needed." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def mean(sequence):\n", + " ..." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "sample_mean = mean(sample)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "sample_mean" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Q23.3**: Write a function `std()` that calculates the [standard deviation](https://en.wikipedia.org/wiki/Standard_deviation) of a `sequence` of numbers! Integrate your `mean()` version from before and the [sqrt()](https://docs.python.org/3/library/math.html#math.sqrt) function from the [math](https://docs.python.org/3/library/math.html) module in the [standard library](https://docs.python.org/3/library/index.html) provided to you below. Make sure `std()` calls `mean()` only *once* internally! Repeated calls to `mean()` would be a waste of computational resources.\n", + "\n", + "Hints: Parts of the code are probably too long to fit within the suggested 79 characters per line. So, use *temporary variables* inside your function. Instead of a `for`-loop, you may want to use a *list comprehension* or, even better, a memoryless *generator expression*." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from math import sqrt" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def std(sequence):\n", + " ..." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "sample_std = std(sample)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "sample_std" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Q23.4**: Complete `standardize()` below that takes a `sequence` of numbers and returns a `list` object with the **[z-scores](https://en.wikipedia.org/wiki/Standard_score)** of these numbers! A z-score is calculated by subtracting the mean and dividing by the standard deviation. Re-use `mean()` and `std()` from before. Again, ensure that `standardize()` calls `mean()` and `std()` only *once*! Further, round all z-scores with the built-in [round()](https://docs.python.org/3/library/functions.html#round) function and pass on the keyword-only argument `digits` to it.\n", + "\n", + "Hint: You may want to use a *list comprehension* instead of a `for`-loop." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def standardize(sequence, *, digits=3):\n", + " ..." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "z_scores = standardize(sample)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The [pprint()](https://docs.python.org/3/library/pprint.html#pprint.pprint) function from the [pprint](https://docs.python.org/3/library/pprint.html) module in the [standard library](https://docs.python.org/3/library/index.html) allows us to \"pretty print\" long `list` objects compactly." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from pprint import pprint" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pprint(z_scores, compact=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We know that `standardize()` works correctly if the resulting z-scores' mean and standard deviation approach `0` and `1` for a long enough `sequence`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "mean(z_scores), std(z_scores)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Even though `standardize()` calls `mean()` and `std()` only once each, `mean()` is called *twice*! That is so because `std()` internally also re-uses `mean()`!\n", + "\n", + "**Q23.5.1**: Rewrite `std()` to take an optional keyword-only argument `seq_mean`, defaulting to `None`. If provided, `seq_mean` is used instead of the result of calling `mean()`. Otherwise, the latter is called.\n", + "\n", + "Hint: You must check if `seq_mean` is still the default value." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def std(sequence, *, seq_mean=None):\n", + " ..." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`std()` continues to work as before." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "sample_std = std(sample)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "sample_std" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Q23.5.2**: Now, rewrite `standardize()` to pass on the return value of `mean()` to `std()`! In summary, `standardize()` calculates the z-scores for the numbers in the `sequence` with as few computational steps as possible." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def standardize(sequence, *, digits=3):\n", + " ..." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "z_scores = standardize(sample)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "mean(z_scores), std(z_scores)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Q23.6**: With both `sample` and `z_scores` being materialized `list` objects, we can loop over pairs consisting of a number from `sample` and its corresponding z-score. Write a `for`-loop that prints out all the \"outliers,\" as which we define numbers with an absolute z-score above `1.96`. There are *four* of them in the `sample`.\n", + "\n", + "Hint: Use the [abs()](https://docs.python.org/3/library/functions.html#abs) and [zip()](https://docs.python.org/3/library/functions.html#zip) built-ins." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "..." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We provide a `stream` module with a `data` object that models an *infinite* **stream** of data (cf., the [*stream.py*](https://github.com/webartifex/intro-to-python/blob/master/stream.py) file in the repository)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from stream import data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "data" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`data` is of type `generator` and has *no* length." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "type(data)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "len(data)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Being a `generator`, it is an `Iterator` in the abstract sense ..." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import collections.abc as abc" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "isinstance(data, abc.Iterator)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "... and so the only thing we can do with it is to pass it to the built-in [next()](https://docs.python.org/3/library/functions.html#next) function and go over the numbers it streams one by one." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "next(data)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Q23.7**: What happens if you call `mean()` with `data` as the argument? What is the problem?\n", + "\n", + "Hints: If you try it out, you may have to press the \"Stop\" button in the toolbar at the top. Your computer should *not* crash, but you will *have to* restart this Jupyter notebook with \"Kernel\" > \"Restart\" and import `data` again." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "mean(data)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Q23.8**: Write a function `take_sample()` that takes an `iterator` as its argument, like `data`, and creates a *materialized* `list` object out of its first `n` elements, defaulting to `1_000`!\n", + "\n", + "Hints: [next()](https://docs.python.org/3/library/functions.html#next) and the [range()](https://docs.python.org/3/library/functions.html#func-range) built-in may be helpful. You may want to use a *list comprehension* instead of a `for`-loop and write a one-liner. Audacious students may want to look at [isclice()](https://docs.python.org/3/library/itertools.html#itertools.islice) in the [itertools](https://docs.python.org/3/library/itertools.html) module in the [standard library](https://docs.python.org/3/library/index.html)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def take_sample(iterator, *, n=1_000):\n", + " ..." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We take a `new_sample` from the stream of `data`, and its statistics are similar to the initial `sample`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "new_sample = take_sample(data)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "len(new_sample)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "mean(new_sample)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "std(new_sample)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Q23.9**: Convert `standardize()` into a *new* function `standardized()` that implements the *same* logic but works on a possibly *infinite* stream of data, provided as an `iterable`, instead of a *finite* `sequence`.\n", + "\n", + "To calculate a z-score, we need the stream's overall mean and standard deviation, and that is *impossible* to calculate if we do not know how long the stream is, and, in particular, if it is *infinite*. So, `standardized()` first takes a sample from the `iterable` internally, and uses the sample's mean and standard deviation to calculate the z-scores.\n", + "\n", + "Hint: `standardized()` *must* return a `generator` object. So, use a *generator expression* as the return value; unless you know about the `yield` statement already (cf., [reference](https://docs.python.org/3/reference/simple_stmts.html#the-yield-statement))." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def standardized(iterable, *, digits=3):\n", + " ..." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`standardized()` works almost like `standardize()` except that we use it with [next()](https://docs.python.org/3/library/functions.html#next) to obtain the z-scores one by one." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "z_scores = standardized(data)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "z_scores" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "type(z_scores)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "next(z_scores)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Q23.10.1**: `standardized()` allows us to go over an *infinite* stream of z-scores. What we want to do instead is to loop over the stream's raw numbers and skip the outliers. In the remainder of this exercise, you look at the parts that make up the `skip_outliers()` function below to achieve precisely that.\n", + "\n", + "The first steps in `skip_outliers()` are the same as in `standardized()`: We take a `sample` from the stream of `data` and calculate its statistics." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "sample = ...\n", + "seq_mean = ...\n", + "seq_std = ..." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Q23.10.2**: Just as in `standardized()`, write a *generator expression* that produces z-scores one by one! However, instead of just generating a z-score, the resulting `generator` object should produce `tuple` objects consisting of a \"raw\" number from `data` and its z-score.\n", + "\n", + "Hint: Look at the revisited \"*Averaging Even Numbers*\" example in [Chapter 7](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/07_sequences.ipynb#Example:-Averaging-Even-Numbers-%28revisited%29) for some inspiration, which also contains a generator expression producing `tuple` objects." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "standardizer = (... for ... in data)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`standardizer` should produce `tuple` objects." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "next(standardizer)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Q23.10.3**: Write another generator expression that loops over `standardizer`. It contains an `if`-clause that keeps only numbers with an absolute z-score below the `threshold_z`. If you fancy, use *tuple unpacking*." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "threshold_z = 1.96" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "no_outliers = (... for ... in standardizer if ...)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`no_outliers` should produce `int` objects." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "next(no_outliers)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Q23.10.4**: Lastly, put everything together in the `skip_outliers()` function! Make sure you refer to `iterable` inside the function and not the global `data`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def skip_outliers(iterable, *, threshold_z=1.96):\n", + " sample = ...\n", + " seq_mean = ...\n", + " seq_std = ...\n", + " standardizer = ...\n", + " no_outliers = ...\n", + " return no_outliers" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, we can create a `generator` object and loop over the `data` in the stream with outliers skipped. Instead of the default `1.96`, we use a `threshold_z` of only `0.05`: That filters out all numbers except `42`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "skipper = skip_outliers(data, threshold_z=0.05)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "skipper" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "type(skipper)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "next(skipper)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Q23.11**: You implemented the functions `mean()`, `std()`, `standardize()`, `standardized()`, and `skip_outliers()`. Which of them are **eager**, and which are **lazy**? How do these two concepts relate to **finite** and **infinite** data?" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " " + ] } ], "metadata": { diff --git a/stream.py b/stream.py new file mode 100644 index 0000000..6aa6cc7 --- /dev/null +++ b/stream.py @@ -0,0 +1,51 @@ +"""Simulation of random streams of data. + +This module defines: +- a generator object `data` modeling an infinite stream of integers +- a function `make_finite_stream()` that creates finite streams of data + +The probability distribution underlying the integers is Gaussian-like with a +mean of 42 and a standard deviation of 8. The left tail of the distribution is +cut off meaning that the streams only produce non-negative numbers. Further, +one in a hundred random numbers has an increased chance to be an outlier. +""" + +import itertools as _itertools +import random as _random + + +_random.seed(87) + + +def _infinite_stream(): + """Internal generator function to simulate an infinite stream of data.""" + while True: + number = max(0, int(_random.gauss(42, 8))) + if _random.randint(1, 100) == 1: + number *= 2 + yield number + + +def make_finite_stream(min_=5, max_=15): + """Simulate a finite stream of data. + + The returned stream is finite, but the number of elements to be produced + by it is still random. This default behavior may be turned off by passing + in `min_` and `max_` arguments with `min_ == max_`. + + Args: + min_ (optional, int): minimum numbers in the stream; defaults to 5 + max_ (optional, int): maximum numbers in the stream; defaults to 15 + + Returns: + finite_stream (generator) + + Raises: + ValueError: if max_ < min_ + """ + stream = _infinite_stream() + n = _random.randint(min_, max_) + yield from _itertools.islice(stream, n) + + +data = _infinite_stream() From b7ad171473887c7be0b738775d27670a2115ad8d Mon Sep 17 00:00:00 2001 From: Alexander Hess Date: Wed, 20 Nov 2019 11:01:43 +0100 Subject: [PATCH 5/7] Add initial version of notebook 08 --- 08_mappings.ipynb | 8202 +++++++++++++++++++++++++++++++ static/fibonacci_call_graph.png | Bin 0 -> 32706 bytes 2 files changed, 8202 insertions(+) create mode 100644 08_mappings.ipynb create mode 100644 static/fibonacci_call_graph.png diff --git a/08_mappings.ipynb b/08_mappings.ipynb new file mode 100644 index 0000000..d42237b --- /dev/null +++ b/08_mappings.ipynb @@ -0,0 +1,8202 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "# Chapter 8: Mappings & Sets" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "While [Chapter 7](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/07_sequences.ipynb) focuses on one special kind of *collection* types, namely *sequences*, this chapter introduces two more: **Mappings** and **sets**. We present the data types belonging to these two groups in one chapter as they share the *same* underlying implementation at the C Level, known as **[hash tables](https://en.wikipedia.org/wiki/Hash_table)**.\n", + "\n", + "The most important mapping type in this chapter is the `dict` type that we have not yet seen before (cf, [documentation](https://docs.python.org/3/library/stdtypes.html#dict)). It is an essential part in a data science practitioner's toolbox for two reasons: First, Python employs `dict` objects basically \"everywhere\" internally. So, we must understand how they work to become better at Python in general. Second, after the many concepts related to *sequential* data, the ideas behind *mappings* enhance our general problem solving skills. As a concrete example, we look at the concept of **memoization** to complete our picture of *recursion*, as depicted in [Chapter 4](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/04_iteration.ipynb#Recursion). We end this chapter with a discussion of *set* types." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "## The `dict` Type" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "A *mapping* is a one-to-one correspondence from a set of **keys** to a set of **values**. In other words, a *mapping* is a *collection* of **key-value pairs**, also called **items** for short.\n", + "\n", + "In the context of mappings, the term *value* has a meaning different from the general *value* that *every* object has: In the \"bag\" analogy from [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb#Value), we descibe an object's value to be the concrete $0$s and $1$s it contains. Here, the terms *key* and *value* mean the *role* an object takes within a mapping. Both, *keys* and *values*, are real *objects* with a distinct *value*. So, the student should always remember the double meaning of the term *value* in this chapter!\n", + "\n", + "Let's continue with an example. To create a `dict` object, we commonly use the literal notation, `{..: .., ..: .., ...}`, and list all the items. `to_words` below maps the `int` objects `0`, `1`, and `2` to their English word equivalents, `\"zero\"`, `\"one\"`, and `\"two\"`, and `from_words` does the opposite. A stylistic side note: Pythonistas often expand `dict` or `list` definitions by writing each item or element on a line on their own. The commas `,` after the *last* items are *not* a mistake, as well, although they *may* be left out. Besides easier reading, such style has actual technical advantages (cf., [source](https://www.python.org/dev/peps/pep-0008/#when-to-use-trailing-commas)) that we do not go into detail about here." + ] + }, + { + "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": "-" + } + }, + "outputs": [], + "source": [ + "from_words = {\n", + " \"zero\": 0,\n", + " \"one\": 1,\n", + " \"two\": 2,\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "As before, `dict` objects are objects on their own: They have an identity, a type, and a value. The latter is a *literal* that creates a *new* `dict` object with the *same* value when evaluated." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "139752734794304" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "id(from_words)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "dict" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "type(from_words)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{'zero': 0, 'one': 1, 'two': 2}" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from_words" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "The [dict()](https://docs.python.org/3/library/functions.html#func-dict) built-in gives us an alternative way to create a `dict` object. It is versatile and can be used in different ways.\n", + "\n", + "First, we may pass it any *mapping* type, for example, a `dict` object, to obtain a *new* `dict` object. That is the easiest way to convert a more specialized mapping type, such as the `OrderedDict`, `defaultdict`, and `Counter` types introduced further below, into an \"ordinary\" `dict` object." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{'zero': 0, 'one': 1, 'two': 2}" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "dict(from_words)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Second, we may pass it an *iterable* of *iterables* with *two* elements each. So, both of the following two code cells work: A `list` of `tuple` objects, or a `tuple` of `list` objects. More importantly, we could use an *iterator*, for example, a `generator` object, that produces the inner iterables \"on the fly.\"" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{'zero': 0, 'one': 1, 'two': 2}" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "dict([(\"zero\", 0), (\"one\", 1), (\"two\", 2)])" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{'zero': 0, 'one': 1, 'two': 2}" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "dict(([\"zero\", 0], [\"one\", 1], [\"two\", 2]))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Lastly, [dict()](https://docs.python.org/3/library/functions.html#func-dict) may also be called with *keyword* arguments: The keywords become the keys and the arguments the values." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{'zero': 0, 'one': 1, 'two': 2}" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "dict(zero=0, one=1, two=2)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Keyword arguments may always be added in the first two cases as well. That is sometimes useful to take data as is and ensure that certain keys are *existent* and have a pre-defined value." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{'zero': 0, 'one': 1, 'two': 2, 'three': 3}" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "dict(from_words, three=3)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "### Nested Data" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Often, `dict` objects occur in a nested form and combined with other collection types, such as `list` or `tuple` objects, to model more complex \"objects\" from the real world.\n", + "\n", + "The reason for this popularity is that many modern [ReST APIs](https://en.wikipedia.org/wiki/Representational_state_transfer#Applied_to_Web_services) on the internet (e.g., [Google Maps API](https://cloud.google.com/maps-platform/), [Yelp API](https://www.yelp.com/developers/documentation/v3), [Twilio API](https://www.twilio.com/docs/usage/api)) provide their data in the popular [JSON](https://en.wikipedia.org/wiki/JSON) format, which looks almost like a combination of `dict` and `list` objects in Python. \n", + "\n", + "The `people` example below models three groups of people: Mathematicians, physicists, and programmers. Each person may have an arbitrary number of email addresses (e.g., [Leonhard Euler](https://en.wikipedia.org/wiki/Leonhard_Euler) has not lived long enough to get one, whereas [Guido](https://en.wikipedia.org/wiki/Guido_van_Rossum) has more than one for sure).\n", + "\n", + "`people` has many (implicit) structural assumptions built in. For example, there are a [one-to-many](https://en.wikipedia.org/wiki/One-to-many_%28data_model%29) relationship between people and their email addresses and a [one-to-one](https://en.wikipedia.org/wiki/One-to-one_%28data_model%29) relationship between each person and their name. It is important to understand that we determine this structure by choosing the data types involved in `people` and that it is impossible to model nested data without any (implicit) assumption about the structure. So, the data science practitioner should have a basic understanding of [database normalization](https://en.wikipedia.org/wiki/Database_normalization)." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "people = {\n", + " \"mathematicians\": [\n", + " {\n", + " \"name\": \"Gilbert Strang\",\n", + " \"emails\": [\"gilbert@mit.edu\"],\n", + " },\n", + " {\n", + " \"name\": \"Leonhard Euler\",\n", + " \"emails\": [],\n", + " },\n", + " ],\n", + " \"physicists\": [],\n", + " \"programmers\": [\n", + " {\n", + " \"name\": \"Guido\",\n", + " \"emails\": [\"guido@python.org\", \"guido@dropbox.com\"],\n", + " },\n", + " ],\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "The literal notation of such a nested `dict` object may be hard to read." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{'mathematicians': [{'name': 'Gilbert Strang', 'emails': ['gilbert@mit.edu']},\n", + " {'name': 'Leonhard Euler', 'emails': []}],\n", + " 'physicists': [],\n", + " 'programmers': [{'name': 'Guido',\n", + " 'emails': ['guido@python.org', 'guido@dropbox.com']}]}" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "people" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Luckily, the [pprint](https://docs.python.org/3/library/pprint.html) module in the [standard library](https://docs.python.org/3/library/index.html) provides a [pprint()](https://docs.python.org/3/library/pprint.html#pprint.pprint) function for \"pretty printing.\"" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "from pprint import pprint" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'mathematicians': [{'emails': ['gilbert@mit.edu'],\n", + " 'name': 'Gilbert Strang'},\n", + " {'emails': [],\n", + " 'name': 'Leonhard Euler'}],\n", + " 'physicists': [],\n", + " 'programmers': [{'emails': ['guido@python.org',\n", + " 'guido@dropbox.com'],\n", + " 'name': 'Guido'}]}\n" + ] + } + ], + "source": [ + "pprint(people, indent=1, width=60)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "### Hash Tables & Key Hashability" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "In [Chapter 0](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/00_start_up.ipynb#Isn't-C-a-lot-faster?), we argue that a major advantage of using Python is that it takes care of the memory managment for us. In line with that, we have never talked about the C level implementation thus far in the book. However, the `dict` type, among others, exhibits some behaviors that may seem \"weird\" for a beginner. To built a solid intuition that enables the student to better \"predict\" how `dict` objects behave, we describe the underlying implementation details on a conceptual level (i.e., without C code).\n", + "\n", + "The first unintuitive behavior is that we may *not* use a *mutable* object as a key. That results in a `TypeError`." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "ename": "TypeError", + "evalue": "unhashable type: 'list'", + "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[1;32m 1\u001b[0m {\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0;34m\"zero\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"one\"\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[1;32m 3\u001b[0m }\n", + "\u001b[0;31mTypeError\u001b[0m: unhashable type: 'list'" + ] + } + ], + "source": [ + "{\n", + " [0, 1]: [\"zero\", \"one\"],\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Similarly surprising is that items with the *same* key get \"merged\" together. The resulting `dict` object seems to keep the position of the *first* mention of a key, while at the same time only the *last* mention of a value survives." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{'zero': 3, 'one': 1, 'two': 2}" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "{\n", + " \"zero\": 0,\n", + " \"one\": 1,\n", + " \"two\": 2,\n", + " \"zero\": 3,\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "The reason for that is that the main building block behind the `dict` type is a [data structure](https://en.wikipedia.org/wiki/Data_structure) called a [hash table](https://en.wikipedia.org/wiki/Hash_table).\n", + "\n", + "Conceptually, when we create a *new* `dict` object, Python creates a \"bag\" in memory that takes significantly more space (i.e., $0$s and $1$s) than needed to store the references to all the key and value objects. This bag is a **contiguous** chunk of memory (i.e., all the $0$s and $1$s lie right next to each other) and divided into equally sized **buckets** that have just enough space to store *two* references each. These references go to an item's key and value objects. The buckets are labeled with *index* numbers, or \"integer addresses.\" Because Python knows how wide each bucket is and where the bag begins, it can jump directly into *any* bucket by calculating its **offset** from the start. It does not have to follow a reference to some \"random\" memory location once it has followed the reference to the `dict` object's \"start\" in memory.\n", + "\n", + "The figure below visualizes how we should think of hash tables. An empty `dict` object, created with the literal `{}`, still takes a lot of memory: It is essentially one big, contiguous, and empty table." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "| Bucket | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |\n", + "| :---: |:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|\n", + "| Key |*...*|*...*|*...*|*...*|*...*|*...*|*...*|*...*|\n", + "| Value |*...*|*...*|*...*|*...*|*...*|*...*|*...*|*...*|" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "To insert a key-value pair, the key must be translated into a bucket's address. As the first step to do so, the built-in [hash()](https://docs.python.org/3/library/functions.html#hash) function maps any **hashable** object to its **hash value**, a long `int` number, similar to the ones returned by the built-in [id()](https://docs.python.org/3/library/functions.html#id) function. This hash value is a summary of all the $0$s and $1$s that make up an object's value, and, according to the official [glossary](https://docs.python.org/3/glossary.html#term-hashable), an object is hashable *only if* \"it has a hash value which *never* changes during its *lifetime*.\" So, hashability implies immutability! This formal requirement is also absolutely useful to have: Without it, an object may have *several* addresses and, thus, we could not sort it into a *predictable* bucket. The exact logic behind [hash()](https://docs.python.org/3/library/functions.html#hash) is beyond the scope of this book.\n", + "\n", + "Let's calculate the hash value of `\"zero\"`. Because `str` objects are immutable, that works. Hash values have *no* semantic meaning. Also, everytime we re-start Python, we see *different* hash values for the *same* objects. That is a security measure, and we do not go into the technicalities here (cf. [source](https://docs.python.org/3/using/cmdline.html#envvar-PYTHONHASHSEED))." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "-2242252796476499076" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "hash(\"zero\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "For numeric objects, we can sometimes predict the hash values. However, we must *never* interpret any meaning into them." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "0" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "hash(0)" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "230584300921369408" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "hash(0.1)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "The [glossary](https://docs.python.org/3/glossary.html#term-hashable) states a second requirement for hashability, namely that \"objects which *compare equal* must have the *same* hash value.\" The purpose of this is to ensure that if we put, for example, `1` as a key in a `dict` object, we can look it up later with `1.0`. In other words, we can look up keys by their object's value (i.e., in the meaning of [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb#Value)). The converse statement does *not* hold: Two objects *may* (accidentally) have the *same* hash value and *not* compare equal." + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "1 == 1.0" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "hash(1) == hash(1.0)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Because `list` objects are not immutable, they are *never* hashable, as indicated by the `TypeError`." + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "ename": "TypeError", + "evalue": "unhashable type: 'list'", + "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[0mhash\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m1\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: unhashable type: 'list'" + ] + } + ], + "source": [ + "hash([0, 1])" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "If we need keys composed of several objects, we can use `tuple` objects instead. In general, the key object must be hashable as a whole. So, we must *never* put a *mutable* object in a `tuple` object used as a key, as well." + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "3713080549409410656" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "hash((0, 1))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "There is no such restiction on objects inserted into `dict` objects as *values*." + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{(0, 1): ['zero', 'one']}" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "{\n", + " (0, 1): [\"zero\", \"one\"],\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "A natural question to ask is how does Python know how much memory it should reserve for a `dict` object's hash table. And, the answer is: Python allocates the memory according to some internal heuristics, and whenever a hash table is roughly 2/3 full, it creates a *new* one with twice the space, and re-inserts all items, one by one, from the *old* one. So, during its lifetime, a `dict` object may have several hash tables.\n", + "\n", + "Let's see how Python translates the keys' hash values into buckets and what happens if a hash table gets too crowded. Assume the now bigger `from_words` example ..." + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "from_words = {\n", + " \"zero\": 0,\n", + " \"one\": 1,\n", + " \"two\": 2,\n", + " \"three\": 3,\n", + " \"four\": 4,\n", + " \"five\": 5,\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "... is to be stored in a hash table with eight buckets. Thus, to label the buckets, we need three bits (i.e., $2^3 = 8$).\n", + "\n", + "Once Python has obtained an object's hash value with [hash()](https://docs.python.org/3/library/functions.html#hash), that number's *least* significant bits in *binary* representation are used as the address. So, here, we cut off the last three digits. These digits could be converted back into an integer for nicer presentation, but Python itself does not need that.\n", + "\n", + "In summary, with a potentially infinite number of possible keys being mapped on a limited number of buckets, there is a chance that two or more keys end up in the *same* bucket. That is called a **hash collision** and sounds worse than it is. In such cases, Python uses a perturbation rule to rearrange the bits, and if the corresponding next bucket is empty, places an item there. The main disadvantage of that is that the nice offsetting logic from above breaks down, and Python needs more time on average to place items into a hash table. The remedy is to just use a bigger hash table as then the chance of colliding hashes decreases. Luckily, Python does all that for us in the background. So, the main cost we pay for that convenience is the *high* memory usage of `dict` objects.\n", + "\n", + "Another effect of this hashing logic is that items that have keys with the *same* value (i.e., $0$s and $1$s) end up in the *same* bucket, as well. The item that gets inserted last *overwrites* all previously inserted items. That is why the two `\"zero\"` keys get \"merged\" above.\n", + "\n", + "The code below shows how the key objects are transformed into their hash values in integer and binary representation, the least significant bits of the latter are cut off, and the buckets are obtained. Because we use `str` objects as the keys, their buckets are *unpredictable*. So, taking into account the randomization for security purposes from above, we may or may not see redundant buckets in the output. The chance is rather high." + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "def buckets(mapping, *, bits):\n", + " \"\"\"Calculate the bucket indices for a mapping's keys.\"\"\"\n", + " for key in mapping: # cf., next sub-section for details on looping\n", + " hash_value = hash(key)\n", + " binary = bin(hash_value)\n", + " address = binary[-bits:]\n", + " bucket = int(\"0b\" + address, base=2)\n", + " print(key, hash_value, \"0b...\" + binary[-12:], address, bucket, sep=\"\\t\")" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "zero\t-2242252796476499076\t0b...010010000100\t100\t4\n", + "one\t6742734203392562231\t0b...010000110111\t111\t7\n", + "two\t8400825950927958036\t0b...000000010100\t100\t4\n", + "three\t-7473701886709619306\t0b...101001101010\t010\t2\n", + "four\t5854090567539607813\t0b...100100000101\t101\t5\n", + "five\t5010767421024509913\t0b...111111011001\t001\t1\n" + ] + } + ], + "source": [ + "buckets(from_words, bits=3)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "With five of the six keys already inserted, the next insertion has a chance of more than 50% to be a hash collision." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "| Bucket | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |\n", + "| :---: | :---: |:---:| :---: | :---: |:---:| :---: |:---:| :---: |\n", + "| Key |`\"one\"`|*...*|`\"two\"`|`\"four\"`|*...*|`\"three\"`|*...*|`\"zero\"`|\n", + "| Value | `1` |*...*| `2` | `4` |*...*| `3` |*...*| `0` |" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Luckily, in the given case, Python allocates sixteen buckets trading off memory against insertion speed." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "source": [ + "| Bucket | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |\n", + "| :---: | :---: |:---:|:---:|:---:|:---:|:---:|:---:| :---: |:---:|:---:| :---: | :---: |:---:| :---: |:---:| :---: |\n", + "| Key |`\"one\"`|*...*|*...*|*...*|*...*|*...*|*...*|`\"five\"`|*...*|*...*|`\"two\"`|`\"four\"`|*...*|`\"three\"`|*...*|`\"zero\"`|\n", + "| Value | `1` |*...*|*...*|*...*|*...*|*...*|*...*| `5` |*...*|*...*| `2` | `4` |*...*| `3` |*...*| `0` |" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Sixteen buckets imply four bits (i.e., $2^4$) be cut off from the hash value's binary representation and used as the buckets' indices. It is unlikely we see redundant buckets in the code cell below." + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "zero\t-2242252796476499076\t0b...010010000100\t0100\t4\n", + "one\t6742734203392562231\t0b...010000110111\t0111\t7\n", + "two\t8400825950927958036\t0b...000000010100\t0100\t4\n", + "three\t-7473701886709619306\t0b...101001101010\t1010\t10\n", + "four\t5854090567539607813\t0b...100100000101\t0101\t5\n", + "five\t5010767421024509913\t0b...111111011001\t1001\t9\n" + ] + } + ], + "source": [ + "buckets(from_words, bits=4)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Although hash tables seem quite complex at first sight, they help us to make certain operations very fast as we see further below." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "### Mappings are Collections without \"Predictable Order\"" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "In [Chapter 7](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/07_sequences.ipynb#Collections-vs.-Sequences), we show how *sequences* are a special kind of *collections*. The latter can be described as iterable containers with a finite number of elements.\n", + "\n", + "The `dict` type is a special kind of a *collection*, as well, as revealed with the `Collection` ABC from the [collections.abc](https://docs.python.org/3/library/collections.abc.html) module in the [standard library](https://docs.python.org/3/library/index.html)." + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [], + "source": [ + "import collections.abc as abc" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 30, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "isinstance(to_words, abc.Collection)" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 31, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "isinstance(from_words, abc.Collection)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "So, we may pass `to_words` or `from_words` to the built-in [len()](https://docs.python.org/3/library/functions.html#len) function to obtain the number of *items* they contain." + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "3" + ] + }, + "execution_count": 32, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "len(to_words)" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "6" + ] + }, + "execution_count": 33, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "len(from_words)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "In the terminology of the [collections.abc](https://docs.python.org/3/library/collections.abc.html) module, both are `Sized` objects." + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 34, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "isinstance(to_words, abc.Sized)" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 35, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "isinstance(from_words, abc.Sized)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "`dict` objects may be looped over, for example, with the `for` statement. For technical reasons, we could *not* rely on the iteration order to be *predictable* in any form until Python 3.7 in 2018. Looping over the *same* `dict` objects multiple times during its lifetime could result in *different* iteration orders every time. That behavior is intentional as `dict` objects are optimized for use cases where order does not matter. Starting with Python 3.7, `dict` objects remember the order in that items are *inserted* (cf., [Python 3.7 release notes](https://www.python.org/downloads/release/python-370/)). A lot of research went into this preservation of order (cf., this [PyCon 2017 talk](https://www.youtube.com/watch?v=npw4s1QTmPg) by core developer [Raymond Hettinger](https://github.com/rhettinger)).\n", + "\n", + "Because of that, the order in the two `for`-loops below is the *same* as in the *source code* that defines `to_words` and `from_words` above. In that sense, it is \"*predictable*.\" However, if we fill `dict` objects with data from real-world sources, that kind of predictability is not really helpful as such data are not written as source code, and, thus, we consider the order of items in `dict` objects to be *unpredictable*. Further, when an \"insertion\" accidentally *updates* an item, the ordering remains unchanged." + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Python 3.7.3\r\n" + ] + } + ], + "source": [ + "!python --version # the order in the for-loops is predictable for Python 3.7 or higher only" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "By convention, iteration goes over the *keys* in the `dict` object only. The \"*Dictionary Methods*\" sub-section below shows how to loop over the *items* or the *values* instead." + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0\n", + "1\n", + "2\n" + ] + } + ], + "source": [ + "for number in to_words:\n", + " print(number)" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "zero\n", + "one\n", + "two\n", + "three\n", + "four\n", + "five\n" + ] + } + ], + "source": [ + "for word in from_words:\n", + " print(word)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Without a predictable *forward* order, `dict` objects are not *reversible* either.\n", + "\n", + "So, passing a `dict` object to the [reversed()](https://docs.python.org/3/library/functions.html#reversed) built-in raises a `TypeError` ..." + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "ename": "TypeError", + "evalue": "'dict' object is not reversible", + "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[0;32mfor\u001b[0m \u001b[0mnumber\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mreversed\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mto_words\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[1;32m 2\u001b[0m \u001b[0mprint\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[0;31mTypeError\u001b[0m: 'dict' object is not reversible" + ] + } + ], + "source": [ + "for number in reversed(to_words):\n", + " print(number)" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "ename": "TypeError", + "evalue": "'dict' object is not reversible", + "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[0;32mfor\u001b[0m \u001b[0mword\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mreversed\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfrom_words\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[1;32m 2\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mword\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mTypeError\u001b[0m: 'dict' object is not reversible" + ] + } + ], + "source": [ + "for word in reversed(from_words):\n", + " print(word)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "... and also `Reversible` ABC in the [collections.abc](https://docs.python.org/3/library/collections.abc.html) module confirms that." + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 41, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "isinstance(to_words, abc.Reversible)" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 42, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "isinstance(from_words, abc.Reversible)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Of course, we could use the built-in [sorted()](https://docs.python.org/3/library/functions.html#sorted) function to loop over, for example, `to_words` in a *predictable* order. However, that *materializes* a temporary `list` object in memory containing references to all the key objects *and* creates a *new* order according to some sorting criterion that has *nothing* to do with how the items are ordered inside the hash table." + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "five\n", + "four\n", + "one\n", + "three\n", + "two\n", + "zero\n" + ] + } + ], + "source": [ + "for word in sorted(from_words):\n", + " print(word)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "That approach may be combined with [reversed()](https://docs.python.org/3/library/functions.html#reversed)." + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "zero\n", + "two\n", + "three\n", + "one\n", + "four\n", + "five\n" + ] + } + ], + "source": [ + "for word in reversed(sorted(from_words)):\n", + " print(word)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "To show the third behavior of *collection* types, we use the boolean `in` operator to check if a given and immutable object evaluates equal to a *key* in `to_words` or `from_words`." + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 45, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "1 in to_words" + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 46, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "1.0 in to_words # 1.0 is not contained but compares equal to a key that is" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 47, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "10 in to_words" + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 48, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "\"one\" in from_words" + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 49, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "\"ten\" in from_words" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "The `Container` ABC in the [collections.abc](https://docs.python.org/3/library/collections.abc.html) module may be used to \"ask\" Python to confirm that `to_words` and `from_words` are indeed *container* types." + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 50, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "isinstance(to_words, abc.Container)" + ] + }, + { + "cell_type": "code", + "execution_count": 51, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 51, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "isinstance(from_words, abc.Container)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "#### Membership Testing: Lists vs. Dictionaries" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Because of the [hash table](https://en.wikipedia.org/wiki/Hash_table) implementation, the `in` operator is *extremely* fast: With the hashing/offsetting described above, Python does *not* need to initiate a [linear search](https://en.wikipedia.org/wiki/Linear_search) as in the `list` case but immediately knows the only places in memory where the searched object must be located if present in the hash table at all. Conceptually, that is like comparing the searched object against all key objects with the `==` operator *without* actually doing it.\n", + "\n", + "To show the difference, we run a little experiment. For that, we create a `haystack`, a `list` object, with `10_000_001` elements in it, *one* of which is the `needle`, namely `42`. Once again, the [randint()](https://docs.python.org/3/library/random.html#random.randint) function in the [random](https://docs.python.org/3/library/random.html) module is helpful." + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "import random" + ] + }, + { + "cell_type": "code", + "execution_count": 53, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [], + "source": [ + "random.seed(87)" + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [], + "source": [ + "haystack = [random.randint(99, 9999) for _ in range(10_000_000)] + [42]" + ] + }, + { + "cell_type": "code", + "execution_count": 55, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [], + "source": [ + "needle = 42" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "We put the elements in `haystack` in a *random* order with the [shuffle()](https://docs.python.org/3/library/random.html#random.shuffle) function in the [random](https://docs.python.org/3/library/random.html) module." + ] + }, + { + "cell_type": "code", + "execution_count": 56, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [], + "source": [ + "random.shuffle(haystack)" + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "[8126, 7370, 3735, 213, 7922, 1434, 8557, 9609, 9704, 9564, 9601, 3444]" + ] + }, + "execution_count": 57, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "haystack[:12]" + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "[5842, 1894, 7237, 886, 5945, 4014, 4998, 2055, 3531, 6919, 7875, 1944]" + ] + }, + "execution_count": 58, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "haystack[-12:]" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "As modern computers are generally fast, we search the `needle` a total of `10` times." + ] + }, + { + "cell_type": "code", + "execution_count": 59, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "4.37 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n" + ] + } + ], + "source": [ + "%%timeit -n 1 -r 1\n", + "for _ in range(10):\n", + " needle in haystack" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Now, we convert the elements of `haystack` into the keys of `magic_haystack`, a `dict` object. We use `None` as a dummy value for all items." + ] + }, + { + "cell_type": "code", + "execution_count": 60, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "magic_haystack = dict((x, None) for x in haystack)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Because the hash table implementation is *extremely* fast, we search the `needle` not `10` but `10_000` times. The code cell still runs in only a fraction of the time its counterpart does above." + ] + }, + { + "cell_type": "code", + "execution_count": 61, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "426 ms ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n" + ] + } + ], + "source": [ + "%%timeit -n 1 -r 1\n", + "for _ in range(10_000_000):\n", + " needle in magic_haystack" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "However, there is no fast way to look up the values the keys are mapped to. To achieve that, we have to loop over *all* items and check for each value object if it evaluates equal to searched object. That is, by definition, a linear search, as well, and rather slow. In the context of `dict` objects, we call that a **reverse look-up**." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "### \"Indexing\" -> Key Look-up" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "The same efficient key look-up executed in the background with the `in` operator is also behind the indexing operator `[]`. Instead of returning either `True` or `False`, it returns the value object the looked up key maps to.\n", + "\n", + "To show the similarity to indexing into `list` objects, we provide another example with `to_words_list` below." + ] + }, + { + "cell_type": "code", + "execution_count": 62, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [], + "source": [ + "to_words_list = [\"zero\", \"one\", \"two\"]" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Without the above definitions, we could not tell the difference between `to_words` and `to_words_list`: The usage of the `[]` is the same." + ] + }, + { + "cell_type": "code", + "execution_count": 63, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "'zero'" + ] + }, + "execution_count": 63, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "to_words[0]" + ] + }, + { + "cell_type": "code", + "execution_count": 64, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "'one'" + ] + }, + "execution_count": 64, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "to_words_list[1]" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Because key objects can be of any immutable type and are, in particular, not constrained to just the `int` type, the word \"*indexing*\" is an understatement here. Therefore, in the context of `dict` objects, we view the `[]` operator as a generalization of the indexing operator and refer to it as the **(key) look-up** operator. " + ] + }, + { + "cell_type": "code", + "execution_count": 65, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "2" + ] + }, + "execution_count": 65, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from_words[\"two\"]" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "If a key is not in a `dict` object, Python raises a `KeyError`. A sequence type would raise an `IndexError` in this situation." + ] + }, + { + "cell_type": "code", + "execution_count": 66, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "ename": "KeyError", + "evalue": "'drei'", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mKeyError\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[0mfrom_words\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m\"drei\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;31mKeyError\u001b[0m: 'drei'" + ] + } + ], + "source": [ + "from_words[\"drei\"]" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "While `dict` objects support the `[]` operator to look up a *single* key, the more general concept of *slicing* is *not* available. That is in line with the idea that there is *no* predictable *order* associated with a `dict` object's keys, and slicing requires an order conceptually.\n", + "\n", + "To access \"lower\" levels in nested data, like `people`, we *chain* the look-up operator `[]`. For example, let's view all the mathematicians in `people`." + ] + }, + { + "cell_type": "code", + "execution_count": 67, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "[{'name': 'Gilbert Strang', 'emails': ['gilbert@mit.edu']},\n", + " {'name': 'Leonhard Euler', 'emails': []}]" + ] + }, + "execution_count": 67, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "people[\"mathematicians\"]" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Let's take the first mathematician on the list, ..." + ] + }, + { + "cell_type": "code", + "execution_count": 68, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{'name': 'Gilbert Strang', 'emails': ['gilbert@mit.edu']}" + ] + }, + "execution_count": 68, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "people[\"mathematicians\"][0]" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "... and output his name ..." + ] + }, + { + "cell_type": "code", + "execution_count": 69, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "'Gilbert Strang'" + ] + }, + "execution_count": 69, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "people[\"mathematicians\"][0][\"name\"]" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "... or all his emails." + ] + }, + { + "cell_type": "code", + "execution_count": 70, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "['gilbert@mit.edu']" + ] + }, + "execution_count": 70, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "people[\"mathematicians\"][0][\"emails\"]" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "### Mutability" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Analogous to `list` objects, we may mutate `dict` objects *in place*.\n", + "\n", + "For example, let's translate the English words in `to_words` to their German counterparts. Behind the scenes, Python determines the bucket of the objects passed to the `[]` operator, looks them up in the hash table, and, if present, *updates* their references to the mapped value objects." + ] + }, + { + "cell_type": "code", + "execution_count": 71, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "to_words[0] = \"null\"\n", + "to_words[1] = \"eins\"\n", + "to_words[2] = \"zwei\"" + ] + }, + { + "cell_type": "code", + "execution_count": 72, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{0: 'null', 1: 'eins', 2: 'zwei'}" + ] + }, + "execution_count": 72, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "to_words" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Let's add two more items. Again, Python determines their buckets, but this time finds them to be empty, and *inserts* the references to their key and value objects." + ] + }, + { + "cell_type": "code", + "execution_count": 73, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "to_words[3] = \"drei\"\n", + "to_words[4] = \"vier\"" + ] + }, + { + "cell_type": "code", + "execution_count": 74, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{0: 'null', 1: 'eins', 2: 'zwei', 3: 'drei', 4: 'vier'}" + ] + }, + "execution_count": 74, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "to_words" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "None of these operations change the identity of the `to_words` object." + ] + }, + { + "cell_type": "code", + "execution_count": 75, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "139752734711240" + ] + }, + "execution_count": 75, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "id(to_words) # same memory location as before" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "The `del` statement removes individual items. Python just removes the *two* references to the key and value objects in the corresponding bucket." + ] + }, + { + "cell_type": "code", + "execution_count": 76, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "del to_words[0]" + ] + }, + { + "cell_type": "code", + "execution_count": 77, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{1: 'eins', 2: 'zwei', 3: 'drei', 4: 'vier'}" + ] + }, + "execution_count": 77, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "to_words" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "We may also change parts of nested data, such as `people`.\n", + "\n", + "For example, let's add [Albert Einstein](https://en.wikipedia.org/wiki/Albert_Einstein) to the list of physicists, ..." + ] + }, + { + "cell_type": "code", + "execution_count": 78, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "people[\"physicists\"].append({\"name\": \"Albert Einstein\"})" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "... complete Guido's name, ..." + ] + }, + { + "cell_type": "code", + "execution_count": 79, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [], + "source": [ + "people[\"programmers\"][0][\"name\"] = \"Guido van Rossum\"" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "... and remove his work email because he retired." + ] + }, + { + "cell_type": "code", + "execution_count": 80, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [], + "source": [ + "del people[\"programmers\"][0][\"emails\"][1]" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Now, `people` looks like this." + ] + }, + { + "cell_type": "code", + "execution_count": 81, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'mathematicians': [{'emails': ['gilbert@mit.edu'],\n", + " 'name': 'Gilbert Strang'},\n", + " {'emails': [],\n", + " 'name': 'Leonhard Euler'}],\n", + " 'physicists': [{'name': 'Albert Einstein'}],\n", + " 'programmers': [{'emails': ['guido@python.org'],\n", + " 'name': 'Guido van Rossum'}]}\n" + ] + } + ], + "source": [ + "pprint(people, indent=1, width=60)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "### Dictionary Methods" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "`dict` objects come with many methods bound on them, many of which are standardized by the `Mapping` and `MutableMapping` ABCs from the [collections.abc](https://docs.python.org/3/library/collections.abc.html) module. While the former requires a data type adhering to it to implement the [keys()](https://docs.python.org/3/library/stdtypes.html#dict.keys), [values()](https://docs.python.org/3/library/stdtypes.html#dict.values), [items()](https://docs.python.org/3/library/stdtypes.html#dict.items), and [get()](https://docs.python.org/3/library/stdtypes.html#dict.get) methods, which *never* mutate an object, the latter formalizes the [update()](https://docs.python.org/3/library/stdtypes.html#dict.update), [pop()](https://docs.python.org/3/library/stdtypes.html#dict.pop), [popitem()](https://docs.python.org/3/library/stdtypes.html#dict.popitem), [clear()](https://docs.python.org/3/library/stdtypes.html#dict.clear), and [setdefault()](https://docs.python.org/3/library/stdtypes.html#dict.setdefault) methods, which *may* do so." + ] + }, + { + "cell_type": "code", + "execution_count": 82, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 82, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "isinstance(to_words, abc.Mapping)" + ] + }, + { + "cell_type": "code", + "execution_count": 83, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 83, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "isinstance(from_words, abc.Mapping)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "While iteration over a mapping type goes over its keys, we may emphasize this explicitly by adding the [keys()](https://docs.python.org/3/library/stdtypes.html#dict.keys) method in the `for`-loop. Again, the iteration order is equivalent to the insertion order but still considered *unpredictable*." + ] + }, + { + "cell_type": "code", + "execution_count": 84, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "zero\n", + "one\n", + "two\n", + "three\n", + "four\n", + "five\n" + ] + } + ], + "source": [ + "for word in from_words.keys():\n", + " print(word)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "[keys()](https://docs.python.org/3/library/stdtypes.html#dict.keys) returns an object of type `dict_keys`. That is a dynamic **view** inside the `from_words`'s hash table, which means it does *not* copy the references to the keys, and changes to `from_words` can be seen through it. View objects behave much like `dict` objects themselves." + ] + }, + { + "cell_type": "code", + "execution_count": 85, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "dict_keys(['zero', 'one', 'two', 'three', 'four', 'five'])" + ] + }, + "execution_count": 85, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from_words.keys()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "iews can be materialized with the [list()](https://docs.python.org/3/library/functions.html#func-list) built-in. However, that may introduce *semantic* errors into a program an the newly created `list` object has a \"*predictable*\" order (i.e., indexes `0`, `1`, ...) created from an *unpredictable* one." + ] + }, + { + "cell_type": "code", + "execution_count": 86, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "['zero', 'one', 'two', 'three', 'four', 'five']" + ] + }, + "execution_count": 86, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "list(from_words.keys())" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "To loop over the value objects instead, we use the [values()](https://docs.python.org/3/library/stdtypes.html#dict.values) method. That returns a *view* on the value objects inside `from_words` without copying the references to them." + ] + }, + { + "cell_type": "code", + "execution_count": 87, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0\n", + "1\n", + "2\n", + "3\n", + "4\n", + "5\n" + ] + } + ], + "source": [ + "for number in from_words.values():\n", + " print(number)" + ] + }, + { + "cell_type": "code", + "execution_count": 88, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "dict_values([0, 1, 2, 3, 4, 5])" + ] + }, + "execution_count": 88, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from_words.values()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "To loop over key-value pairs, we invoke the [items()](https://docs.python.org/3/library/stdtypes.html#dict.items) method. That returns a view on the key-value pairs as `tuple` objects, where the first element is the key and the second the value. Because of that, we use tuple unpacking in the `for`-loop." + ] + }, + { + "cell_type": "code", + "execution_count": 89, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "zero -> 0\n", + "one -> 1\n", + "two -> 2\n", + "three -> 3\n", + "four -> 4\n", + "five -> 5\n" + ] + } + ], + "source": [ + "for word, number in from_words.items():\n", + " print(f\"{word} -> {number}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 90, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "dict_items([('zero', 0), ('one', 1), ('two', 2), ('three', 3), ('four', 4), ('five', 5)])" + ] + }, + "execution_count": 90, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from_words.items()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Above, we see how the look-up operator fails *loudly* if a key is not in a `dict` object. For example, `to_words` does *not* have a key `0` any more." + ] + }, + { + "cell_type": "code", + "execution_count": 91, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{1: 'eins', 2: 'zwei', 3: 'drei', 4: 'vier'}" + ] + }, + "execution_count": 91, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "to_words" + ] + }, + { + "cell_type": "code", + "execution_count": 92, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "ename": "KeyError", + "evalue": "0", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mKeyError\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[0mto_words\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;31mKeyError\u001b[0m: 0" + ] + } + ], + "source": [ + "to_words[0]" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "That may be mitigated with the [get()](https://docs.python.org/3/library/stdtypes.html#dict.get) method that takes two arguments: `key` and `default`. It returns the value object `key` maps to if it is in the `dict` object; otherwise, `default` is returned. If not provided, `default` is `None`." + ] + }, + { + "cell_type": "code", + "execution_count": 93, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "'n/a'" + ] + }, + "execution_count": 93, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "to_words.get(0, \"n/a\")" + ] + }, + { + "cell_type": "code", + "execution_count": 94, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "'eins'" + ] + }, + "execution_count": 94, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "to_words.get(1, \"n/a\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "`dict` objects are *mutable* as can be formally verified with the `MutableMapping` ABC from the [collections.abc](https://docs.python.org/3/library/collections.abc.html) module." + ] + }, + { + "cell_type": "code", + "execution_count": 95, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 95, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "isinstance(to_words, abc.MutableMapping)" + ] + }, + { + "cell_type": "code", + "execution_count": 96, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 96, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "isinstance(from_words, abc.MutableMapping)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "The [update()](https://docs.python.org/3/library/stdtypes.html#dict.update) method takes the items of another mapping and either inserts them or overwrites the ones with matching keys already in the `dict` objects. It may be used in the other two ways as the [dict()](https://docs.python.org/3/library/functions.html#func-dict) built-in allows, as well." + ] + }, + { + "cell_type": "code", + "execution_count": 97, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{1: 'eins', 2: 'zwei', 3: 'drei', 4: 'vier'}" + ] + }, + "execution_count": 97, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "to_words" + ] + }, + { + "cell_type": "code", + "execution_count": 98, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [], + "source": [ + "to_spanish = {\n", + " 1: \"uno\",\n", + " 2: \"dos\",\n", + " 3: \"tres\",\n", + " 4: \"cuatro\",\n", + " 5: \"cinco\", \n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 99, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "to_words.update(to_spanish)" + ] + }, + { + "cell_type": "code", + "execution_count": 100, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{1: 'uno', 2: 'dos', 3: 'tres', 4: 'cuatro', 5: 'cinco'}" + ] + }, + "execution_count": 100, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "to_words" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "In contrast to the `pop()` method of the `list` type, the [pop()](https://docs.python.org/3/library/stdtypes.html#dict.pop) method of the `dict` type *requires* a `key` argument to be passed. Then, it removes the corresponding key-value pair *and* returns the value object. If the `key` is not in the `dict` object, a `KeyError` is raised. With an optional `default` argument, that loud error may be suppressed and the `default` returned instead, just as with the [get()](https://docs.python.org/3/library/stdtypes.html#dict.get) method above." + ] + }, + { + "cell_type": "code", + "execution_count": 101, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{'zero': 0, 'one': 1, 'two': 2, 'three': 3, 'four': 4, 'five': 5}" + ] + }, + "execution_count": 101, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from_words" + ] + }, + { + "cell_type": "code", + "execution_count": 102, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [], + "source": [ + "number = from_words.pop(\"zero\")" + ] + }, + { + "cell_type": "code", + "execution_count": 103, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "0" + ] + }, + "execution_count": 103, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "number" + ] + }, + { + "cell_type": "code", + "execution_count": 104, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{'one': 1, 'two': 2, 'three': 3, 'four': 4, 'five': 5}" + ] + }, + "execution_count": 104, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from_words" + ] + }, + { + "cell_type": "code", + "execution_count": 105, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "ename": "KeyError", + "evalue": "'zero'", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mKeyError\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[0mfrom_words\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpop\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"zero\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;31mKeyError\u001b[0m: 'zero'" + ] + } + ], + "source": [ + "from_words.pop(\"zero\")" + ] + }, + { + "cell_type": "code", + "execution_count": 106, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "0" + ] + }, + "execution_count": 106, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from_words.pop(\"zero\", 0)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Similar to the `pop()` method of the `list` type, the [popitem()](https://docs.python.org/3/library/stdtypes.html#dict.popitem) method of the `dict` type removes *and* returns an \"arbitrary\" key-value pair as a `tuple` object from a `dict` object. With the preservation of the insertion order in Python 3.7 and higher, this effectively becomes a \"last in, first out\" rule, just as with the `list` type. Once a `dict` object is empty, [popitem()](https://docs.python.org/3/library/stdtypes.html#dict.popitem) raises a `KeyError`." + ] + }, + { + "cell_type": "code", + "execution_count": 107, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{'one': 1, 'two': 2, 'three': 3, 'four': 4, 'five': 5}" + ] + }, + "execution_count": 107, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from_words" + ] + }, + { + "cell_type": "code", + "execution_count": 108, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [], + "source": [ + "word, number = from_words.popitem()" + ] + }, + { + "cell_type": "code", + "execution_count": 109, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "('five', 5)" + ] + }, + "execution_count": 109, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "word, number" + ] + }, + { + "cell_type": "code", + "execution_count": 110, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{'one': 1, 'two': 2, 'three': 3, 'four': 4}" + ] + }, + "execution_count": 110, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from_words" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "The [clear()](https://docs.python.org/3/library/stdtypes.html#dict.clear) method removes all items but keeps the `dict` object alive in memory." + ] + }, + { + "cell_type": "code", + "execution_count": 111, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [], + "source": [ + "to_words.clear()" + ] + }, + { + "cell_type": "code", + "execution_count": 112, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{}" + ] + }, + "execution_count": 112, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "to_words" + ] + }, + { + "cell_type": "code", + "execution_count": 113, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [], + "source": [ + "from_words.clear()" + ] + }, + { + "cell_type": "code", + "execution_count": 114, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{}" + ] + }, + "execution_count": 114, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from_words" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "The [setdefault()](https://docs.python.org/3/library/stdtypes.html#dict.setdefault) method may have a bit of an unfortunate name but is useful, in particular, with nested `list` objects. It takes two arguments, `key` and `default`, and returns the value mapped to `key` if `key` is in the `dict` object; otherwise, it inserts the `key`-`default` pair *and* returns a reference to the newly created value object. So, it is similar to the [get()](https://docs.python.org/3/library/stdtypes.html#dict.get) method above, but *mutates* the `dict` object.\n", + "\n", + "Consider the `people` example again and note hwo the `dict` object modeling \"Albert Einstein\" has *no* `\"emails\"` key in it." + ] + }, + { + "cell_type": "code", + "execution_count": 115, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'mathematicians': [{'emails': ['gilbert@mit.edu'],\n", + " 'name': 'Gilbert Strang'},\n", + " {'emails': [],\n", + " 'name': 'Leonhard Euler'}],\n", + " 'physicists': [{'name': 'Albert Einstein'}],\n", + " 'programmers': [{'emails': ['guido@python.org'],\n", + " 'name': 'Guido van Rossum'}]}\n" + ] + } + ], + "source": [ + "pprint(people, indent=1, width=60)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Let's say we want to append the imaginary emails `\"leonhard@math.org\"` and `\"albert@physics.org\"`. With the current \"messy\" structure, we cannot be sure if a `dict` object modeling a person has already a `\"emails\"` key or not. We could first use the `in` operator to check for that and create a new `list` object in a second step if one is missing. Third, we would finally append the new email.\n", + "\n", + "[setdefault()](https://docs.python.org/3/library/stdtypes.html#dict.setdefault) allows us to do all of the three steps at once. More importantly, behind the scenes Python only needs to make *one* key look-up instead of potentially three. For large nested data that could speed up the computations significantly.\n", + "\n", + "So, the first code cell below adds the email to the already existing empty `list` object, while the second one creates a new one." + ] + }, + { + "cell_type": "code", + "execution_count": 116, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "people[\"mathematicians\"][1].setdefault(\"emails\", []).append(\"leonhard@math.org\")" + ] + }, + { + "cell_type": "code", + "execution_count": 117, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [], + "source": [ + "people[\"physicists\"][0].setdefault(\"emails\", []).append(\"albert@physics.org\")" + ] + }, + { + "cell_type": "code", + "execution_count": 118, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'mathematicians': [{'emails': ['gilbert@mit.edu'],\n", + " 'name': 'Gilbert Strang'},\n", + " {'emails': ['leonhard@math.org'],\n", + " 'name': 'Leonhard Euler'}],\n", + " 'physicists': [{'emails': ['albert@physics.org'],\n", + " 'name': 'Albert Einstein'}],\n", + " 'programmers': [{'emails': ['guido@python.org'],\n", + " 'name': 'Guido van Rossum'}]}\n" + ] + } + ], + "source": [ + "pprint(people, indent=1, width=60)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "In addition to the standardized methods, `dict` objects come with a [copy()](https://docs.python.org/3/library/stdtypes.html#dict.copy) method on them that creates *shallow* copies." + ] + }, + { + "cell_type": "code", + "execution_count": 119, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "guido = people[\"programmers\"][0].copy()" + ] + }, + { + "cell_type": "code", + "execution_count": 120, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{'name': 'Guido van Rossum', 'emails': ['guido@python.org']}" + ] + }, + "execution_count": 120, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "guido" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "If we mutate `guido`, for example, remove all his emails with the `clear()` method on the `list` type, these changes are also visible through `people`." + ] + }, + { + "cell_type": "code", + "execution_count": 121, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "guido[\"emails\"].clear()" + ] + }, + { + "cell_type": "code", + "execution_count": 122, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{'name': 'Guido van Rossum', 'emails': []}" + ] + }, + "execution_count": 122, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "guido" + ] + }, + { + "cell_type": "code", + "execution_count": 123, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'mathematicians': [{'emails': ['gilbert@mit.edu'],\n", + " 'name': 'Gilbert Strang'},\n", + " {'emails': ['leonhard@math.org'],\n", + " 'name': 'Leonhard Euler'}],\n", + " 'physicists': [{'emails': ['albert@physics.org'],\n", + " 'name': 'Albert Einstein'}],\n", + " 'programmers': [{'emails': [],\n", + " 'name': 'Guido van Rossum'}]}\n" + ] + } + ], + "source": [ + "pprint(people, indent=1, width=60)" + ] + }, + { + "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/master/07_sequences.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": 124, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "to_words = {\n", + " 0: \"zero\",\n", + " 1: \"one\",\n", + " 2: \"two\",\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 125, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [], + "source": [ + "more_words = {\n", + " 2: \"TWO\", # upper case 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` objects 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": 126, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{0: 'zero', 1: 'one', 2: 'TWO', 3: 'three', 4: 'four'}" + ] + }, + "execution_count": 126, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "{**to_words, **more_words}" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "With the [update()](https://docs.python.org/3/library/stdtypes.html#dict.update) method from above, we can only *mutate* one of the two `dict` objects in place. So, unpacking is *no* syntactic sugar in this context." + ] + }, + { + "cell_type": "code", + "execution_count": 127, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [], + "source": [ + "to_words.update(more_words)" + ] + }, + { + "cell_type": "code", + "execution_count": 128, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{0: 'zero', 1: 'one', 2: 'TWO', 3: 'three', 4: 'four'}" + ] + }, + "execution_count": 128, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "to_words # we do not want to change an existing object" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "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": 129, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [], + "source": [ + "def print_args1(*args, **kwargs):\n", + " \"\"\"Print out all arguments passed in.\"\"\"\n", + " for i, arg in enumerate(args):\n", + " print(\"position\", i, arg)\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": 130, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [], + "source": [ + "print_args1()" + ] + }, + { + "cell_type": "code", + "execution_count": 131, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "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": 132, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "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": 133, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "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": [ + "The next example, `print_args2()`, requires the caller to pass one positional argument, captured in the `pos` parameter, and one keyword argument, captured in `key`." + ] + }, + { + "cell_type": "code", + "execution_count": 134, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [], + "source": [ + "def print_args2(pos, *args, key, **kwargs):\n", + " \"\"\"Print out all arguments passed in.\"\"\"\n", + " print(\"required position\", pos)\n", + " for i, arg in enumerate(args):\n", + " print(\"optional position\", i, arg)\n", + " print(\"required keyword\", key)\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": 135, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "ename": "TypeError", + "evalue": "print_args2() missing 1 required positional argument: 'pos'", + "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: 'pos'" + ] + } + ], + "source": [ + "print_args2()" + ] + }, + { + "cell_type": "code", + "execution_count": 136, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "ename": "TypeError", + "evalue": "print_args2() missing 1 required keyword-only argument: 'key'", + "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: 'key'" + ] + } + ], + "source": [ + "print_args2(\"p\")" + ] + }, + { + "cell_type": "code", + "execution_count": 137, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "required position p\n", + "required keyword k\n" + ] + } + ], + "source": [ + "print_args2(\"p\", key=\"k\")" + ] + }, + { + "cell_type": "code", + "execution_count": 138, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "required position p\n", + "optional position 0 x\n", + "optional position 1 y\n", + "required keyword k\n", + "optional keyword flag True\n" + ] + } + ], + "source": [ + "print_args2(\"p\", \"x\", \"y\", key=\"k\", flag=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Just as above when we merge `to_words` and `more_words`, we may use the `**` symbol to unpack the items of a mapping in a function call." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "### Dictionary Comprehensions" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Analogous to list comprehensions in [Chapter 7](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/07_sequences.ipynb#List-Comprehensions), **dictionary comprehensions**, or **dictcomps** for short, are a concise literal notation to derive new `dict` objects out of existing ones.\n", + "\n", + "For example, let's derive `from_words` from `to_words` below by swapping the keys and values." + ] + }, + { + "cell_type": "code", + "execution_count": 139, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "to_words = {\n", + " 0: \"zero\",\n", + " 1: \"one\",\n", + " 2: \"two\",\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Without a dictionary comprehension, we would have to initialize an empty `dict` object, loop over the items of the original one, and insert the key-value pairs one by one in a \"reversed\" fashion as \"value-key\" pairs. That assumes that the values are unique as otherwise some would be merged." + ] + }, + { + "cell_type": "code", + "execution_count": 140, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{'zero': 0, 'one': 1, 'two': 2}" + ] + }, + "execution_count": 140, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from_words = {}\n", + "\n", + "for number, word in to_words.items():\n", + " from_words[word] = number\n", + "\n", + "from_words" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "While that code is correct, it is also unnecessarily verbose. The dictionary comprehension below works in the same way as list comprehensions except that entire expression is written within curly braces `{}` instead of brackets `[]`, and a colon `:` added to separate the keys from the values." + ] + }, + { + "cell_type": "code", + "execution_count": 141, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{'zero': 0, 'one': 1, 'two': 2}" + ] + }, + "execution_count": 141, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "{v: k for k, v in to_words.items()}" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "We may filter out items with an `if`-clause and transform the remaining key and value objects.\n", + "\n", + "For no good reason, let's filter out all words starting with a `\"z\"` and upper case the remainin words." + ] + }, + { + "cell_type": "code", + "execution_count": 142, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{'ONE': 1, 'TWO': 2}" + ] + }, + "execution_count": 142, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "{v.upper(): k for k, v in to_words.items() if not v.startswith(\"z\")}" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Multiple `for`- and/or `if`-clauses are allowed.\n", + "\n", + "For example, let's find all pairs of two numbers from `1` through `10` whose product is \"close\" to `50` (e.g., within a delta of `5`). The resulting `dict` object maps `tuple` to `int` objects." + ] + }, + { + "cell_type": "code", + "execution_count": 143, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{(5, 9): 45,\n", + " (5, 10): 50,\n", + " (6, 8): 48,\n", + " (6, 9): 54,\n", + " (7, 7): 49,\n", + " (8, 6): 48,\n", + " (9, 5): 45,\n", + " (9, 6): 54,\n", + " (10, 5): 50}" + ] + }, + "execution_count": 143, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "one_to_ten = range(1, 11)\n", + "\n", + "{\n", + " (x, y): x * y\n", + " for x in one_to_ten for y in one_to_ten\n", + " if abs(x * y - 50) <= 5\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "### Memoization" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "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": [ + "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/master/04_iteration.ipynb#\"Easy-at-first-Glance\"-Example:-Fibonacci-Numbers) takes long to compute for large Fibonacci numbers as the number of function calls grows exponentially.\n", + "\n", + "The graph below visualizes what the problem is and also suggests a solution: Instead of calculating the return value of the `fibonacci()` function for the *same* argument over and over again, it makes sense to **cache** the result and reuse it. This concept is called **[memoization](https://en.wikipedia.org/wiki/Memoization)** in the computer science literature." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Below is revision of the recursive `fibonacci()` implementation that uses a **globally** defined `dict` object `memo` to store intermediate results and look them up.\n", + "\n", + "To be precise, called with a valid `i`, the the revised `fibonacci()` function first checks if the `i`th Fibonacci number has already been calculated before. If yes, it is in the `memo` dictionary. That number is then returned immediately *without* any more calculations. If no, there is no corresponding entry in `memo` and a recursive function call must be made. The number obtained by recursion is then put into `memo`.\n", + "\n", + "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)` (i.e., the left-most leaf node) 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.\n", + "\n", + "Effectively, this mirrors the *iterative* implementation in that the order of all computational steps are *identical*.\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": 144, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "memo = {\n", + " 0: 0,\n", + " 1: 1,\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 145, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [], + "source": [ + "import numbers # for the goose typing" + ] + }, + { + "cell_type": "code", + "execution_count": 146, + "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", + " Raises:\n", + " TypeError: if i is not an integer\n", + " ValueError: if i is not positive\n", + " \"\"\"\n", + " if not isinstance(i, numbers.Integral):\n", + " raise TypeError(\"i must be an integer\")\n", + " elif i < 0:\n", + " raise ValueError(\"i must be non-negative\")\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": "code", + "execution_count": 147, + "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": 147, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fibonacci(12, debug=True) # = 13th number, 11 recursive calls necessary" + ] + }, + { + "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." + ] + }, + { + "cell_type": "code", + "execution_count": 148, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "144" + ] + }, + "execution_count": 148, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fibonacci(12, debug=True) # = 13th number, no recursive calls needed" + ] + }, + { + "cell_type": "code", + "execution_count": 149, + "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": 149, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "memo" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "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": 150, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The slowest run took 195.30 times longer than the fastest. This could mean that an intermediate result is being cached.\n", + "18.9 µs ± 44.4 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" + ] + } + ], + "source": [ + "%%timeit -n 1\n", + "fibonacci(99) # = 100th number" + ] + }, + { + "cell_type": "code", + "execution_count": 151, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The slowest run took 1919.60 times longer than the fastest. This could mean that an intermediate result is being cached.\n", + "346 µs ± 844 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" + ] + } + ], + "source": [ + "%%timeit -n 1\n", + "fibonacci(999) # = 1,000th number" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "The iterative implementation still has an advantage as the `RecursionError` shows for even larger numbers.\n", + "\n", + "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 ourselves." + ] + }, + { + "cell_type": "code", + "execution_count": 152, + "metadata": { + "scrolled": true, + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "ename": "RecursionError", + "evalue": "maximum recursion depth exceeded in comparison", + "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) # = 10,000th number\\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~/.pyenv/versions/anaconda3-2019.07/lib/python3.7/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 2356\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 2357\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-> 2358\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 2359\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 2360\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~/.pyenv/versions/anaconda3-2019.07/lib/python3.7/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~/.pyenv/versions/anaconda3-2019.07/lib/python3.7/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 1160\u001b[0m \u001b[0;32mbreak\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1161\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 1162\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 1163\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 1164\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/anaconda3-2019.07/lib/python3.7/timeit.py\u001b[0m in \u001b[0;36mrepeat\u001b[0;34m(self, repeat, number)\u001b[0m\n\u001b[1;32m 202\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 203\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--> 204\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 205\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 206\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~/.pyenv/versions/anaconda3-2019.07/lib/python3.7/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 24\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 25\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 26\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 27\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 28\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 24\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 25\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 26\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 27\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 28\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 in comparison" + ] + } + ], + "source": [ + "%%timeit -n 1\n", + "fibonacci(9999) # = 10,000th number" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "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 anyhow. As we are good citizens, we reset everything to the defaults after our hack is completed." + ] + }, + { + "cell_type": "code", + "execution_count": 153, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [], + "source": [ + "import sys" + ] + }, + { + "cell_type": "code", + "execution_count": 154, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [], + "source": [ + "old_recursion_limit = sys.getrecursionlimit()" + ] + }, + { + "cell_type": "code", + "execution_count": 155, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "3000" + ] + }, + "execution_count": 155, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "old_recursion_limit" + ] + }, + { + "cell_type": "code", + "execution_count": 156, + "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": 157, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The slowest run took 25077.61 times longer than the fastest. This could mean that an intermediate result is being cached.\n", + "2.36 ms ± 5.77 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" + ] + } + ], + "source": [ + "%%timeit -n 1\n", + "fibonacci(9999) # = 10,000th number" + ] + }, + { + "cell_type": "code", + "execution_count": 158, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [], + "source": [ + "sys.setrecursionlimit(old_recursion_limit)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "#### \"Easy at third Glance\" Example: [Fibonacci Numbers](https://en.wikipedia.org/wiki/Fibonacci_number) (revisited)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "It is considered *bad practice* to make a function and thereby its correctness dependent on a program's *global state*: For example, `memo` above could be \"manipulated.\"\n", + "\n", + "More often than not, such things happen by accident: Imagine we wrote two independent recursive functions with memoization built-in to solve different problems, and, unintentionally, we made both work with the *same* global `memo`. As a result, we would observe \"random\" bugs depending on the order in which we executed these functions. Such bugs are hard to track down in practice.\n", + "\n", + "A common pattern is to avoid global state and pass intermediate results \"down\" the recursion tree in a \"hidden\" argument. By convention, we prefix variable and argument names with a single leading underscore `_`, such as with `_memo` below, to indicate that a user of our code *must not* use it. Also, we make `_memo` a *keyword-only* argument to force ourselves to always explicitly name it in a function call. Because it is an **implementation detail**, the `_memo` argument is *not* even mentioned in the docstring.\n", + "\n", + "When initially called, `fibonacci()` creates a new `dict` object named `_memo` in its *local scope*. That is then shared \"internally\" between successive function calls by passing it on as an argument. Once the first call to `fibonacci()` returns, `_memo` is \"forgotten.\"" + ] + }, + { + "cell_type": "code", + "execution_count": 159, + "metadata": { + "code_folding": [], + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "def fibonacci(i, *, debug=False, _memo=None):\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", + " Raises:\n", + " TypeError: if i is not an integer\n", + " ValueError: if i is not positive\n", + " \"\"\"\n", + " if not isinstance(i, numbers.Integral):\n", + " raise TypeError(\"i must be an integer\")\n", + " elif i < 0:\n", + " raise ValueError(\"i must be non-negative\")\n", + "\n", + " if _memo is None:\n", + " _memo = {\n", + " 0: 0,\n", + " 1: 1,\n", + " }\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 = (\n", + " fibonacci(i - 1, debug=debug, _memo=_memo)\n", + " + fibonacci(i - 2, debug=debug, _memo=_memo)\n", + " )\n", + " _memo[i] = recurse\n", + " return recurse" + ] + }, + { + "cell_type": "code", + "execution_count": 160, + "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": 160, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fibonacci(12, debug=True) # = 13th number, 11 recursive calls necessary" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Because `fibonacci()` is now independent from *global state*, the same eleven recursive function calls are made each time." + ] + }, + { + "cell_type": "code", + "execution_count": 161, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "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": 161, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fibonacci(12, debug=True) # = 13th number, still 11 recursive calls necessary" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "##### Efficiency of Algorithms (continued)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Also, the runtime is now stable (i.e., no message that \"an intermediate result is being cached\"). The limitation with respect to the maximum number of simultaneous function calls still applies." + ] + }, + { + "cell_type": "code", + "execution_count": 162, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "330 µs ± 18.7 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" + ] + } + ], + "source": [ + "%%timeit -n 1\n", + "fibonacci(99) # = 100th number" + ] + }, + { + "cell_type": "code", + "execution_count": 163, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "3.53 ms ± 75.2 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" + ] + } + ], + "source": [ + "%%timeit -n 1\n", + "fibonacci(999) # = 1,000th number" + ] + }, + { + "cell_type": "code", + "execution_count": 164, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [], + "source": [ + "sys.setrecursionlimit(99999)" + ] + }, + { + "cell_type": "code", + "execution_count": 165, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "15.5 ms ± 572 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" + ] + } + ], + "source": [ + "%%timeit -n 1\n", + "fibonacci(9999) # = 10,000th number" + ] + }, + { + "cell_type": "code", + "execution_count": 166, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [], + "source": [ + "sys.setrecursionlimit(old_recursion_limit)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "### Specialized Mappings" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "The [collections](https://docs.python.org/3/library/collections.html) module in the [standard library](https://docs.python.org/3/library/index.html) provides specialized mapping types for common enough use cases." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "#### The `OrderedDict` Type" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "The [OrderedDict](https://docs.python.org/3/library/collections.html#collections.OrderedDict) type may be used to create a `dict`-like object with the added feature that *explicitly* remembers the *insertion* order of its items.\n", + "\n", + "Let's look at a quick example: We create an `OrderedDict` object by passing an iterable of $2$-element iterables, one of the three ways to use [dict()](https://docs.python.org/3/library/functions.html#func-dict)." + ] + }, + { + "cell_type": "code", + "execution_count": 167, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [], + "source": [ + "from collections import OrderedDict" + ] + }, + { + "cell_type": "code", + "execution_count": 168, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [], + "source": [ + "ordered = OrderedDict([(\"first\", 1), (\"second\", 2), (\"third\", 3)])" + ] + }, + { + "cell_type": "code", + "execution_count": 169, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "OrderedDict([('first', 1), ('second', 2), ('third', 3)])" + ] + }, + "execution_count": 169, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ordered" + ] + }, + { + "cell_type": "code", + "execution_count": 170, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "collections.OrderedDict" + ] + }, + "execution_count": 170, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "type(ordered)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "The iteration order is the insertion order, and ..." + ] + }, + { + "cell_type": "code", + "execution_count": 171, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "first 1\n", + "second 2\n", + "third 3\n" + ] + } + ], + "source": [ + "for key, value in ordered.items():\n", + " print(key, value)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "... we may also loop over `ordered` in *reverse* order." + ] + }, + { + "cell_type": "code", + "execution_count": 172, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "third 3\n", + "second 2\n", + "first 1\n" + ] + } + ], + "source": [ + "for key, value in reversed(ordered.items()):\n", + " print(key, value)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "For `OrderedDict` objects, the [popitem()](https://docs.python.org/3/library/collections.html#collections.OrderedDict.popitem) method takes an optional and boolean argument `last`. That allows us to remove either the *first* or *last* item inserted.\n", + "\n", + "Further, the [move_to_end()](https://docs.python.org/3/library/collections.html#collections.OrderedDict.move_to_end) method allows us to move any item by its key to the end of the order. It also takes an optional argument `last`." + ] + }, + { + "cell_type": "code", + "execution_count": 173, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [], + "source": [ + "ordered.move_to_end(\"first\")" + ] + }, + { + "cell_type": "code", + "execution_count": 174, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "second 2\n", + "third 3\n", + "first 1\n" + ] + } + ], + "source": [ + "for key, value in ordered.items():\n", + " print(key, value)" + ] + }, + { + "cell_type": "code", + "execution_count": 175, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [], + "source": [ + "ordered.move_to_end(\"first\", last=False)" + ] + }, + { + "cell_type": "code", + "execution_count": 176, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "first 1\n", + "second 2\n", + "third 3\n" + ] + } + ], + "source": [ + "for key, value in ordered.items():\n", + " print(key, value)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Otherwise, an `OrderedDict` object is no different than a normal `dict` one, starting with Python 3.7." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "#### The `defaultdict` Type" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "A more useful mapping is the [defaultdict](https://docs.python.org/3/library/collections.html#collections.defaultdict) type, which allows us to define a factory function. That creates default values whenever we look up a key that does not yet exist. Ordinary `dict` objects would throw a `KeyError` exception in such situations.\n", + "\n", + "Let's say we have a `list` with *records* of goals scored during a soccer game. The records consist of the fields \"Country,\" \"Player,\" and the \"Time\" when a goal was scored. Our task is to group the goals by player and/or country." + ] + }, + { + "cell_type": "code", + "execution_count": 177, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [], + "source": [ + "goals = [\n", + " (\"Germany\", \"Müller\", 11), (\"Germany\", \"Klose\", 23),\n", + " (\"Germany\", \"Kroos\", 24), (\"Germany\", \"Kroos\", 26),\n", + " (\"Germany\", \"Khedira\", 29), (\"Germany\", \"Schürrle\", 69),\n", + " (\"Germany\", \"Schürrle\", 79), (\"Brazil\", \"Oscar\", 90),\n", + "]" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Using a normal `dict` object, we have to tediously check if a player has already scored a goal before. If not, we must create a *new* `list` object with the first time the player scored. Otherwise, we append the goal to an already existing `list` object." + ] + }, + { + "cell_type": "code", + "execution_count": 178, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{'Müller': [11],\n", + " 'Klose': [23],\n", + " 'Kroos': [24, 26],\n", + " 'Khedira': [29],\n", + " 'Schürrle': [69, 79],\n", + " 'Oscar': [90]}" + ] + }, + "execution_count": 178, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "goals_by_player = {}\n", + "\n", + "for _, player, minute in goals:\n", + " if player not in goals_by_player:\n", + " goals_by_player[player] = [minute]\n", + " else:\n", + " goals_by_player[player].append(minute)\n", + "\n", + "goals_by_player" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "We could replace the `if`-`else`-logic with the [setdefault()](https://docs.python.org/3/library/stdtypes.html#dict.setdefault) method mentioned above." + ] + }, + { + "cell_type": "code", + "execution_count": 179, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{'Müller': [11],\n", + " 'Klose': [23],\n", + " 'Kroos': [24, 26],\n", + " 'Khedira': [29],\n", + " 'Schürrle': [69, 79],\n", + " 'Oscar': [90]}" + ] + }, + "execution_count": 179, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "goals_by_player = {}\n", + "\n", + "for _, player, minute in goals:\n", + " goals_by_player.setdefault(player, []).append(minute)\n", + "\n", + "goals_by_player" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Instead, with a `defaultdict` object, we can portray the code fragment's intent in a concise form. We pass a reference to the [list()](https://docs.python.org/3/library/functions.html#func-list) built-in to `defaultdict`." + ] + }, + { + "cell_type": "code", + "execution_count": 180, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [], + "source": [ + "from collections import defaultdict" + ] + }, + { + "cell_type": "code", + "execution_count": 181, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "defaultdict(list,\n", + " {'Müller': [11],\n", + " 'Klose': [23],\n", + " 'Kroos': [24, 26],\n", + " 'Khedira': [29],\n", + " 'Schürrle': [69, 79],\n", + " 'Oscar': [90]})" + ] + }, + "execution_count": 181, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "goals_by_player = defaultdict(list)\n", + "\n", + "for _, player, minute in goals:\n", + " goals_by_player[player].append(minute)\n", + "\n", + "goals_by_player" + ] + }, + { + "cell_type": "code", + "execution_count": 182, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "collections.defaultdict" + ] + }, + "execution_count": 182, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "type(goals_by_player)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "The factory function is stored in the `default_factory` attribute." + ] + }, + { + "cell_type": "code", + "execution_count": 183, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "list" + ] + }, + "execution_count": 183, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "goals_by_player.default_factory" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "If we want this code to produce a normal `dict` object, we pass `goals_by_player` to the [dict()](https://docs.python.org/3/library/functions.html#func-dict) built-in." + ] + }, + { + "cell_type": "code", + "execution_count": 184, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{'Müller': [11],\n", + " 'Klose': [23],\n", + " 'Kroos': [24, 26],\n", + " 'Khedira': [29],\n", + " 'Schürrle': [69, 79],\n", + " 'Oscar': [90]}" + ] + }, + "execution_count": 184, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "dict(goals_by_player)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Being creative, we use a factory function that returns another [defaultdict](https://docs.python.org/3/library/collections.html#collections.defaultdict) with [list()](https://docs.python.org/3/library/functions.html#func-list) as its factory to group on the country and the player level simultaneously." + ] + }, + { + "cell_type": "code", + "execution_count": 185, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "defaultdict(()>,\n", + " {'Germany': defaultdict(list,\n", + " {'Müller': [11],\n", + " 'Klose': [23],\n", + " 'Kroos': [24, 26],\n", + " 'Khedira': [29],\n", + " 'Schürrle': [69, 79]}),\n", + " 'Brazil': defaultdict(list, {'Oscar': [90]})})" + ] + }, + "execution_count": 185, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "goals_by_country_and_player = defaultdict(lambda: defaultdict(list))\n", + "\n", + "for country, player, minute in goals:\n", + " goals_by_country_and_player[country][player].append(minute)\n", + "\n", + "goals_by_country_and_player" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Conversion into a normal and nested `dict` object is now a bit tricky but can be achieved in one line with a comprehension." + ] + }, + { + "cell_type": "code", + "execution_count": 186, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'Brazil': {'Oscar': [90]},\n", + " 'Germany': {'Khedira': [29],\n", + " 'Klose': [23],\n", + " 'Kroos': [24, 26],\n", + " 'Müller': [11],\n", + " 'Schürrle': [69, 79]}}\n" + ] + } + ], + "source": [ + "pprint({country: dict(by_player) for country, by_player in goals_by_country_and_player.items()})" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "#### The `Counter` Type" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "A common task is to count the number of occurrences of elements in an iterable.\n", + "\n", + "The [Counter](https://docs.python.org/3/library/collections.html#collections.Counter) type provides an easy-to-use interface that can be called with any iterable and returns a `dict`-like object of type `Counter` that maps each unique elements to the number of times it occurs.\n", + "\n", + "To continue the previous example, let's create an overview that shows how many goals a player scorred. We use a generator expression as the argument to `Counter`." + ] + }, + { + "cell_type": "code", + "execution_count": 187, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "[('Germany', 'Müller', 11),\n", + " ('Germany', 'Klose', 23),\n", + " ('Germany', 'Kroos', 24),\n", + " ('Germany', 'Kroos', 26),\n", + " ('Germany', 'Khedira', 29),\n", + " ('Germany', 'Schürrle', 69),\n", + " ('Germany', 'Schürrle', 79),\n", + " ('Brazil', 'Oscar', 90)]" + ] + }, + "execution_count": 187, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "goals" + ] + }, + { + "cell_type": "code", + "execution_count": 188, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [], + "source": [ + "from collections import Counter" + ] + }, + { + "cell_type": "code", + "execution_count": 189, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [], + "source": [ + "scorers = Counter(x[1] for x in goals)" + ] + }, + { + "cell_type": "code", + "execution_count": 190, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Counter({'Müller': 1,\n", + " 'Klose': 1,\n", + " 'Kroos': 2,\n", + " 'Khedira': 1,\n", + " 'Schürrle': 2,\n", + " 'Oscar': 1})" + ] + }, + "execution_count": 190, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "scorers" + ] + }, + { + "cell_type": "code", + "execution_count": 191, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "collections.Counter" + ] + }, + "execution_count": 191, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "type(scorers)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Now we can look up individual players. `scores` behaves like a normal dictionary with regard to key look-ups." + ] + }, + { + "cell_type": "code", + "execution_count": 192, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "1" + ] + }, + "execution_count": 192, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "scorers[\"Müller\"]" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "By default, it returns `0` if a key is not found. So, we do not have to handle a `KeyError`." + ] + }, + { + "cell_type": "code", + "execution_count": 193, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "0" + ] + }, + "execution_count": 193, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "scorers[\"Lahm\"]" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "`Counter` objects have a [most_common()](https://docs.python.org/3/library/collections.html#collections.Counter.most_common) method that returns a `list` object containing $2$-element `tuple` objects, where the first element is the element from the original iterable and the second the number of occurrences. The `list` object is sorted in descending order of occurrences." + ] + }, + { + "cell_type": "code", + "execution_count": 194, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "[('Kroos', 2), ('Schürrle', 2)]" + ] + }, + "execution_count": 194, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "scorers.most_common(2)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "We can increase the count of individual entries with the [update()](https://docs.python.org/3/library/collections.html#collections.Counter.update) method: That takes an *iterable* of the elements we want to count.\n", + "\n", + "Imagine if [Philipp Lahm](https://en.wikipedia.org/wiki/Philipp_Lahm) had also scored against Brazil." + ] + }, + { + "cell_type": "code", + "execution_count": 195, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [], + "source": [ + "scorers.update([\"Lahm\"])" + ] + }, + { + "cell_type": "code", + "execution_count": 196, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Counter({'Müller': 1,\n", + " 'Klose': 1,\n", + " 'Kroos': 2,\n", + " 'Khedira': 1,\n", + " 'Schürrle': 2,\n", + " 'Oscar': 1,\n", + " 'Lahm': 1})" + ] + }, + "execution_count": 196, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "scorers" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "If we use a `str` object as the argument instead, each individual character is treated as an element to be updated. That is most likely not what we want." + ] + }, + { + "cell_type": "code", + "execution_count": 197, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [], + "source": [ + "scorers.update(\"Lahm\")" + ] + }, + { + "cell_type": "code", + "execution_count": 198, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Counter({'Müller': 1,\n", + " 'Klose': 1,\n", + " 'Kroos': 2,\n", + " 'Khedira': 1,\n", + " 'Schürrle': 2,\n", + " 'Oscar': 1,\n", + " 'Lahm': 1,\n", + " 'L': 1,\n", + " 'a': 1,\n", + " 'h': 1,\n", + " 'm': 1})" + ] + }, + "execution_count": 198, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "scorers" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "#### The `ChainMap` Type" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Consider `to_words`, `more_words`, and `even_more_words` below. Instead of merging the items of the three `dict` objects together into a *new* one, we want to create an object that behaves as if it contained all the unified items in it without materializing them in memory a second time." + ] + }, + { + "cell_type": "code", + "execution_count": 199, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [], + "source": [ + "to_words = {\n", + " 0: \"zero\",\n", + " 1: \"one\",\n", + " 2: \"two\",\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 200, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [], + "source": [ + "more_words = {\n", + " 2: \"TWO\", # upper case to illustrate a point\n", + " 3: \"three\",\n", + " 4: \"four\",\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 201, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [], + "source": [ + "even_more_words = {\n", + " 4: \"FOUR\", # upper case to illustrate a point\n", + " 5: \"five\",\n", + " 6: \"six\",\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "The [ChainMap](https://docs.python.org/3/library/collections.html#collections.ChainMap) type allows us to do precisely that." + ] + }, + { + "cell_type": "code", + "execution_count": 202, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [], + "source": [ + "from collections import ChainMap" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "We simply pass all mappings as positional arguments to `ChainMap` and obtain a **proxy** object that occupies almost no memory but gives us access to the union of all the items." + ] + }, + { + "cell_type": "code", + "execution_count": 203, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [], + "source": [ + "chain = ChainMap(to_words, more_words, even_more_words)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Let's loop over the items in `chain` and see what is \"in\" it. The order is obviously *unpredictable* but all seven items we expected are there. Keys of later mappings do *not* overwrite earlier keys." + ] + }, + { + "cell_type": "code", + "execution_count": 204, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "4 four\n", + "5 five\n", + "6 six\n", + "2 two\n", + "3 three\n", + "0 zero\n", + "1 one\n" + ] + } + ], + "source": [ + "for number, word in chain.items():\n", + " print(number, word)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "When looking up a non-existent key, `ChainMap` objects raise a `KeyError` just like normal `dict` objects would." + ] + }, + { + "cell_type": "code", + "execution_count": 205, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "ename": "KeyError", + "evalue": "10", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mKeyError\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[0mchain\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m10\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~/.pyenv/versions/anaconda3-2019.07/lib/python3.7/collections/__init__.py\u001b[0m in \u001b[0;36m__getitem__\u001b[0;34m(self, key)\u001b[0m\n\u001b[1;32m 912\u001b[0m \u001b[0;32mexcept\u001b[0m \u001b[0mKeyError\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 913\u001b[0m \u001b[0;32mpass\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 914\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__missing__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mkey\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;31m# support subclasses that define __missing__\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 915\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 916\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mget\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mkey\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mdefault\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mNone\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~/.pyenv/versions/anaconda3-2019.07/lib/python3.7/collections/__init__.py\u001b[0m in \u001b[0;36m__missing__\u001b[0;34m(self, key)\u001b[0m\n\u001b[1;32m 904\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 905\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m__missing__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mkey\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--> 906\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mKeyError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mkey\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 907\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 908\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m__getitem__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mkey\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;31mKeyError\u001b[0m: 10" + ] + } + ], + "source": [ + "chain[10]" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "## The `set` Type" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Python's provides a built-in `set` type that resembles [mathematical sets](https://en.wikipedia.org/wiki/Set_%28mathematics%29): Each element may only be a member of a set once, and there is no *predictable* order regarding the elements (cf., [documentation](https://docs.python.org/3/library/stdtypes.html#set)).\n", + "\n", + "To create a set, we use the literal notation, `{..., ...}`, and list all the elements. The redundant `7` and `4` are discarded." + ] + }, + { + "cell_type": "code", + "execution_count": 206, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "numbers = {7, 7, 11, 8, 5, 3, 12, 2, 6, 9, 10, 1, 4, 4}" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "`set` objects are objects on their own." + ] + }, + { + "cell_type": "code", + "execution_count": 207, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "139751998783784" + ] + }, + "execution_count": 207, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "id(numbers)" + ] + }, + { + "cell_type": "code", + "execution_count": 208, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "set" + ] + }, + "execution_count": 208, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "type(numbers)" + ] + }, + { + "cell_type": "code", + "execution_count": 209, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}" + ] + }, + "execution_count": 209, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "numbers" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "To create an empty set, we must use the [set()](https://docs.python.org/3/library/functions.html#func-set) built-in as empty curly brackets, `{}`, already creates an empty `dict` object." + ] + }, + { + "cell_type": "code", + "execution_count": 210, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "empty_dict = {}" + ] + }, + { + "cell_type": "code", + "execution_count": 211, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "dict" + ] + }, + "execution_count": 211, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "type(empty_dict)" + ] + }, + { + "cell_type": "code", + "execution_count": 212, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "empty_set = set()" + ] + }, + { + "cell_type": "code", + "execution_count": 213, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "set()" + ] + }, + "execution_count": 213, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "empty_set" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "The [set()](https://docs.python.org/3/library/functions.html#func-set) built-in takes any iterable and only keeps unique elements.\n", + "\n", + "For example, we obtain all unique letters of a long word like so." + ] + }, + { + "cell_type": "code", + "execution_count": 214, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{'a', 'b', 'c', 'd', 'r'}" + ] + }, + "execution_count": 214, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "set(\"abracadabra\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "### Sets are like \"Dictionaries without Values\"" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "The curly brackets notation can be viewed as a hint that `dict` objects are conceptually generalizations of `set` objects, and we think of `set` objects as a collection consisting of a `dict` object's keys with all the mapped values removed.\n", + "\n", + "Like `dict` objects, `set` objects are built on top of [hash tables](https://en.wikipedia.org/wiki/Hash_table), and, thus, each element must be a *hashable* (i.e., immutable at the very least) object and can only be contained in a set once due to the buckets logic." + ] + }, + { + "cell_type": "code", + "execution_count": 215, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "ename": "TypeError", + "evalue": "unhashable type: 'list'", + "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[0;34m{\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m2\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m3\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: unhashable type: 'list'" + ] + } + ], + "source": [ + "{0, [1, 2], 3}" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "[len()](https://docs.python.org/3/library/functions.html#len) tells us the number of elements in a `set` object, which is always `Sized`." + ] + }, + { + "cell_type": "code", + "execution_count": 216, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "12" + ] + }, + "execution_count": 216, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "len(numbers)" + ] + }, + { + "cell_type": "code", + "execution_count": 217, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 217, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "isinstance(numbers, abc.Sized)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "We may loop over the elements in a `set` object, but we must keep in mind that there is no *predictable* order. In contrast to `dict` objects, the iteration order is also *not* guaranteed to be the insertion order. Because of the special hash values for `int` objects, `numbers` seems to be \"magically\" sorted, which is not the case." + ] + }, + { + "cell_type": "code", + "execution_count": 218, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1 2 3 4 5 6 7 8 9 10 11 12 " + ] + } + ], + "source": [ + "for number in numbers:\n", + " print(number, end=\" \")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "`set` objects are `Iterable`." + ] + }, + { + "cell_type": "code", + "execution_count": 219, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 219, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "isinstance(numbers, abc.Iterable)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "`set` objects are not `Reversible`." + ] + }, + { + "cell_type": "code", + "execution_count": 220, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "ename": "TypeError", + "evalue": "'set' object is not reversible", + "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[0;32mfor\u001b[0m \u001b[0mnumber\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mreversed\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnumbers\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[1;32m 2\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnumber\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mend\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[0;31mTypeError\u001b[0m: 'set' object is not reversible" + ] + } + ], + "source": [ + "for number in reversed(numbers):\n", + " print(number, end=\" \")" + ] + }, + { + "cell_type": "code", + "execution_count": 221, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 221, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "isinstance(numbers, abc.Reversible)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "The boolean `in` operator checks if a given and immutable object evaluates equal to an element in a `set` object. As with `dict` objects, membership testing is an *extremely* fast operation. Conceptually, it has the same result as conducting a linear search with the `==` operator behind the scenes." + ] + }, + { + "cell_type": "code", + "execution_count": 222, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 222, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "0 in numbers" + ] + }, + { + "cell_type": "code", + "execution_count": 223, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 223, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "1 in numbers" + ] + }, + { + "cell_type": "code", + "execution_count": 224, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 224, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "1.0 in numbers" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "So, a `set` object is a `Container`. " + ] + }, + { + "cell_type": "code", + "execution_count": 225, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 225, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "isinstance(numbers, abc.Container)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "There is a `Set` ABC in the [collections.abc](https://docs.python.org/3/library/collections.abc.html) module that formalizes, in particular, the operators supported by `set` objects. Furthermore, the ordinary `set` type is *mutable*, as expressed with the `MutableSet` ABC. The latter formalizes all the methods that mutate a `set` object." + ] + }, + { + "cell_type": "code", + "execution_count": 226, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 226, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "isinstance(numbers, abc.Set)" + ] + }, + { + "cell_type": "code", + "execution_count": 227, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 227, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "isinstance(numbers, abc.MutableSet)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "### No Indexing / Key Look-up / Slicing" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "As `set` objects come without a *predictable* order, indexing and slicing is not supported and results in a `TypeError`. In particular, as there are no values to be looked up, these operations are not *semantically* meaningful. Instead, we check membership via the `in` operator, as shown in the previous sub-section." + ] + }, + { + "cell_type": "code", + "execution_count": 228, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "ename": "TypeError", + "evalue": "'set' object is not subscriptable", + "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[0mnumbers\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\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: 'set' object is not subscriptable" + ] + } + ], + "source": [ + "numbers[0]" + ] + }, + { + "cell_type": "code", + "execution_count": 229, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "ename": "TypeError", + "evalue": "'set' object is not subscriptable", + "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[0mnumbers\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[0m", + "\u001b[0;31mTypeError\u001b[0m: 'set' object is not subscriptable" + ] + } + ], + "source": [ + "numbers[:]" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "### Mutability & Set Methods" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Because the `[]` operator does not work for `set` objects, they are mutated maily via methods (cf., [documentation](https://docs.python.org/3/library/stdtypes.html#set)).\n", + "\n", + "We may add new elements to an existing `set` object with the [add()](https://docs.python.org/3/library/stdtypes.html#frozenset.add) method." + ] + }, + { + "cell_type": "code", + "execution_count": 230, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "numbers.add(99)" + ] + }, + { + "cell_type": "code", + "execution_count": 231, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 99}" + ] + }, + "execution_count": 231, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "numbers" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "The [update()](https://docs.python.org/3/library/stdtypes.html#frozenset.update) method takes an iterable and adds all its elements to a `set` object if they are not already contained in it." + ] + }, + { + "cell_type": "code", + "execution_count": 232, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "numbers.update(range(5))" + ] + }, + { + "cell_type": "code", + "execution_count": 233, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 99}" + ] + }, + "execution_count": 233, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "numbers" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "To remove an element by value, we use the [remove()](https://docs.python.org/3/library/stdtypes.html#frozenset.remove) or [discard()](https://docs.python.org/3/library/stdtypes.html#frozenset.discard) methods. If the element to be removed is not in the `set` object, [remove()](https://docs.python.org/3/library/stdtypes.html#frozenset.remove) raises a loud `KeyError` while [discard()](https://docs.python.org/3/library/stdtypes.html#frozenset.discard) stays *silent*." + ] + }, + { + "cell_type": "code", + "execution_count": 234, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "numbers.remove(99)" + ] + }, + { + "cell_type": "code", + "execution_count": 235, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "ename": "KeyError", + "evalue": "99", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mKeyError\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[0mnumbers\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mremove\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m99\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;31mKeyError\u001b[0m: 99" + ] + } + ], + "source": [ + "numbers.remove(99)" + ] + }, + { + "cell_type": "code", + "execution_count": 236, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [], + "source": [ + "numbers.discard(0)" + ] + }, + { + "cell_type": "code", + "execution_count": 237, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [], + "source": [ + "numbers.discard(0)" + ] + }, + { + "cell_type": "code", + "execution_count": 238, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}" + ] + }, + "execution_count": 238, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "numbers" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "The [pop()](https://docs.python.org/3/library/stdtypes.html#frozenset.pop) method removes an *arbitrary* element. As not even the insertion order is tracked, that removes a \"random\" element in theory." + ] + }, + { + "cell_type": "code", + "execution_count": 239, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "1" + ] + }, + "execution_count": 239, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "numbers.pop()" + ] + }, + { + "cell_type": "code", + "execution_count": 240, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}" + ] + }, + "execution_count": 240, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "numbers" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "The [clear()](https://docs.python.org/3/library/stdtypes.html#frozenset.clear) method discards all elements but keeps the `set` object alive." + ] + }, + { + "cell_type": "code", + "execution_count": 241, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [], + "source": [ + "numbers.clear()" + ] + }, + { + "cell_type": "code", + "execution_count": 242, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "set()" + ] + }, + "execution_count": 242, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "numbers" + ] + }, + { + "cell_type": "code", + "execution_count": 243, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "139751998783784" + ] + }, + "execution_count": 243, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "id(numbers) # same memory location as before" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "### Set Operations" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "The arithmetic and relational operators are overloaded with typical set operations from math. The operators have methods that do the equivalent. We omit them for brevity in this sub-section and only show them as comments in the code cells. Both the operators and the methods return *new* `set` objects without modifying the operands.\n", + "\n", + "We showcase the set operations with easy math examples." + ] + }, + { + "cell_type": "code", + "execution_count": 244, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "numbers = set(range(1, 13))\n", + "zero = {0}\n", + "evens = set(range(2, 13, 2))" + ] + }, + { + "cell_type": "code", + "execution_count": 245, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}" + ] + }, + "execution_count": 245, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "numbers" + ] + }, + { + "cell_type": "code", + "execution_count": 246, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{0}" + ] + }, + "execution_count": 246, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "zero" + ] + }, + { + "cell_type": "code", + "execution_count": 247, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{2, 4, 6, 8, 10, 12}" + ] + }, + "execution_count": 247, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "evens" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "The bitwise OR operator `|` returns the union of two `set` objects." + ] + }, + { + "cell_type": "code", + "execution_count": 248, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}" + ] + }, + "execution_count": 248, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "zero | numbers # zero.union(numbers) or reversed order" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Of course, the operators may be *chained*." + ] + }, + { + "cell_type": "code", + "execution_count": 249, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}" + ] + }, + "execution_count": 249, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "zero | numbers | evens # zero.union(numbers).union(evens) or any possible order" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "To obtain an intersection of two or more `set` objects, we use the bitwise AND operator `&`." + ] + }, + { + "cell_type": "code", + "execution_count": 250, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "set()" + ] + }, + "execution_count": 250, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "zero & numbers # zero.intersection(numbers) or reversed order" + ] + }, + { + "cell_type": "code", + "execution_count": 251, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{2, 4, 6, 8, 10, 12}" + ] + }, + "execution_count": 251, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "numbers & evens # numbers.intersection(evens) or reversed order" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "To calculate a `set` object containing all elements that are in one but not the other `set` object, we use the minus operator `-`. This operation is *not* symmetric." + ] + }, + { + "cell_type": "code", + "execution_count": 252, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{1, 3, 5, 7, 9, 11}" + ] + }, + "execution_count": 252, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "numbers - evens # numbers.difference(evens)" + ] + }, + { + "cell_type": "code", + "execution_count": 253, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "set()" + ] + }, + "execution_count": 253, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "evens - numbers # evens.difference(numbers)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "The *symmetric* difference is defined as the `set` object containing all elements that are in one but not both `set` objects. It is calculated with the bitwise XOR operator `^`." + ] + }, + { + "cell_type": "code", + "execution_count": 254, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{1, 3, 5, 7, 9, 11}" + ] + }, + "execution_count": 254, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "numbers ^ evens # numbers.symmetric_difference(evens)" + ] + }, + { + "cell_type": "code", + "execution_count": 255, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{1, 3, 5, 7, 9, 11}" + ] + }, + "execution_count": 255, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "evens ^ numbers # evens.symmetric_difference(numbers)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "The augmented versions of the four operators (e.g., `|` becomes `|=`) are also defined: They mutate the left operand *in place*." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "### Set Comprehensions" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Python provides a literal notation for **set comprehensions**, or **setcomps** for short, that works exactly like the one for dictionary comprehensions described above except that they use a single loop variable instead of a key-value pair.\n", + "\n", + "For example, let's create a new `set` object that consists of the squares of all the elements of `numbers`." + ] + }, + { + "cell_type": "code", + "execution_count": 256, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144}" + ] + }, + "execution_count": 256, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "{x ** 2 for x in numbers}" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "As before, we may have multiple `for`- and/or `if`-clauses.\n", + "\n", + "For example, let's only keep the squares if they turn out to be an even number, or ..." + ] + }, + { + "cell_type": "code", + "execution_count": 257, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{4, 16, 36, 64, 100, 144}" + ] + }, + "execution_count": 257, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "{x ** 2 for x in numbers if (x ** 2) % 2 == 0}" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "... create a `set` object with all products obtained from the Cartesian product of `numbers` with itself as long as the products are greater than `80`." + ] + }, + { + "cell_type": "code", + "execution_count": 258, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{81, 84, 88, 90, 96, 99, 100, 108, 110, 120, 121, 132, 144}" + ] + }, + "execution_count": 258, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "{x * y for x in numbers for y in numbers if x * y > 80}" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "### The `frozenset` Type" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "As `set` objects are mutable, they may *not* be used, for example, as keys in a `dict` object. Similar to how we replace `list` with `tuple` objects, we may often use a `frozenset` object instead of an ordinary one. The `frozenset` type is a built-in, and as `frozenset` objects are immutable, the only limitation is that we must specify *all* elements *upon* creation (cf., [documentation](https://docs.python.org/3/library/stdtypes.html#frozenset)).\n", + "\n", + "`frozenset` objects are created by passing an iterable to the [frozenset()](https://docs.python.org/3/library/functions.html#func-frozenset) built-in. Note that even though `frozenset` objects are hashable, their elements do still *not* come in a predictable order." + ] + }, + { + "cell_type": "code", + "execution_count": 259, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "frozenset({1, 2, 3})" + ] + }, + "execution_count": 259, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "frozenset([1, 1, 2, 2, 3, 3])" + ] + }, + { + "cell_type": "code", + "execution_count": 260, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "frozenset({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12})" + ] + }, + "execution_count": 260, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "frozenset(numbers)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "## TL;DR" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "`dict` objects are **mutable** one-to-one **mappings** from a set of **key** objects to a set of **value** objects. The association between a key-value pair is also called **item**.\n", + "\n", + "The items contained in a `dict` object have **no order** that is *predictable*.\n", + "\n", + "The underlying **data structure** of the `dict` type are **hash tables**. They make key look-ups *extremely* fast by converting the items' keys into *deterministic* hash values specifiying *precisely* one of a fixed number of equally \"wide\" buckets in which an item's references are stored. A limitation is that objects used as keys must be *immutable* (for technical reasons) and *unique* (for practical reasons).\n", + "\n", + "A `set` object is a **mutable** and **unordered collection** of **immutable** objects. The `set` type mimics sets we know from math." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "## Further Resources" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "This section provides some conference talks that go into great detail regarding the workings of the `dict` type." + ] + }, + { + "cell_type": "code", + "execution_count": 261, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "image/jpeg": "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\n", + "text/html": [ + "\n", + " \n", + " " + ], + "text/plain": [ + "" + ] + }, + "execution_count": 261, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from IPython.display import YouTubeVideo\n", + "YouTubeVideo(\"C4Kc8xzcA68\", width=\"60%\")" + ] + }, + { + "cell_type": "code", + "execution_count": 262, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "image/jpeg": "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jaZrfV2i5bO0tdRTiioZJH1MsUU5jutrbIAy4SyuY5/A854PQ1zc73RAQH+D/s3Q22wUDKGCpiFVCytqX18O4uNTVVLQ+aouDC8kVROBjJwGtAOAFPkRAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAUW2j2wZQ1b4JYy5oo2TxCLnVFTUy1BgipIIjwfI4gY4+EnABKlKh20+xEVxrzVVAZobQcmglYSKulqeUidtTTPLcRSNLWEPHHLejC3hm39o5Vc+3sbS9qNqo6OGH4SG5qnwunlgpIqmuEEbCNb3ughJbC3UGmVwAJBVnctu4I5blAxrgaC2R3HlMsVVyN7ZBVkZlihPxQbTB2tmdWp4bktcBjL5s7eqpkTZpaWZ3I5qWUNq62jgbOXuay4bqmZ/GHPi0Zp3kBpBDXEElU67YqudBWQMdSkVuzVNZ3PdLK0w1dIyvax4aITvKd5rzzuBG66Dnh0UYbzhKdXcunyJXWbV0MM4ppJXCXVDG8tgnfBFLUFoginqWR7mnkeXsAbIQTvGeEZt9r9oZKSahpom04krnysbNVyuhp2GFgfuwWtJkqX6ubHwyI5Tnm4OArdhpXV1TLojqKatqqaqk3lwuNPuHQxUsMjORU3xFZwpWPDnluC7ByAFJNrqCqmEYhjo6uAtkjqqCu5kNQH7sxyCYQvLHMLHDQWkHenoIC1tBNWNlKo4u+rXq5mKum1lVT07zPTw09Wyhu9TuHSSTNkdbTCGSwPYwNfSu3zHc8td8YwY6cZel2opTC6SWUNdFLR007QyXm1Va2mMETW6cvDjVwAFuRz+ngVEYtgasU7Y95TMxb7/SMha+Yw0vwrJSPpKeBzmanUsTadzckD8HDQOAzDtj5DcKGp3rBTxU8XK4edmaromSx0MjBjSWAVdS4k8c09L4OGzUDWMqvh4fp5mUh2vt75nwiZ2pm/GswVDYJHUurlMcFS6Pc1ErND8sjJI3cn5JxQt+2VJUvpzDK3k00FXUb2eKqpi+KmbSPM8BnhDH0wbVNJkJxxGM4diN2fYGeBwicyGSOnNa6lqn3G5SPzUxVMMX+jn/xanlEdU9hkaTka8NGrm3902HlqYKKmkljYyKxXC01D2anP3lbDQRCWEFuHsHJZTzsdLfPg407/AOf54GVOta9l/lvUkVh2mo65zmU75NYjbKGTU9RTOkheSGzwipjBmhJGNbMjiPCsyorZbTXSV0NbXtpYnUlFPRwspJZZhMaqWklnnkMsTdyP4lCBGNWNb8uPBSpcpJJ6jvTcmtYREWp0CIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIDkLt9bR+OpfRG+0nb62j8dS+iN9pauV9s60GsowQC11XSggjIIM8YIIPAjB6F6l4WklfNXI8BHHYhu2e+ZsHt+7RePpPRWe0nb92i8fSeis9pdUN2YtuP5PovRYPYT7mLb5PovRYPYVTpdD4aPQ924r4z6+Zyv2/dovH0norPaTt+7RePpPRWe0uqPuYtvk+i9Fg9hPuYtvk+i9Fg9hNMofDRnu3FfGfXzOV+37tF4+k9FZ7Sdv3aLx9J6Kz2l1R9zFt8n0XosHsJ9zFt8n0XosHsJplD4aHduK+M+vmcr9v3aLx9J6Kz2l72+to/HUnojfaXT9z2atwgmIoKIERSEHksHAhh/mLg2E81v5o/sUzC9jXv7CVitygsThM29Vu9+htTt9bR+OpfRG+0nb62j8dS+iN9pauRS9FpcK5Fb3hiON8zaPb62j8dS+iN9pO31tH46l9Eb7S1ciaLS4VyHeGI43zNo9vraPx1L6I32k7fW0fjqX0RvtLVyJotLhXId4YjjfM2j2+to/HUvojfaTt9bR+OpfRG+0tXImi0uFch3hiON8zaPb62j8dS+iN9pO31tH46l9Eb7S1ciaLS4VyHeGI43zNo9vraPx1L6I32k7fW0fjqX0RvtLVyJotLhXId4YjjfM2j2+to/HUvojfaTt9bR+OpfRG+0tXImi0uFch3hiON8zaPb62j8dS+iN9pO31tH46l9Eb7S1ciaLS4VyHeGI43zNo9vraPx1L6I32k7fW0fjqX0RvtLVyJotLhXId4YjjfM2j2+to/HUvojfaTt9bR+OpfRG+0tXImi0uFch3hiON8zaPb62j8dS+iN9pO31tH46l9Eb7S1ciaLS4VyHeGI43zNo9vraPx1L6I32k7fW0fjqX0RvtLVyJotLhXId4YjjfM2j2+to/HUvojfaTt9bR+OpfRG+0tXImi0uFch3hiON8zaPb62j8dS+iN9pO31tH46l9Eb7S1ciaLS4VyHeGI43zNo9vraPx1L6I32k7fW0fjqX0RvtLVyJotLhXId4YjjfM2j2+to/HUvojfaTt9bR+OpfRG+0tXImi0uFch3hiON8zaPb62j8dS+iN9pO31tH46l9Eb7S1ciaLS4VyHeGI43zNodvraPx1J6I32l72+to/HUvojfaWriiaLS4VyM6fiON8zaPb62j8dS+iN9pO31tH46l9Eb7S1ciaLS4VyMd4YjjfM2j2+to/HUvojfaTt9bR+OpfRG+0tXImi0uFch3hiON8zaPb62j8dS+iN9pO31tH46l9Eb7S1ciaLS4VyHeGI43zNo9vraPx1L6I32k7fW0fjqX0RvtLVyJotLhXId4YjjfM2j2+to/HUvojfaTt9bR+OpfRG+0tXImi0uFch3hiON8zaPb62j8dS+iN9pO31tH46l9Eb7S1ciaLS4VyHeGI43zNo9vraPx1L6I32k7fW0fjqX0RvtLVyJotLhXId4YjjfMK/wBm/v6i/XKX/HjVgr/Zv7+ov1yl/wAeNdp+6yPS99fU7+b0L1eN6PqWEu13cKptHCAXtgfV1Tz+JpwXRxNb/wCLJK1+CeGIJevGfIxi5OyPo0pqKuy6ut7p6Zwje5z5iA5tPBHJPOWk4DtzC0ubHkY1nh4Ssabzc5BmC0Fg6uXVsFPnwEClEhA+nBUHtPZHkhpKcxUEtY42f4Yqp56uGOcwxvdE8yFlOGzTBsbegDqCrw9k2SOou1RLGZLZTUlqqKfnRRTRyXCJhihcHcHbx8gy9xw3dlTY4OorrNTt8/mlufz37Svlj6bt7TV/BfJvf9NxK5b1d4hqls0crBxIobiyeXHWWx1cETXHzZWT2Y2ipbjG99O5wfE/dzwSsdDUU8oGTHPC8ao3fuPVla3reyI6vdQRwObTTQ36209W2lrI6uCamqoqh7Q2phGmSN27cC3AwYys7b2MbtS5lO5zt3ZWtuDs6symqYaHfEdNRu+UHJ44P0LWeHsmpKztfV++3busZp4q8lmyzle2v9tmzeTm6/IT/oZP7jl+e8Het/NH9i/Qi6/IT/oZP7jl+e8Het/NH9imZJ/u+37lV/EX9n3/AGPtERXJ5kIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCuLdRTVMrIKeKSeaQkRxRNL5HkNLiGtbxJ0tJ+oq3U27BH+slp/WJP8tOtKks2DfgjrQgp1Ixe9pdTHdr+++R7j6JL7Kdr+++R7j6JL7K7owsI7ay3Cbk/KG696IS4MkMImLgwQuqA3ctm1kN0E5ycKmjlOrLZFPmellkGhHbNrkcZdr+++R7j6JL7Kdr+++R7j6JL7K7pTCx3rPwRv/L1LiZwt2v775HuPokvsp2v775HuPokvsruGsrIod3vHhm9kbFHn8KR2dLB5zg/sVwsd7T8EY/l+lxPocJVWw16iY+WW1V8ccbHSSSPppWsYxgLnvc4jg0AE58yjy7q7KP8h3n/AMquH+UmXCgVhgsU66d1axUZUwEcK4qLbuCvV4V6pu8q9xXt9HLUSxwQRvmmldpjijGp73YJ0taOk8P3KQ9rq/eR7h6O5Vuwv/rDaP1xn9x67WulYymgmqJM6IYnyv0gF2mNpccAnGcBV+MxkqM1GKvdFzk3JkMTTc5Nqzt0OI+11fvI9w9Hcna6v3ke4ejuXWGyfZJobjXPt8cVRFOxsjgZRC6J+6ID9EkErg7gc56COgqbKLLKVWDs4pFhDINCaupt8jhntdX7yPcPR3J2ur95HuHo7l3NhYS+bTUtKx7nEymOut1vljh0OfFUXOppKanEgc4BrRy2CQ9el2QDwC071n4I3/l6lxPocadrq/eR7h6O5O11fvI9w9Hcu5sJhO9Z+CH8vUuJ9DhntdX7yPcPR3J2ur95HuHo7l3NhMJ3rPwQ/l6lxPocM9rq/eR7h6O5Wl32Nu1HC6oqrdWU8DC0PllhcyNpe4MYC49GXOaPrC7xwtZ/wnf9Wa79LQ/56nXSllOc5qLS1uxxxGQqdOnKak9SbOPURFdHmAr/AGb+/qL9cpf8eNWCv9m/v6i/XKX/AB41rP3WdKXvr6nfw6PqUA2iukdqvTqit+Lt90oqaj5W4ndU9VSTVb2xTuxiGOSOrdhx643Kft6AsfWS0dTvKOURTh+WSQyMEkbuGoxv1DQXaeOk8V5SlKzd1dbz6FWg5RVnZrYRK29jy37nENVO+F9mkszHCSF4NLK98m+a5seHTc/Gro4DgvKjsa29sc7X1VSyGeio6Scb2FjXOt4YKOsD93qjqmaActOnwtVpcuxNYY3h0QqqF00mlraOrqWa5HZdhseSGgNa44aMANJ4AJB2GbG/D5uV1oIDmmeune0gjIIMbhkYU1VYJ37SX/Vf/RXuhO1uzj/2fkYm43iyRyUtJNdqq81vwnS1NO2nFNLJHPC4RNa40kLYY4A1zi5ruPF5HFbF2R2ZpbZE9lOHufM8y1FRM8y1NTKemSeV3F7vN0DqC+9ntmbfbgW0VHT02Rhzoo2h7gOjXJjU/wCsrMKNXrKWqF7b77/stn0JeGw7g86dr7rbvu9pbXX5Cf8AQyf3HL894O9b+aP7F+hF1+Qn/Qyf3HL894O9b+aP7FYZJ/u+37lJ/EX9n3/Y+0RFcnmQiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAKbdgj/WS0/rEn+WnUJU27BH+slp/WJP8tOuNf+nL6P8AQkYT+tD/AHL9TtOrY50b2tdoc5jmtcPwXEEB31Hitb01znprbS0NOKimuNJCIHUgt8k4qahjWsD2VDm7oQOkBfv84w8k4OVs1MLzFOpm7Vc99VpObunbcaxludybV6HT1ZrW19PC2jZS/wAQkoTuhNPvdzjTuTLLvNXBzQ3+ac5XV1e2nlfTGWWrFNUOfC+LmRzNjcYxGNIDnbwABueP71McKnVU7JWPikaHMka5j2noc1wLXA46iCVXTwspVYzzmkne3jrT8flb6NljWxCnDNUbO1r/AG+n38b72aslrJ5alkdPUVlc2GS3TxirgMZbO51c1+TuWuDCWRg54A5HmGV7FlbW1D3msqahzt1G+SmmgmY6GfU7WTPuGRsJyQaYB2NLTk9Jl9jsNPRmR0Ilc+UMa+SeeaplLItW6j3k7y4Rt1vOB+W49JKyYCmV4qVXOi9Xh/4RME3RpThNJuT2+HO/7Ed7KX8h3n/yq4f5SZcJhd2dlL+Q7z/5VcP8pMuEwrrJOyX2PL/xF70Po/2BXq8K9Vutp5x7CXdhf/WG0frjP7j10R2arTcKyvtsMDqllG9kglmhbK+OB7TqfJNuzpaTGGgF+OOeK537C/8ArDaP1xn9x67cnYHNc0jIc0gjOMgjBGepUmVKkoVE4bc12+us9TkKmp0JRezO8jTey2wTKCsNcyorKqbdPiaHgOADy3LiWN1OIDMfWVm9t62Nt2slPcBUy0ctmvs89NDBWVTX1VPUbPMgmlp6Jhc5zGVNU0PIwDPw4kKb2m2tieXbt44nBc8OxnhwwPB4VXntMD6uCucwmppqeqpYZNTgGwVslJLUM0A6XFz6GmOTxG7OOkry+DpYlzdXEzzpNWtusvpY9PJUorMpKy2ml219xoLXc/hFt2bLPsfStpZDDXVT2VNO297wTz07XCmr2xT0Bc+QgnGcnSSMnfbDI8XVopasvq79shUh8MdUDLSR1NgbUzR1EI5u7fT1bnOaQWiMuOBgraO0djp7hCaaqD3078iaBsskcdRGWlroagRuBmp3A4MbuBGQQQSEr7FSzvMksWp5ABO8kb0DA4NdjoVmvmcnfca7koJ4q+ohmhr32WO9OO5aysnj3UthoHxODGgvqLcK91ZlrctEjgTjScWtls9dUQ1EVdHXyQstt1fb2yuqhJG2S7V5tWXcHtuTbaKIDV8Y3Azzsk7H+5ag8R/xZvbT7lqDxH/Fm9tZtHxfL1MXl4dSKugqJIIHzQ1xkfR0jZy3WJXTNp6cyPDXRl0c4dM8cMd5U9fBXNVG50jt3SV0WRI7THqYyXIMMAGYNLXd8eeRpzk55uZD9y1B4j/ize2vDsnb/Ef8Wf209n/F6i8jB2R01LKZGUlZO6cdMpdrAM8jRxMIZE8hgkIfjvmcThYX+EjNvNlaqTS5mt1ufpe1zHt1VtM7S5rwHNcM4wR1KZnZC3H/AGc/76f7RQ7+EvG1my9Yxow1slva0cTgNraYAZPE8F2w9u1jbxRGxt+wnfhf6HICIi9UfPQr/Zv7+ov1yl/x41YK/wBm/v6i/XKX/HjWs9jOlL319Tv5vQPoUYuVFV65WUjJoGycoMrnTRcneZIZNL4C1xngmMxjOWgDjIeJ6ZO08AvcryMZZp9FlFSRF4LO588Ehp9zDHUa2wSvjc6P+K1MckjQx5a0Oe+DmtP4BPSSrSktFbBDTsga5ghp4i6JszWNkqKPLBF0kCGoDwdXUIBkZKmWV7lb9rI07CJB7lZ7gQ6OMHXuZYjUskbG6UPt8jMmQybwP5a4OwAAMNPSspBapWVmrEm5EjXwvYYy2OIQNa+CQySbw6phK84BzvGknI4STK8ysutJ8rGFQint33Le6/e8/wChl/uFcO2bYisnooa0Pp46eWnnqI3PM7nujpJjTVGIqeBzy5sm7yAOidh8OO4bqfiJ/wBDJ/cK4x2UhrBbWTNuk9LSiAtex0LXU8bOWGPQDNMBKC+pllywHiXAZcMKfk5tKVvFfuU+WoxlKCavqez7FEdjyt1tjdPQtc+pgpR8dNI0S1QYaUvMEBDI5WywlrndIlB6Gu0/NL2PK+aJ08L6SWNkYlcWyytIaaeCrbzZIQ4k01VTS4HQJhnBDg3NwWCvaAxl2qWNjlhl0cnk1g0TzSRTxRMlLpmtNFSaQ3i4bg45vCpRWS4hrIfheshjjYyGNrogGCOWSeiZGxwq9L3buPSWAk6ZIGjVwAse1lxLkUqw8d8HzRA9pbPJb6uajmdG+WneY5DGJgwPHSGmeJrnD+cBg8CCRxWOVxcrjPVyOqKiR0s0nF8jsanHHS4tHE+dW6lRvbXtK6ds55uwIiLY1CIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCymyd8mtlbT19O2J01M9z42zNc6Il0b4+e1jg4jDz0HqCxaLDSaszaMnF3W1G9LD2a9qa7eckorTNutGsaJIzl7ZXtDWzXAGQ6IZnYZnAjcTwV/TdlHbSUgR2q3OLt1jEcnHf1T6OI8bh0GoY5ueoYccNIJ0fYr/V0O85LKIjLo1ndxPOWNlY0tdIwmM6J5m5bjhI4LLw9kS8s06a5w04DfiqchrREyLQAY8Bmhjeb0ZLj0klQJ4KN/ZjHqWtPKcre3Od/lY25D2TdtXtDm2q1ubpa4kAkNDoZZwJP9JfFP3ULzpdgjLAeLmg2j+y/tcJpKc0FoEsT6aN7TnTrrM8mDJfhLdy68HiwnoOVqmLby7MADaxzQGhmGw04DgIZqfMmI/jHmKd7S52ScMJ4tGLX7q6/lElVvhv5d1qkEFOC0Qad0yMCPTEwaW81mAcDOSsLBLfGPUy8pu2qc+huJnZc2vIy232l3MEmG5cd27XokwLjnduEchDug7t2OheM7L21xc1goLPrcIi1mTqO+a58YDTcs5MbHPx1BpJwFpqh2lrYAwRTluh7Xh2iN0mpkz6hhdI9ut+JpJX4cT8q/wAKqQ7VVzNGmZrd3p3eKenBYWmR2tuI+a872UEjp3js5W2hR4V1Md5y459Cf7V9m6/TQVVvqqe2MbUU74Jd1FO5wiqoO/jkbWGMkxShwPEcQtSqrWVMkzzJK4vkdp1PPfPLWBgc4/hPIaMuPEnJPFUlJpUY017KsQK+JnWfttu2y54V6iLqtpwb1GS2XvMtvrKauhax8tLKJWNkDjG5wBGHhpBxx6ltTujL1jPJLZjozu6rHQevlHTwP7CtMrL7P7QT0Qc2MRua86y2RjXgyCNzIXnUOhj3CQAY4xsXKth4VNcldknDYupS9mMnFGz+6MvecckturOMbqqznwY5RnK9H8Iu9/M7bgYz8VV4GejP8Y4Z4LXA2tqPisRUwMMgla7TOZC4EluuZ0+9cBqcME/hvznU7N/b7lWXAyHfUkb2yU2iKUva2d+tkrYRqeRo10bHEHrI6AVHeEpLXmLmS1lGu9SqO/0Jsf4Rt6xnklswevd1WMjj84+hed0be/mtryccN1VdfR/tP/eVHLhJcKdtRIJrTNEBJM5jQdJl3cbnPjicNW9G5wM/lOPSeFw+Sv3jnby0mRsjCwyNkY6XPM1mRz/itOG9J6znjkLTR6PCuZ10vE/EfJGc7ou9/NLZjw7mr68j5z4Qf2Fed0bevmtr/wB1Ve8qJtiro4I4XSW9lNvKN2NbpXNa1+9gJDzxZ8XggY6DnicmrSQ1m816bWNbpdLjrIDql1PJK4MBJfpZEMD/AMTOSCM50ejwrmY0zE8b5En7o29dPJbZj9FVe8p3Rt6+a2v/AHVV0ekqNzMr9DWuFo4gn8a0xlwDcgh3B5a1vEfzR1cI5UbVVEnF0VLneMlJELg4uZIJRl2vV32RkccOd18VmOFpPZFczWePxEdtR8jZB/hGXvrpbZ1fiqrr4j/aVg9u+zLc7xQy2+pp6FkMzonOdBHUNlBhmZM3SXzlo50YHEdZUUZtXVASNLadzZHl5a+Iua07hlMNDS7S0NjYAB1ZPVwWBC6QwlNO+baxwq5Rryjm57ae3UERFLK8I044jgRxBHAgjiCCOgoiAuOX1Hzmp9Im9tOX1Hzmp9Im9tEWnZx8EdO2n4vmOX1Hzmp9Im9tOX1Hzmp9Im9tETs4+CHbT8XzHL6j5zU+kTe2nL6j5zU+kTe2iJ2cfBDtp+L5g11Rj74qPP8Axibj5u/6Fb+bq6h1dXQPqH7AiLKilsNZTlLa7jP/AMfuGB9WOC9Lj4T0k9PWcZP0nA/YERZMXPERFkwEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBAcfv8A3jB/ciIDzCaR4B+xeIgPQE0jwBeIsA9wPB+5eoiAIiLICIiA/9k=\n", + "text/html": [ + "\n", + " \n", + " " + ], + "text/plain": [ + "" + ] + }, + "execution_count": 262, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "YouTubeVideo(\"66P5FMkWoVU\", width=\"60%\")" + ] + }, + { + "cell_type": "code", + "execution_count": 263, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "image/jpeg": "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dfw1e3XmZbwsbcXO668vktwY+pI1law3eI1b8bdYSRodaOyXQlmcXhrGPrZynJYlzlnKyixi45cZka9mKsx9K/Rc/Vrd+DfDonDTe0HjAGxuiXanI5SvYfkaFao+GcxwWaF+HI47IwaeDapzx6PazUOBZIAfi+MgTVax6BOjCTZxuUfNPVdLlrjbb6mNrOpYqjux8MRUqz5HFoOp1cSOTYxpy57OQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAUDj20uBndUlWr3EcvLitGWJBbBblX4qOZsbodyUmVrXlgI5F3MkaGeFa6p9HfBdHdijpDKRZi9fFjc+XqXbtpz688vD4m+KlkAEdT4mdY112U837xXrZ+jMJJ9+eP4roeNJvtNhgcK1owyS1WyPswQT8LhWJ2Nil1jjJPwUnLwTp9XNscbFGyV9kcOSCC0x7I5ZGugtTRV68msbDyfJNGAOv4x6gdIxDstkW36tqzJA+Knkbt11l924+SWtPWyEEMTaT4+56nBZajb4BOohJ5EnWw2V2TksULLonsLJMjQGOfIySENwuIyrLVWEN0LiNO69x/U4SRHkCs8yHeaulq6radP5eegljdv8WS5plsMdHIyKZslC/GarpN3hG4HwfwSN++0tfLoCCSDoCryztbQjsmq+ZwkEsUD3iCw6vHYmDDDXlttj4EU7xJHpG5wPwsf8oa4rP7Kz2I84xj4QcmyBsG8X6MMVdkLuLozkN5pPg6q2vbL3nd1U2Gr3Bcvx3n2HySi3C3iwzz12wCLclc58Lg2XfG6JR4J3fCjNgZudVbOXj8/AzVTbTHS2G1mTSGR1ieoHGtabB3VWdK2at3S6Lg8ccGQhmupDdRqNFYv22hlvUKlQmVti1agmlfXssic2tUtSudUsvYIbBbNAxhLC7rcqFPZKwyGCMvhLos9cyj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\n", + "text/html": [ + "\n", + " \n", + " " + ], + "text/plain": [ + "" + ] + }, + "execution_count": 263, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "YouTubeVideo(\"npw4s1QTmPg\", width=\"60%\")" + ] + } + ], + "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" + }, + "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": 2 +} diff --git a/static/fibonacci_call_graph.png b/static/fibonacci_call_graph.png new file mode 100644 index 0000000000000000000000000000000000000000..64cba5d4c09a43f0c2599046e6821dfe595db40e GIT binary patch literal 32706 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
1?x4 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 literal 0 HcmV?d00001 From 7fee5f553000aa46edc8f4874e5ffdce88a507b5 Mon Sep 17 00:00:00 2001 From: Alexander Hess Date: Wed, 20 Nov 2019 11:02:48 +0100 Subject: [PATCH 6/7] Add review and exercises for notebook 08 --- 08_mappings_review_and_exercises.ipynb | 596 +++++++++++++++++++++++++ 1 file changed, 596 insertions(+) create mode 100644 08_mappings_review_and_exercises.ipynb diff --git a/08_mappings_review_and_exercises.ipynb b/08_mappings_review_and_exercises.ipynb new file mode 100644 index 0000000..c05004e --- /dev/null +++ b/08_mappings_review_and_exercises.ipynb @@ -0,0 +1,596 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "# Chapter 8: Mappings & Sets" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Content Review" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Read [Chapter 8](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/08_mappings.ipynb) of the book. Then work through the twelve 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**: `dict` objects are well-suited **to model** discrete mathematical **functions** and to approximate continuous ones. What property of dictionaries is the basis for that claim, and how does it relate to functions in the mathematical sense?" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Q2**: Explain why **hash tables** are a **trade-off** between **computational speed** and **memory** usage!" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Q3:** The `dict` type is an **iterable** that **contains** a **finite** number of key-value pairs. Despite that, why is it *not* considered a **sequence**?" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Q4**: Whereas *key* **look-ups** in a `dict` object run in so-called **[constant time](https://en.wikipedia.org/wiki/Time_complexity#Constant_time)** (i.e., *extremely* fast), that does not hold for *reverse* look-ups. Why is that?" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Q5**: Why is it conceptually correct that the Python core developers do not implement **slicing** with the `[]` operator for `dict` objects?" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Q6**: **Memoization** is an essential concept to know to solve problems in the real world. Together with the idea of **recursion**, it enables us to solve problems in a \"backwards\" fashion *effectively*.\n", + "\n", + "\n", + "Compare the **recursive** formulation of `fibonacci()` in [Chapter 8](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/08_mappings.ipynb#\"Easy-at-third-Glance\"-Example:-Fibonacci-Numbers-%28revisited%29), the \"*Easy at third Glance*\" example, with the **iterative** version in [Chapter 4](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/04_iteration.ipynb#\"Hard-at-first-Glance\"-Example:-Fibonacci-Numbers-%28revisited%29), the \"*Hard at first Glance*\" example!\n", + "\n", + "How are they similar and how do they differ?" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Q7**: How are the `set` and the `dict` type related? How could we use the latter to mimic the former?" + ] + }, + { + "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**: We may *not* put `dict` objects inside other `dict` objects because they are **mutable**." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Q9**: **Mutable** objects (e.g., `list`) may generally *not* be used as keys in a `dict` object. However, if we collect, for example, `list` objects in a `tuple` object, the composite object becomes **hashable**." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Q10**: **Mutability** of a `dict` object works until the underlying hash table becomes too crowded. Then, we cannot insert any items any more making the `dict` object effectively **immutable**. Luckily, that almost never happens in practice." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Q11**: A `dict` object's [update()](https://docs.python.org/3/library/stdtypes.html#dict.update) method only inserts key-value pairs whose key is *not* yet in the `dict` object. So, it does *not* overwrite anything." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Q12**: The `set` type is both a mapping and a sequence." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Coding Exercises" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Working with Nested Data" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's write some code to analyze the historic soccer game [Brazil vs. Germany](https://en.wikipedia.org/wiki/Brazil_v_Germany_%282014_FIFA_World_Cup%29) during the 2014 World Cup.\n", + "\n", + "Below, `players` consists of two nested `dict` objects, one for each team, that hold `tuple` objects (i.e., records) with information on the players. Besides the jersey number, name, and position, each `tuple` objects contains a `list` object with the times when the player scored." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "players = {\n", + " \"Brazil\": [\n", + " (12, \"Júlio César\", \"Goalkeeper\", []),\n", + " (4, \"David Luiz\", \"Defender\", []),\n", + " (6, \"Marcelo\", \"Defender\", []),\n", + " (13, \"Dante\", \"Defender\", []),\n", + " (23, \"Maicon\", \"Defender\", []),\n", + " (5, \"Fernandinho\", \"Midfielder\", []),\n", + " (7, \"Hulk\", \"Midfielder\", []),\n", + " (8, \"Paulinho\", \"Midfielder\", []),\n", + " (11, \"Oscar\", \"Midfielder\", [90]),\n", + " (16, \"Ramires\", \"Midfielder\", []),\n", + " (17, \"Luiz Gustavo\", \"Midfielder\", []),\n", + " (19, \"Willian\", \"Midfielder\", []),\n", + " (9, \"Fred\", \"Striker\", []),\n", + " ],\n", + " \"Germany\": [\n", + " (1, \"Manuel Neuer\", \"Goalkeeper\", []),\n", + " (4, \"Benedikt Höwedes\", \"Defender\", []),\n", + " (5, \"Mats Hummels\", \"Defender\", []),\n", + " (16, \"Philipp Lahm\", \"Defender\", []),\n", + " (17, \"Per Mertesacker\", \"Defender\", []),\n", + " (20, \"Jérôme Boateng\", \"Defender\", []),\n", + " (6, \"Sami Khedira\", \"Midfielder\", [29]),\n", + " (7, \"Bastian Schweinsteiger\", \"Midfielder\", []),\n", + " (8, \"Mesut Özil\", \"Midfielder\", []),\n", + " (13, \"Thomas Müller\", \"Midfielder\", [11]),\n", + " (14, \"Julian Draxler\", \"Midfielder\", []),\n", + " (18, \"Toni Kroos\", \"Midfielder\", [24, 26]),\n", + " (9, \"André Schürrle\", \"Striker\", [69, 79]),\n", + " (11, \"Miroslav Klose\", \"Striker\", [23]),\n", + " ],\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Q13.1**: Write a dictionary comprehension to derive a new `dict` object, called `brazilian_players`, that maps a Brazilian player's name to his position!" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "brazilian_players = {...}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "brazilian_players" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Q13.2**: Generalize the code fragment into a `get_players()` function: Passed a `team` name, it returns a `dict` object like `brazilian_players`. Verify that the function works for the German team!" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def get_players(team):\n", + " ..." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "get_players(\"Germany\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Often, we are given a `dict` object like the one returned from `get_players()`: It is characterized by the observation that a large set of unique keys (i.e., the players' names) is mapped onto a smaller set of non-unique values (i.e., the positions).\n", + "\n", + "**Q13.3**: Create a generic `invert()` function that swaps the keys and values of a `mapping` argument passed to it and returns them in a *new* `dict` object! Ensure that *no* key gets lost. Verify your implementation with the `brazilian_players` dictionary!\n", + "\n", + "Hints: Think of this as a grouping operation. The *new* values are `list` or `tuple` objects that hold the original keys. You may want to use either the the [defaultdict](https://docs.python.org/3/library/collections.html#collections.defaultdict) type from the [collections](https://docs.python.org/3/library/collections.html) module in the [standard library](https://docs.python.org/3/library/index.html) or the [setdefault()](https://docs.python.org/3/library/stdtypes.html#dict.setdefault) method of the ordinary `dict` type." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def invert(mapping):\n", + " ..." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "invert(brazilian_players)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Q13.4**: Write a `score_at_minute()` function: It takes two arguments, `team` and `minute`, and returns the number of goals the `team` has scored up until this time in the game.\n", + "\n", + "Hints: The function may reference the global `players` for simplicity. Earn bonus points if you can write this in a one-line expression using some *reduction* function and a *generator expression*." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def score_at_minute(team, minute):\n", + " ..." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The score at half time was:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "score_at_minute(\"Brazil\", 45)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "score_at_minute(\"Germany\", 45)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The final score was:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "score_at_minute(\"Brazil\", 90)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "score_at_minute(\"Germany\", 90)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Q13.5**: Write a `goals_by_player()` function: It takes an argument like the global `players`, and returns a `dict` object mapping the players to the number of goals they scored.\n", + "\n", + "Hints: Do *not* \"hard code\" the names of the teams! Earn bonus points if you can solve it in a single dictionary comprehension." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def goals_by_player(players):\n", + " ..." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "goals_by_player(players)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Q13.6**: Write a *dictionary comprehension* to filter out the players who did *not* score from the preceding result. Then, write a *set comprehension* that does the same but discards the number of goals scored.\n", + "\n", + "Hints: Reference the `goals_by_player()` function from before." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "{...}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "{...}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Q13.7**: Write a `all_goals()` function: It takes one argument like the global `players` and returns a `list` object containing $2$-element `tuple` objects, where the first element is the minute a player scored and the second his name. The list should be sorted by the time.\n", + "\n", + "Hints: You may want to use either the built-in [sorted()](https://docs.python.org/3/library/functions.html#sorted) function or the `list` type's [sort()](https://docs.python.org/3/library/stdtypes.html#list.sort) method. Earn bonus points if you can write a one-line expression with a *generator expression*." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def all_goals(players):\n", + " ..." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "all_goals(players)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Q13.8**: Lastly, write a `summary()` function: It takes one argument like the global `players` and prints out a concise report of the goals, the score at the half, and the final result.\n", + "\n", + "Hints: Use the `all_goals()` and `score_at_minute()` functions from before.\n", + "\n", + "The output should look similar to this:\n", + "```\n", + "12' Gerd Müller scores\n", + "...\n", + "HALFTIME: TeamA 1 TeamB 2\n", + "77' Ronaldo scores\n", + "...\n", + "FINAL: TeamA 1 TeamB 3\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def summary(players):\n", + " ..." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "summary(players)" + ] + } + ], + "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 +} From 615c3f1cc34c62a39e65e5b796c59d4864312244 Mon Sep 17 00:00:00 2001 From: Alexander Hess Date: Wed, 20 Nov 2019 11:05:04 +0100 Subject: [PATCH 7/7] Bump version number --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 333c176..f25a893 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "intro-to-python" -version = "0.5.0" +version = "0.6.0" description = "An introduction to Python and programming for wanna-be data scientists" authors = ["Alexander Hess "] license = "MIT"