Release 0.3.0

This commit is contained in:
Alexander Hess 2019-10-01 23:23:33 +02:00
commit 6e43ef262c
11 changed files with 7713 additions and 203 deletions

View file

@ -18,7 +18,7 @@
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"Read Chapter 0 of the book. Then work through the ten review questions." "Read [Chapter 0](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/00_start_up.ipynb) of the book. Then work through the ten review questions."
] ]
}, },
{ {

View file

@ -24,7 +24,7 @@
"\n", "\n",
"It is intuitively best to take the very mindset of a small child when learing a foreign language and we do so for learning the Python language as well. This first chapter introduces simplistic examples and we better just accept them as they are without knowing any of the \"grammar\" rules yet. Then, we analyze them in parts and slowly build up our understanding.\n", "It is intuitively best to take the very mindset of a small child when learing a foreign language and we do so for learning the Python language as well. This first chapter introduces simplistic examples and we better just accept them as they are without knowing any of the \"grammar\" rules yet. Then, we analyze them in parts and slowly build up our understanding.\n",
"\n", "\n",
"Consequently, if parts of this chapter do not make sense right away, let's not worry too much. Besides introducing some basics (that we need to understand), it also serves as an outlook for what is to come. So, many terms and concepts referenced here will be covered in great detail in following chapters." "Consequently, if parts of this chapter do not make sense right away, let's not worry too much. Besides introducing some basics that we need to understand, it also serves as an outlook for what is to come. So, many terms and concepts used here will be covered in great detail in following chapters."
] ]
}, },
{ {
@ -46,7 +46,7 @@
} }
}, },
"source": [ "source": [
"As our introductory example, we want to calculate the average of all even numbers from one through ten.\n", "As our introductory example, we want to calculate the *average* of all *even* numbers from *one* through *ten*.\n",
"\n", "\n",
"While we could come up with 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\" from, e.g., one of [Faulhaber's formulas](https://en.wikipedia.org/wiki/Faulhaber%27s_formula)), we instead solve the task programmatically.\n", "While we could come up with 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\" from, e.g., one of [Faulhaber's formulas](https://en.wikipedia.org/wiki/Faulhaber%27s_formula)), we instead solve the task programmatically.\n",
"\n", "\n",
@ -75,7 +75,7 @@
} }
}, },
"source": [ "source": [
"To verify that something happened in our computer's memory, we simply **reference** `numbers` and observe that Python indeed knows about." "To verify that something happened in our computer's memory, we simply **reference** `numbers` and observe that Python indeed knows about it."
] ]
}, },
{ {
@ -110,17 +110,17 @@
} }
}, },
"source": [ "source": [
"So far, so good. Let's see how the desired result could be expressed as a **sequence of instructions** in Python.\n", "So far, so good. Let's see how the desired **computation** could be expressed as a **sequence of instructions** in Python.\n",
"\n", "\n",
"Intuitively, the line `for number in numbers` describes a \"loop\" over all the numbers in the `numbers` list, one at a time.\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", "\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 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",
"\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` were initially set to $0$ and the single `=` 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", "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` were 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", "\n",
"Lastly, the `average` is calculated as the ratio of the final **values** of `total` and `count`. Overall, we divide the sum of all even numbers by the count of all even numbers, which is exactly what we are looking for.\n", "Lastly, the `average` is calculated as the ratio of the final **values** of `total` and `count`. Overall, we divide the sum of all even numbers by the count of all even numbers, which is exactly what we are looking for.\n",
"\n", "\n",
"We also observe how the lines of code \"within\" the `for` and `if` **statements** are *indented* and *aligned* with multiples of **four spaces**: This shows immediately how the lines relate to each other." "We also observe how the lines of code \"within\" the `for` and `if` **statements** are **indented** and *aligned* with multiples of **four spaces**: This shows immediately how the lines relate to each other."
] ]
}, },
{ {
@ -200,7 +200,7 @@
"source": [ "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", "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", "\n",
"By default, Jupyter notebooks show the value of a cell's last so-called **expression**. This output can be suppressed by ending the last line with a semicolon `;`." "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 `;`."
] ]
}, },
{ {
@ -249,7 +249,7 @@
} }
}, },
"source": [ "source": [
"To visualize something before the end of the cell, we can use the [print()](https://docs.python.org/3/library/functions.html#print) built-in **function**." "To visualize something before the end of the cell, we use the [print()](https://docs.python.org/3/library/functions.html#print) built-in **function**."
] ]
}, },
{ {
@ -327,11 +327,13 @@
} }
}, },
"source": [ "source": [
"Python comes with basic mathematical operators built in. **[Operators](https://docs.python.org/3/reference/lexical_analysis.html#operators)** are built-in **tokens** that have a special meaning to the Python interpreter. Most operators either \"operate\" with the object immediately following them (= **unary** operators; e.g., negation) or somehow \"process\" the two objects \"around\" them (= **binary** operators; e.g., addition). But we will see some exceptions from that as well.\n", "Python comes with many **[operators](https://docs.python.org/3/reference/lexical_analysis.html#operators)** built in: They are **tokens** (i.e., \"symbols\") that have a special meaning to the Python interpreter.\n",
"\n", "\n",
"By definition, operators have **no** permanent **side effects** in the computer's memory. Although the code cells in this section do indeed lead to *new* objects being created in memory, they are immediately \"forgotten\" as they are not stored in a **variable** (like `numbers` above). We will revisit this idea further below when we compare **expressions** with **statements**.\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). But we will see many exceptions from that as well.\n",
"\n", "\n",
"Let's see some examples of operators. We start with the binary `+` and the `-` operators for addition and subtraction. Binary operators resemble what mathematicians call [infix notation](https://en.wikipedia.org/wiki/Infix_notation) and have the expected meaning." "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 will continue this thought further below 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."
] ]
}, },
{ {
@ -425,7 +427,7 @@
} }
}, },
"source": [ "source": [
"When we compare the output of the `*` and `/` operators for multiplication and division, we note the subtle difference between the $42$ and the $42.0$. This is a first illustration of the concept of a **data type**." "When we compare the output of the `*` and `/` operators for multiplication and division, we note the subtle *difference* between the $42$ and the $42.0$. This is a first illustration of the concept of a **data type**."
] ]
}, },
{ {
@ -543,7 +545,7 @@
} }
}, },
"source": [ "source": [
"To obtain the remainder of a division, we can use the **modulo operator** `%`." "To obtain the remainder of a division, we use the **modulo operator** `%`."
] ]
}, },
{ {
@ -613,7 +615,7 @@
} }
}, },
"source": [ "source": [
"Modulo division can be useful if we, for example, need to get the last couple of digits of a large integer." "Modulo division is also useful if we, for example, need to get the last couple of digits of a large integer."
] ]
}, },
{ {
@ -672,7 +674,7 @@
} }
}, },
"source": [ "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 any more (but a function). Also observe that [divmod()](https://docs.python.org/3/library/functions.html#divmod) returns a \"pair\" of integers." "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 observe that [divmod()](https://docs.python.org/3/library/functions.html#divmod) returns a \"pair\" of integers."
] ]
}, },
{ {
@ -707,7 +709,7 @@
} }
}, },
"source": [ "source": [
"Raising a number to a power is performed with the **exponentiation operator** `**`. Note that this is different from the `^` operator many other programming languages might use and that also exists in Python with a *different* meaning." "Raising a number to a power is performed with the **exponentiation operator** `**`. This is different from the `^` operator many other programming languages use and that also exists in Python with a *different* meaning."
] ]
}, },
{ {
@ -742,7 +744,7 @@
} }
}, },
"source": [ "source": [
"The normal [order of precedence](https://docs.python.org/3/reference/expressions.html#operator-precedence) from mathematics applies (i.e., \"PEMDAS\" rule) but parentheses help avoid confusion." "The normal [order of precedence](https://docs.python.org/3/reference/expressions.html#operator-precedence) from mathematics applies (i.e., \"PEMDAS\" rule)."
] ]
}, },
{ {
@ -777,7 +779,7 @@
} }
}, },
"source": [ "source": [
"The parentheses here serve as a **delimiter**." "Parentheses help avoid confusion and take the role of a **delimiter** here."
] ]
}, },
{ {
@ -871,7 +873,7 @@
} }
}, },
"source": [ "source": [
"There are plenty more mathematical and non-mathematical operators that are introduced throughout this book together with the concepts they implement or support. Some of these are already shown in the next section." "There are many more non-mathematical operators that are introduced throughout this book together with the concepts they implement. Some of these are already shown in the next section."
] ]
}, },
{ {
@ -949,7 +951,7 @@
{ {
"data": { "data": {
"text/plain": [ "text/plain": [
"139940106427216" "140658972730512"
] ]
}, },
"execution_count": 27, "execution_count": 27,
@ -973,7 +975,7 @@
{ {
"data": { "data": {
"text/plain": [ "text/plain": [
"139940106602344" "140658972907392"
] ]
}, },
"execution_count": 28, "execution_count": 28,
@ -997,7 +999,7 @@
{ {
"data": { "data": {
"text/plain": [ "text/plain": [
"139940105769456" "140658972586992"
] ]
}, },
"execution_count": 29, "execution_count": 29,
@ -1017,7 +1019,7 @@
} }
}, },
"source": [ "source": [
"These addresses are really not that meaningful for anything other than checking if two variables actually **point** at the same object. This may be helpful as different objects can of course have the same value." "These addresses are really not that meaningful for anything other than checking if two variables actually **point** at the same object. This may be helpful as, for example, different objects in memory may of course have the same value."
] ]
}, },
{ {
@ -1041,7 +1043,7 @@
} }
}, },
"source": [ "source": [
"`a` and `d` indeed have the same value as can be checked with the **equality operator** `==`. The resulting `True` (and the `False` below) is yet another data type, a so-called **boolean**. We will look into that closely in Chapter 3." "`a` and `d` indeed have the same value as can be checked with the **equality operator** `==`. The resulting `True` (and the `False` below) is yet another data type, a so-called **boolean**. We will look into that closely in [Chapter 3](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/03_conditionals.ipynb)."
] ]
}, },
{ {
@ -1780,7 +1782,7 @@
" " " "
], ],
"text/plain": [ "text/plain": [
"<IPython.lib.display.YouTubeVideo at 0x7f46584be518>" "<IPython.lib.display.YouTubeVideo at 0x7fedb8113c50>"
] ]
}, },
"execution_count": 50, "execution_count": 50,
@ -1801,7 +1803,7 @@
} }
}, },
"source": [ "source": [
"For example, while the above code to calculate the average of the even numbers from 1 through 10 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 (cf., Chapter 2) as well as a so-called **list comprehension** (cf., Chapter 7). 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 from 1 through 10 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 (cf., [Chapter 2](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/02_functions.ipynb)) as well as a so-called **list comprehension** (cf., Chapter 7). Pythonic code runs faster in many cases and is less error prone."
] ]
}, },
{ {
@ -2003,7 +2005,7 @@
"\n", "\n",
"At the same time, for a beginner's course it is often easier to just code in a linear fashion.\n", "At the same time, for a beginner's course it is often easier to just code in a linear fashion.\n",
"\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) and then use Jupyter notebooks to built up a linear report or story line for a business argument to be made." "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)) and then use Jupyter notebooks to built up a linear report or story line for a business argument to be made."
] ]
}, },
{ {
@ -2868,7 +2870,7 @@
"\n", "\n",
"In general, the assignment statement creates (or overwrites) a variable and makes it point to whatever object is on the right-hand side *only if* the left-hand side is a *pure* name (i.e., it contains no operators like the indexing operator in the example). Otherwise, it mutates some already existing object. And we always have to expect that the latter might have more than one variable pointing at it.\n", "In general, the assignment statement creates (or overwrites) a variable and makes it point to whatever object is on the right-hand side *only if* the left-hand side is a *pure* name (i.e., it contains no operators like the indexing operator in the example). Otherwise, it mutates some already existing object. And we always have to expect that the latter might have more than one variable pointing at it.\n",
"\n", "\n",
"In the beginning, visualizing 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%0Adel%20x,%20y%0Ax%20%3D%20%5B1,%202,%203%5D%0Ay%20%3D%20x.copy%28%29%0Ax%5B0%5D%20%3D%2099&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false) will assist in understanding what is going on." "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."
] ]
}, },
{ {
@ -2903,7 +2905,7 @@
} }
}, },
"source": [ "source": [
"Variable names may contain upper and lower case letters, numbers, and underscores (\"\\_\") and be as long as we want them to be. However, they must not begin with a number. Also, they must not be any of Python's **[keywords](https://docs.python.org/3/reference/lexical_analysis.html#keywords)**.\n", "Variable names may contain upper and lower case letters, numbers, and underscores (\"\\_\") and be as long as we want them to be. However, they must not begin with a number. Also, they must not be any of Python's built-in **[keywords](https://docs.python.org/3/reference/lexical_analysis.html#keywords)**.\n",
"\n", "\n",
"Variable names are usually chosen such that they do not need any more documentation and are self-explanatory. A very common convention is to use so-called **[snake\\_case](https://en.wikipedia.org/wiki/Snake_case)**: Keep everything lowercase and use underscores to seperate words.\n", "Variable names are usually chosen such that they do not need any more documentation and are self-explanatory. A very common convention is to use so-called **[snake\\_case](https://en.wikipedia.org/wiki/Snake_case)**: Keep everything lowercase and use underscores to seperate words.\n",
"\n", "\n",
@ -3150,7 +3152,7 @@
" " " "
], ],
"text/plain": [ "text/plain": [
"<IPython.lib.display.YouTubeVideo at 0x7f465852de48>" "<IPython.lib.display.YouTubeVideo at 0x7fedb808c518>"
] ]
}, },
"execution_count": 93, "execution_count": 93,
@ -3188,7 +3190,7 @@
"\n", "\n",
"What we said about individual operators above, namely that they have *no* side effects, should have been put here to begin with. The examples in the section on operators above were actually all expressions!\n", "What we said about individual operators above, namely that they have *no* side effects, should have been put here to begin with. The examples in the section on operators above were actually all expressions!\n",
"\n", "\n",
"The simplest possible expression contains only one variable (or literal)." "The simplest possible expressions contain only one variable or literal."
] ]
}, },
{ {
@ -3218,6 +3220,33 @@
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 95, "execution_count": 95,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"42"
]
},
"execution_count": 95,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"42"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"For sure, we need to include operators to achieve something useful."
]
},
{
"cell_type": "code",
"execution_count": 96,
"metadata": { "metadata": {
"slideshow": { "slideshow": {
"slide_type": "-" "slide_type": "-"
@ -3230,7 +3259,7 @@
"165" "165"
] ]
}, },
"execution_count": 95, "execution_count": 96,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
@ -3247,12 +3276,12 @@
} }
}, },
"source": [ "source": [
"The definition of an expression is **recursive**. So here the sub-expression `a + b` is combined with the literal `3` by the operator `**` to form the full expression." "The definition of an expression is **recursive**. So, here the sub-expression `a + b` is combined with the literal `3` by the operator `**` to form the full expression."
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 96, "execution_count": 97,
"metadata": { "metadata": {
"slideshow": { "slideshow": {
"slide_type": "-" "slide_type": "-"
@ -3265,7 +3294,7 @@
"4492125" "4492125"
] ]
}, },
"execution_count": 96, "execution_count": 97,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
@ -3282,12 +3311,12 @@
} }
}, },
"source": [ "source": [
"Here, the variable `y` is combined with the literal `2` by the indexing operator `[]`. The resulting expression evaluates to the " "Here, the variable `y` is combined with the literal `2` by the indexing operator `[]`. The resulting expression evaluates to the third element in the `y` list."
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 97, "execution_count": 98,
"metadata": { "metadata": {
"slideshow": { "slideshow": {
"slide_type": "-" "slide_type": "-"
@ -3300,7 +3329,7 @@
"3" "3"
] ]
}, },
"execution_count": 97, "execution_count": 98,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
@ -3317,12 +3346,12 @@
} }
}, },
"source": [ "source": [
"When not used as a **delimiter**, parentheses also constitute an operator, namely the **call operator** `()`. We have seen this syntax above when we \"called\" (i.e., executed) built-in functions and methods." "When not used as a delimiter, parentheses also constitute an operator, namely the **call operator** `()`. We have seen this syntax above when we \"called\" (i.e., executed) built-in functions and methods."
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 98, "execution_count": 99,
"metadata": { "metadata": {
"slideshow": { "slideshow": {
"slide_type": "-" "slide_type": "-"
@ -3335,7 +3364,7 @@
"104" "104"
] ]
}, },
"execution_count": 98, "execution_count": 99,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
@ -3368,7 +3397,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 99, "execution_count": 100,
"metadata": { "metadata": {
"slideshow": { "slideshow": {
"slide_type": "slide" "slide_type": "slide"
@ -3382,7 +3411,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 100, "execution_count": 101,
"metadata": { "metadata": {
"slideshow": { "slideshow": {
"slide_type": "fragment" "slide_type": "fragment"
@ -3395,7 +3424,7 @@
"'Hi class'" "'Hi class'"
] ]
}, },
"execution_count": 100, "execution_count": 101,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
@ -3417,7 +3446,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 101, "execution_count": 102,
"metadata": { "metadata": {
"slideshow": { "slideshow": {
"slide_type": "fragment" "slide_type": "fragment"
@ -3430,7 +3459,7 @@
"'Hi Hi Hi Hi Hi Hi Hi Hi Hi Hi Hi Hi Hi Hi Hi Hi Hi Hi Hi Hi Hi Hi Hi Hi Hi Hi Hi Hi Hi Hi Hi Hi Hi Hi Hi Hi Hi Hi Hi Hi Hi Hi '" "'Hi Hi Hi Hi Hi Hi Hi Hi Hi Hi Hi Hi Hi Hi Hi Hi Hi Hi Hi Hi Hi Hi Hi Hi Hi Hi Hi Hi Hi Hi Hi Hi Hi Hi Hi Hi Hi Hi Hi Hi Hi Hi '"
] ]
}, },
"execution_count": 101, "execution_count": 102,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
@ -3458,14 +3487,14 @@
} }
}, },
"source": [ "source": [
"A **[statement](https://docs.python.org/3/reference/simple_stmts.html)** is anything that changes the state of the program's memory or has some other side effect. Statements do not just evaluate to a value like expressions; instead, they create or change values.\n", "A **[statement](https://docs.python.org/3/reference/simple_stmts.html)** is anything that changes the state of a program or has some other side effect. Statements do not just evaluate to a value like expressions; instead, they create or change values.\n",
"\n", "\n",
"Most notably of course are the `=` and `del` statements." "Most notably of course are the `=` and `del` statements."
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 102, "execution_count": 103,
"metadata": { "metadata": {
"slideshow": { "slideshow": {
"slide_type": "slide" "slide_type": "slide"
@ -3478,7 +3507,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 103, "execution_count": 104,
"metadata": { "metadata": {
"slideshow": { "slideshow": {
"slide_type": "-" "slide_type": "-"
@ -3497,12 +3526,12 @@
} }
}, },
"source": [ "source": [
"The [print()](https://docs.python.org/3/library/functions.html#print) function is regarded a \"statement\" as well. In fact, it used to be a real statement in Python 2 and has all the necessary properties. It is a bit of a corner case as expressions are also \"printed\" in a Jupyter notebook when evaluated last in a code cell." "The built-in [print()](https://docs.python.org/3/library/functions.html#print) function is regarded a \"statement\" as well. In fact, it used to be a real statement in Python 2 and has all the necessary properties. It is a bit of a corner case as expressions are also \"printed\" in a Jupyter notebook when evaluated last in a code cell."
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 104, "execution_count": 105,
"metadata": { "metadata": {
"slideshow": { "slideshow": {
"slide_type": "skip" "slide_type": "skip"
@ -3549,7 +3578,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 105, "execution_count": 106,
"metadata": { "metadata": {
"slideshow": { "slideshow": {
"slide_type": "slide" "slide_type": "slide"
@ -3577,7 +3606,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 106, "execution_count": 107,
"metadata": { "metadata": {
"slideshow": { "slideshow": {
"slide_type": "fragment" "slide_type": "fragment"
@ -3590,7 +3619,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 107, "execution_count": 108,
"metadata": { "metadata": {
"slideshow": { "slideshow": {
"slide_type": "-" "slide_type": "-"
@ -3690,14 +3719,14 @@
" - ignored by Python\n", " - ignored by Python\n",
"\n", "\n",
"\n", "\n",
"- functions (cf., Chapter 2)\n", "- functions (cf., [Chapter 2](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/02_functions.ipynb))\n",
" - named sequences of instructions\n", " - named sequences of instructions\n",
" - the smaller parts in a larger program\n", " - the smaller parts in a larger program\n",
" - make a program more modular and thus easier to understand\n", " - make a program more modular and thus easier to understand\n",
"\n", "\n",
"\n", "\n",
"- flow control (cf., Chapter 3)\n", "- flow control (cf., [Chapter 3](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/03_conditionals.ipynb))\n",
" - expression of **logic** or an **algorithm**\n", " - expression of **business logic** or an **algorithm**\n",
" - conditional execution of a small **branch** within a program (i.e., `if` statements)\n", " - conditional execution of a small **branch** within a program (i.e., `if` statements)\n",
" - repetitive execution of parts of a program (i.e., `for`-loops and `while`-loops)" " - repetitive execution of parts of a program (i.e., `for`-loops and `while`-loops)"
] ]

View file

@ -19,7 +19,7 @@
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"Read Chapter 1 of the book. Then work through the ten review questions." "Read [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb) of the book. Then work through the ten review questions."
] ]
}, },
{ {
@ -275,78 +275,6 @@
"for number in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]:\n", "for number in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]:\n",
" print(...)" " print(...)"
] ]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Fizz Buzz"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The kids game [Fizz Buzz](https://en.wikipedia.org/wiki/Fizz_buzz) is said to be often used in job interviews for entry level positions. However, opinions vary as to how good of a test it actually is ([source](https://news.ycombinator.com/item?id=16446774)).\n",
"\n",
"In its simplest form, a group of people start counting upwards in an alternating fashion. Whenever a number is divisible by $3$, the person must say \"Fizz\" instead of the number. The same holds for numbers divisible by $5$ when the person must say \"Buzz\". If a number is divisible by both numbers, one must say \"FizzBuzz\". Probably, this game would also make a good drinking game with the \"right\" beverages.\n",
"\n",
"With just Chapter 1, we actually do not yet know all of Python's language constructs we need to write an implementation of Fizz Buzz in a Pythonic way. Yet, we will tweak what we know a bit and make it work."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q11.1**: First, create a list `numbers` with the numbers from 1 through 100. You could type all numbers manually but there is of course a smarter way. The built-in [range()](https://docs.python.org/3/library/functions.html#func-range) may be useful here. Read how it works in the documentation. To make the output of [range()](https://docs.python.org/3/library/functions.html#func-range) a `list` object, you have to \"wrap\" it with the [list()](https://docs.python.org/3/library/functions.html#func-list) built-in (i.e., `list(range(...))`)."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"numbers = ..."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q11.2**: Loop over the `numbers` list and replace numbers for which one of the two (or both) conditions apply with text strings `\"Fizz\"`, `\"Buzz\"`, or `\"FizzBuzz\"` using the indexing operator `[...]` and the assignment statement `=`. In the chapter we saw that Python starts indexing with `0` as the first element. So in each iteration of the `for`-loop you have to determine the *index* as well as checking the actual `number`.\n",
"\n",
"Also note that for numbers divisible by both $3$ and $5$ we need some sort of a \"third\" condition check: As we only know about the `if` statement so far (and have not heard about `elif` and `else` from Chapter 3), there will be three `if` statements in total within the loop. And the order of them matters!\n",
"\n",
"Hint: Is there a single condition that checks for both $3$ and $5$?"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"for number in numbers:\n",
" ..."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q11.3**: Create a loop that prints out either the number or any of the Fizz Buzz substitutes. Do it in such a way that we do not end up with 100 lines of output here."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"for number in numbers:\n",
" ..."
]
} }
], ],
"metadata": { "metadata": {

View file

@ -19,7 +19,7 @@
} }
}, },
"source": [ "source": [
"In Chapter 1 we typed the **business logic** of our little program to calculate the mean of a subset of a list of numbers right into the 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) we typed the **business logic** of our little program to calculate the mean of a subset of a list of numbers right into the 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",
"\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 re-using the same parts inside core Python every time we use them.\n",
"\n", "\n",
@ -45,7 +45,7 @@
} }
}, },
"source": [ "source": [
"So-called **[user-defined functions](https://docs.python.org/3/reference/compound_stmts.html#function-definitions)** can be created with the `def` statement. To extend an already familiar example, we re-use the introductory example from Chapter 1 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)** can 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) in its final Pythonic version and transform it into the function `average_evens()` below. \n",
"\n", "\n",
"A function's **name** must be chosen according to the same naming rules as for ordinary variables. In fact, Python manages function names just 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 actually part of the name but must always be written out in the `def` statement for syntactic reasons.\n", "A function's **name** must be chosen according to the same naming rules as for ordinary variables. In fact, Python manages function names just 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 actually part of the name but must always be written out in the `def` statement for syntactic reasons.\n",
"\n", "\n",
@ -142,7 +142,7 @@
{ {
"data": { "data": {
"text/plain": [ "text/plain": [
"139655430681056" "140693945143776"
] ]
}, },
"execution_count": 3, "execution_count": 3,
@ -289,7 +289,7 @@
} }
}, },
"source": [ "source": [
"Once defined we can **call** (i.e., \"execute\") a function with the **call operator** `()`. The formal parameters are filled in by passing variables or expressions as **arguments** to the function within the parentheses." "We can **call** (i.e., \"execute\") a function with the **call operator** `()` as often as we wish. The formal parameters are filled in by passing variables or expressions as **arguments** to the function within the parentheses."
] ]
}, },
{ {
@ -407,7 +407,7 @@
} }
}, },
"source": [ "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 are only mapped 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 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."
] ]
}, },
{ {
@ -485,6 +485,17 @@
"average" "average"
] ]
}, },
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"[PythonTutor](http://pythontutor.com/visualize.html#code=nums%20%3D%20%5B1,%202,%203,%204,%205,%206,%207,%208,%209,%2010%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` is 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-18). When the function returns, only the global frame is left (cf., steps 21-22)."
]
},
{ {
"cell_type": "markdown", "cell_type": "markdown",
"metadata": { "metadata": {
@ -504,7 +515,7 @@
} }
}, },
"source": [ "source": [
"On the contrary, while a function is being executed, it can \"see\" the variables of the **enclosing scope** (i.e., \"outside\" of it). This is a common source of *semantic* errors. Consider the following stylized (and incorrect) example `average_wrong()`. The error is hard to spot with eyes: The function never references the `numbers` parameter but the `nums` variable in the **global scope** instead." "On the contrary, while a function is being executed, it can reference the variables of **enclosing scopes** (i.e., \"outside\" of it). This is a common source of *semantic* errors. Consider the following stylized (and incorrect) example `average_wrong()`. The error is hard to spot with eyes: The function never references the `numbers` parameter but the `nums` variable in the **global scope** instead."
] ]
}, },
{ {
@ -526,11 +537,22 @@
" Returns:\n", " Returns:\n",
" float: average\n", " float: average\n",
" \"\"\"\n", " \"\"\"\n",
" evens = [n for n in nums if n % 2 == 0] # should reference numbers, not nums\n", " evens = [n for n in nums if n % 2 == 0] # should reference numbers not nums\n",
" average = sum(evens) / len(evens)\n", " average = sum(evens) / len(evens)\n",
" return average" " return average"
] ]
}, },
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"`nums` in the global scope is of course unchanged."
]
},
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 16, "execution_count": 16,
@ -555,6 +577,17 @@
"nums" "nums"
] ]
}, },
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Sometimes a function might return a correct solution for *some* inputs ..."
]
},
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 17, "execution_count": 17,
@ -576,7 +609,18 @@
} }
], ],
"source": [ "source": [
"average_wrong(nums) # the result is correct by accident!" "average_wrong(nums) # this is correct by accident"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"... but still be wrong *in general*."
] ]
}, },
{ {
@ -611,7 +655,9 @@
} }
}, },
"source": [ "source": [
"Also, observe how both `average_evens()` and `average_wrong()` use the same names for their respective parameters and variables internally. For sure, Python is smart enough to not mix them up. This is because each function call creates a temporary **[namespace](https://en.wikipedia.org/wiki/Namespace)** that *isolates* the local scope's names for usage only from within the function. As we saw in the [Zen of Python](https://www.python.org/dev/peps/pep-0020/), \"namespaces are one honking great idea\" (cf., `import this`)." "[PythonTutor](http://pythontutor.com/visualize.html#code=nums%20%3D%20%5B1,%202,%203,%204,%205,%206,%207,%208,%209,%2010%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 *12* computational steps, namely one for setting up an empty `list` object, *ten* for filling it with elements derived from `nums` in the global scope, and one to make `evens` point at it (cf., steps 6-18).\n",
"\n",
"The frames logic shown by PythonTutor is the mechanism by which Python not only manages the names inside one function call but for potentially many calls occuring simultaneously as we will see in [Chapter 4](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/04_iteration.ipynb). 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."
] ]
}, },
{ {
@ -633,9 +679,9 @@
} }
}, },
"source": [ "source": [
"Code gets even more confusing when variables by the same name from different scopes collide. In particular, what should we expect to happen if a function changes a globally defined variable internally?\n", "Code gets even more confusing when variables by the *same* name from *different* scopes collide. In particular, what should we expect to happen if a function \"changes\" a globally defined variable internally?\n",
"\n", "\n",
"`average_odds()` below works like `average_evens()` above except that it **[casts](https://en.wikipedia.org/wiki/Type_conversion)** (i.e., \"converts\") the elements of `numbers` as objects of type `int` with the [int()](https://docs.python.org/3/library/functions.html#int) built-in first before filtering and averaging them. In doing so, it introduces an *internal* variable `nums` whose name collides with the one in the global scope. The **inequality operator** `!=` is just the **reversed** version of `==`." "`average_odds()` below works like `average_evens()` above except that it **[casts](https://en.wikipedia.org/wiki/Type_conversion)** (i.e., \"converts\") the elements of `numbers` as objects of type `int` with the [int()](https://docs.python.org/3/library/functions.html#int) built-in first before filtering and averaging them. In doing so, it introduces an *internal* variable `nums` whose name collides with the one in the global scope. To filter for odd numbers, we use the **inequality operator** `!=` that is just the **reversed** version of `==`."
] ]
}, },
{ {
@ -652,7 +698,7 @@
" \"\"\"Calculate the average of all odd numbers in a list.\n", " \"\"\"Calculate the average of all odd numbers in a list.\n",
"\n", "\n",
" Args:\n", " Args:\n",
" numbers (list): a list of numbers; must be integers\n", " numbers (list): a list of numbers; must be integer-like\n",
"\n", "\n",
" Returns:\n", " Returns:\n",
" float: average\n", " float: average\n",
@ -671,7 +717,7 @@
} }
}, },
"source": [ "source": [
"`nums` in the global scope is of course the same list from above." "`nums` in the global scope is still unchanged."
] ]
}, },
{ {
@ -706,7 +752,7 @@
} }
}, },
"source": [ "source": [
"As good practice, let's first use inputs for which we can calculate the answer in our heads to verify that `average_odds()` is correct." "As good practice, let's first use inputs for which we can calculate the answer in our heads to \"verify\" that `average_odds()` is \"correct\"."
] ]
}, },
{ {
@ -730,7 +776,7 @@
} }
], ],
"source": [ "source": [
"average_odds([1, 100, 3, 100, 5]) # verify the function's correctness with predictable inputs" "average_odds([1.0, 10.0, 3.0, 10.0, 5.0]) # verify correctness with predictable inputs"
] ]
}, },
{ {
@ -776,7 +822,7 @@
} }
}, },
"source": [ "source": [
"Python, however, is again smart enough to keep the two `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 pointing to the very same `list` object as before."
] ]
}, },
{ {
@ -811,7 +857,9 @@
} }
}, },
"source": [ "source": [
"The reason why everything works just fine 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 we save that for a later chapter.\n", "The reason why everything just 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%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",
"\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", "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", "\n",
@ -1122,7 +1170,7 @@
} }
}, },
"source": [ "source": [
"So far we have only specified one parameter in each of our user-defined functions. In Chapter 1, however, we saw the built-in function [divmod()](https://docs.python.org/3/library/functions.html#divmod) taking two arguments. Obviously, the order of the numbers passed in mattered. Whenever we call a function and list its arguments in a comma seperated manner, we say that we pass in the arguments by position or refer to them as **positional arguments**." "So far we have only specified one parameter in each of our user-defined functions. In [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb), however, we saw the built-in function [divmod()](https://docs.python.org/3/library/functions.html#divmod) taking two arguments. Obviously, the order of the numbers passed in mattered. Whenever we call a function and list its arguments in a comma seperated manner, we say that we pass in the arguments by position or refer to them as **positional arguments**."
] ]
}, },
{ {
@ -2036,7 +2084,7 @@
{ {
"data": { "data": {
"text/plain": [ "text/plain": [
"139655519584648" "140694050824664"
] ]
}, },
"execution_count": 58, "execution_count": 58,
@ -2308,7 +2356,7 @@
"source": [ "source": [
"Observe how the arguments passed to functions do not need to be just variables or simple literals. Instead, we can pass in any *expression* that evaluates to a *new* object of the type the function expects.\n", "Observe how the arguments passed to functions do not need to be just variables or simple literals. Instead, we can pass in any *expression* that evaluates to a *new* object of the type the function expects.\n",
"\n", "\n",
"So just as a reminder from the expression vs. statement discussion in Chapter 1: An expression is *any* syntactically correct combination of variables and literals with operators. And the call operator `()` is just ... well another operator. So both of the next two code cells are just expressions! They have no permanent side effect in memory. We can execute them as often as we want *without* changing the state of the program (i.e., this Jupyter notebook).\n", "So just as a reminder from the expression vs. statement discussion in [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb): An expression is *any* syntactically correct combination of variables and literals with operators. And the call operator `()` is just ... well another operator. So both of the next two code cells are just expressions! They have no permanent side effect in memory. We can execute them as often as we want *without* changing the state of the program (i.e., this Jupyter notebook).\n",
"\n", "\n",
"So, regarding the very next cell in particular: Although the `2 ** 2` creates a *new* object `4` in memory that is then immediately passed into the [math.sqrt()](https://docs.python.org/3/library/math.html#math.sqrt) function, once that function call returns, \"all is lost\" and the newly created `4` object is forgotten again, as well as the return value of [math.sqrt()](https://docs.python.org/3/library/math.html#math.sqrt)." "So, regarding the very next cell in particular: Although the `2 ** 2` creates a *new* object `4` in memory that is then immediately passed into the [math.sqrt()](https://docs.python.org/3/library/math.html#math.sqrt) function, once that function call returns, \"all is lost\" and the newly created `4` object is forgotten again, as well as the return value of [math.sqrt()](https://docs.python.org/3/library/math.html#math.sqrt)."
] ]
@ -2649,7 +2697,7 @@
{ {
"data": { "data": {
"text/plain": [ "text/plain": [
"0.12717011866176486" "0.15268128055183228"
] ]
}, },
"execution_count": 75, "execution_count": 75,
@ -2684,7 +2732,7 @@
{ {
"data": { "data": {
"text/plain": [ "text/plain": [
"<bound method Random.choice of <random.Random object at 0x562161984ba8>>" "<bound method Random.choice of <random.Random object at 0x56111ba1cba8>>"
] ]
}, },
"execution_count": 76, "execution_count": 76,
@ -2733,7 +2781,7 @@
{ {
"data": { "data": {
"text/plain": [ "text/plain": [
"8" "2"
] ]
}, },
"execution_count": 78, "execution_count": 78,
@ -2881,7 +2929,7 @@
"source": [ "source": [
"[numpy](http://www.numpy.org/) is the de-facto standard in the Python world for handling **array-like** data. That is a fancy word for data that can be put into a matrix or vector format. We will look at it in depth in Chapter 9.\n", "[numpy](http://www.numpy.org/) is the de-facto standard in the Python world for handling **array-like** data. That is a fancy word for data that can be put into a matrix or vector format. We will look at it in depth in Chapter 9.\n",
"\n", "\n",
"As [numpy](http://www.numpy.org/) is *not* in the [standard library](https://docs.python.org/3/library/index.html), it must be *manually* installed, for example, with the [pip](https://pip.pypa.io/en/stable/) tool. As mentioned in Chapter 0, to execute terminal commands from within a Jupyter notebook, we just need to start a code cell with an exclamation mark.\n", "As [numpy](http://www.numpy.org/) is *not* in the [standard library](https://docs.python.org/3/library/index.html), it must be *manually* installed, for example, with the [pip](https://pip.pypa.io/en/stable/) tool. As mentioned in [Chapter 0](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/00_start_up.ipynb), to execute terminal commands from within a Jupyter notebook, we just need to start a code cell with an exclamation mark.\n",
"\n", "\n",
"If you are running this notebook with an installation of the [Anaconda Distribution](https://www.anaconda.com/distribution/), then [numpy](http://www.numpy.org/) is probably already installed. Running the cell below, will just confirm that." "If you are running this notebook with an installation of the [Anaconda Distribution](https://www.anaconda.com/distribution/), then [numpy](http://www.numpy.org/) is probably already installed. Running the cell below, will just confirm that."
] ]

View file

@ -18,7 +18,7 @@
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"Read Chapter 2 of the book. Then work through the ten review questions." "Read [Chapter 2](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/02_functions.ipynb) of the book. Then work through the ten review questions."
] ]
}, },
{ {

View file

@ -19,7 +19,7 @@
} }
}, },
"source": [ "source": [
"We analyzed every aspect of the `average_evens()` function in Chapter 2 except for the `if` part. While it seems to intuitively do what we expect it to, there is a whole lot more to be learned from taking it apart. In particular, the `if` can occur within both a **statement** as in our introductory example in Chapter 1 but also an **expression** as in `average_evens()`. This is 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 versions of the `if` 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` part. While it seems to intuitively do what we expect it to, there is a whole lot more to be learned from taking it apart. In particular, the `if` can occur within both a **statement** as in our introductory example in [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb) but also an **expression** as in `average_evens()`. This is 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 versions of the `if` 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", "\n",
"After deconstructing `if` in the first part of this chapter, we take a close look at a similar concept, namely handling and raising **exceptions**." "After deconstructing `if` in the first part of this chapter, we take a close look at a similar concept, namely handling and raising **exceptions**."
] ]
@ -139,7 +139,7 @@
} }
}, },
"source": [ "source": [
"There are, however, cases where even well-behaved Python does not make us happy. Chapter 5 will provide more insights on that." "There are, however, cases where even well-behaved Python does not make us happy. Chapter 5 will provide more insights on this \"bug\"."
] ]
}, },
{ {
@ -189,7 +189,7 @@
{ {
"data": { "data": {
"text/plain": [ "text/plain": [
"94709180875744" "94163040564192"
] ]
}, },
"execution_count": 5, "execution_count": 5,
@ -213,7 +213,7 @@
{ {
"data": { "data": {
"text/plain": [ "text/plain": [
"94709180875712" "94163040564160"
] ]
}, },
"execution_count": 6, "execution_count": 6,
@ -281,9 +281,9 @@
} }
}, },
"source": [ "source": [
"Let's not confuse the boolean `False` with `None`, another special built-in object! We saw the latter before in Chapter 2 as the *implicit* return value of a function without a `return` statement.\n", "Let's not confuse the boolean `False` with `None`, another special built-in object! We saw the latter before in [Chapter 2](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/02_functions.ipynb) as the *implicit* return value of a function without a `return` statement.\n",
"\n", "\n",
"We might think of `None` in a boolean context indicating a \"maybe\" or even an \"unknown\" answer. But for Python, there are no \"maybe\" or \"unknown\" objects as we will see further below!\n", "We might think of `None` in a boolean context indicating a \"maybe\" or even an \"unknown\" answer; however, for Python, there are no \"maybe\" or \"unknown\" objects as we will see further below!\n",
"\n", "\n",
"Whereas `False` is of type `bool`, `None` is of type `NoneType`. So, they are totally unrelated. On the contrary, as both `True` and `False` are of the same type, we could call them \"siblings\"." "Whereas `False` is of type `bool`, `None` is of type `NoneType`. So, they are totally unrelated. On the contrary, as both `True` and `False` are of the same type, we could call them \"siblings\"."
] ]
@ -313,7 +313,7 @@
{ {
"data": { "data": {
"text/plain": [ "text/plain": [
"94709180862704" "94163040551152"
] ]
}, },
"execution_count": 10, "execution_count": 10,
@ -357,7 +357,7 @@
} }
}, },
"source": [ "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 will never mutate its value in place (i.e., to re-use the bag analogy from Chapter 1, 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 already 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 will *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), 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 already built in for *some* types.\n",
"\n", "\n",
"We can verify this with either the `is` operator or by comparing memory addresses." "We can verify this with either the `is` operator or by comparing memory addresses."
] ]
@ -512,7 +512,7 @@
} }
], ],
"source": [ "source": [
"42 != 123 # = \"not equal to\"; other programming languages sometimes use \"<>\" instead" "42 != 123 # = \"not equal to\"; other languages may use \"<>\""
] ]
}, },
{ {
@ -672,7 +672,7 @@
} }
}, },
"source": [ "source": [
"Relational operators have a *higher precedence* over logical operators (cf., the [reference](https://docs.python.org/3/reference/expressions.html#operator-precedence)). So the following expression means what we intuitively think it does." "Relational operators have a **[higher precedence](https://docs.python.org/3/reference/expressions.html#operator-precedence)** over logical operators. So the following expression means what we intuitively think it does."
] ]
}, },
{ {
@ -825,7 +825,7 @@
} }
}, },
"source": [ "source": [
"For even better readability, [some practitioner](https://llewellynfalco.blogspot.com/2016/02/dont-use-greater-than-sign-in.html) suggest to never use the `>` and `>=` operators (note that the included example is written in [Java](https://en.wikipedia.org/wiki/Java_%28programming_language%29) and `&&` means `and` and `||` means `or`).\n", "For even better readability, [some practitioners](https://llewellynfalco.blogspot.com/2016/02/dont-use-greater-than-sign-in.html) suggest to *never* use the `>` and `>=` operators (note that the included example is written in [Java](https://en.wikipedia.org/wiki/Java_%28programming_language%29) and `&&` means `and` and `||` means `or`).\n",
"\n", "\n",
"Python allows **chaining** relational operators that are combined with the `and` operator. For example, the following two cells implement the same logic where the second is a lot easier to read." "Python allows **chaining** relational operators that are combined with the `and` operator. For example, the following two cells implement the same logic where the second is a lot easier to read."
] ]
@ -899,7 +899,7 @@
"source": [ "source": [
"The operands of the logical operators do not actually have to be *boolean* expressions as defined above but may be *any* kind of expression. If a sub-expression does *not* evaluate to an object of type `bool`, Python automatically casts the resulting object as such.\n", "The operands of the logical operators do not actually have to be *boolean* expressions as defined above but may be *any* kind of expression. If a sub-expression does *not* evaluate to an object of type `bool`, Python automatically casts the resulting object as such.\n",
"\n", "\n",
"For example, any non-zero numeric object effectively becomes `True`. While this behavior allows writing more concise and thus \"beautiful\" code, it is also a common source of confusion." "For example, any non-zero numeric object becomes `True`. While this behavior allows writing conciser and thus more \"beautiful\" code, it is also a common source of confusion."
] ]
}, },
{ {
@ -1028,7 +1028,7 @@
} }
}, },
"source": [ "source": [
"In a boolean context `None` is casted as `False`. So, `None` is really *not* a \"maybe\" answer but a \"no\"." "In a boolean context, `None` is casted as `False`! So, `None` is really *not* a \"maybe\" answer but a \"no\"."
] ]
}, },
{ {
@ -1144,9 +1144,9 @@
} }
}, },
"source": [ "source": [
"In order to write useful programs, we need to control the flow of execution, for example, to react to user input.\n", "In order to write useful programs, we need to control the flow of execution, for example, to react to user input. The logic by which a program does that is referred to as **business logic**.\n",
"\n", "\n",
"One major language construct to do so is the **conditional statement** or `if` **statement** (cf., the [reference](https://docs.python.org/3/reference/compound_stmts.html#the-if-statement)). It consists of:\n", "One major language construct to do so is the **[conditional statement](https://docs.python.org/3/reference/compound_stmts.html#the-if-statement)** or `if` statement. It consists of:\n",
"\n", "\n",
"- *one* mandatory `if`-clause,\n", "- *one* mandatory `if`-clause,\n",
"- an *arbitrary* number of `elif`-clauses (i.e. \"else if\"), and\n", "- an *arbitrary* number of `elif`-clauses (i.e. \"else if\"), and\n",
@ -1350,7 +1350,7 @@
"name": "stdout", "name": "stdout",
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"z is positive\n" "z is odd\n"
] ]
} }
], ],
@ -1602,7 +1602,7 @@
"source": [ "source": [
"In the previous two chapters we already encountered a couple of *runtime* errors. A natural urge we might have after reading about conditional statements is to write code that somehow reacts to the occurence of such exceptions. All we need for that is a way to formulate a condition for that.\n", "In the previous two chapters we already encountered a couple of *runtime* errors. A natural urge we might have after reading about conditional statements is to write code that somehow reacts to the occurence of such exceptions. All we need for that is a way to formulate a condition for that.\n",
"\n", "\n",
"For sure, this is such a common thing to do that Python provides its own language construct for it, namely the `try` statement (cf., the [reference](https://docs.python.org/3/reference/compound_stmts.html#the-try-statement)).\n", "For sure, this is such a common thing to do that Python provides its own language construct for it, namely the `try` [statement](https://docs.python.org/3/reference/compound_stmts.html#the-try-statement).\n",
"\n", "\n",
"In its simplest form, it comes with just two branches: `try` and `except`. The following basically tells Python to execute the code in the `try`-branch and if *anything* goes wrong, continue in the `except`-branch instead of **raising** an error to us. Of course, if nothing goes wrong, the `except`-branch is *not* executed." "In its simplest form, it comes with just two branches: `try` and `except`. The following basically tells Python to execute the code in the `try`-branch and if *anything* goes wrong, continue in the `except`-branch instead of **raising** an error to us. Of course, if nothing goes wrong, the `except`-branch is *not* executed."
] ]
@ -1711,7 +1711,7 @@
"name": "stdout", "name": "stdout",
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"Yes, division worked smoothly.\n", "Oops. Division by 0. How does that work?\n",
"I am always printed\n" "I am always printed\n"
] ]
} }
@ -1748,7 +1748,7 @@
} }
}, },
"source": [ "source": [
"- **boolean expressions** evaluate either to `True` or `False`\n", "- **boolean expressions** evaluate to either `True` or `False`\n",
"- **relational operators** compare operands according to \"human\" interpretations\n", "- **relational operators** compare operands according to \"human\" interpretations\n",
"- **logical operators** combine boolean sub-expressions to more \"complex\" expressions\n", "- **logical operators** combine boolean sub-expressions to more \"complex\" expressions\n",
"- the **conditional statement** is a *major* concept to **control** the **flow of execution** depending on some **conditions**\n", "- the **conditional statement** is a *major* concept to **control** the **flow of execution** depending on some **conditions**\n",

View file

@ -19,7 +19,7 @@
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"Read Chapter 3 of the book. Then work through the seven review questions." "Read [Chapter 3](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/03_conditionals.ipynb) of the book. Then work through the seven review questions."
] ]
}, },
{ {
@ -211,11 +211,6 @@
"\n", "\n",
"\n", "\n",
"\n", "\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n" "\n"
] ]
}, },
@ -235,28 +230,36 @@
"execution_count": null, "execution_count": null,
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [] "source": [
"discounted_price(...)"
]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": null, "execution_count": null,
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [] "source": [
"discounted_price(...)"
]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": null, "execution_count": null,
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [] "source": [
"discounted_price(...)"
]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": null, "execution_count": null,
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [] "source": [
"discounted_price(...)"
]
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
@ -270,14 +273,18 @@
"execution_count": null, "execution_count": null,
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [] "source": [
"discounted_price(...)"
]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": null, "execution_count": null,
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [] "source": [
"discounted_price(...)"
]
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
@ -304,14 +311,23 @@
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"### Fizz Buzz revisited" "### Fizz Buzz"
] ]
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"When you worked on the Fizz Buzz exercise in Chapter 1, you actually did not know about the `elif` and `else` keywords yet. Well, now you do." "The kids game [Fizz Buzz](https://en.wikipedia.org/wiki/Fizz_buzz) is said to be often used in job interviews for entry level positions. However, opinions vary as to how good of a test it actually is ([source](https://news.ycombinator.com/item?id=16446774)).\n",
"\n",
"In its simplest form, a group of people start counting upwards in an alternating fashion. Whenever a number is divisible by $3$, the person must say \"Fizz\" instead of the number. The same holds for numbers divisible by $5$ when the person must say \"Buzz\". If a number is divisible by both numbers, one must say \"FizzBuzz\". Probably, this game would also make a good drinking game with the \"right\" beverages."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q9.1**: First, create a list `numbers` with the numbers from 1 through 100. You could type all numbers manually but there is of course a smarter way. The built-in [range()](https://docs.python.org/3/library/functions.html#func-range) may be useful here. Read how it works in the documentation. To make the output of [range()](https://docs.python.org/3/library/functions.html#func-range) a `list` object, you have to wrap it with the [list()](https://docs.python.org/3/library/functions.html#func-list) built-in (i.e., `list(range(...))`)."
] ]
}, },
{ {
@ -320,16 +336,20 @@
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
"numbers = list(range(1, 101))" "numbers = ..."
] ]
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"**Q9**: Copy and paste your answer to **Q11.2** in Chapter 1 here and instead of three consecutive `if` statements re-write it with *one* compound `if` statement.\n", "**Q9.2**: Loop over the `numbers` list and replace numbers for which one of the two (or both) conditions apply with text strings `\"Fizz\"`, `\"Buzz\"`, or `\"FizzBuzz\"` using the indexing operator `[]` and the assignment statement `=`.\n",
"\n", "\n",
"This code will then be a lot more robust as the order of the three `if` statements cannot be screwed up." "In Chapter 1 we saw that Python starts indexing with `0` as the first element. Keep that in mind.\n",
"\n",
"So in each iteration of the `for`-loop you have to determine an `index` variable as well as checking the actual `number` for its divisors.\n",
"\n",
"Hint: the order of the conditions is important!"
] ]
}, },
{ {
@ -338,17 +358,31 @@
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
"\n", "for number in numbers:\n",
"\n", " ...\n",
"\n", " ...\n",
"\n", " ...\n",
"\n", " ...\n",
"\n", " ...\n",
"\n", " ...\n",
"\n", " ..."
"\n", ]
"\n", },
"\n" {
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q9.3**: Create a loop that prints out either the number or any of the Fizz Buzz substitutes. Do it in such a way that we do not end up with 100 lines of output here."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"for number in numbers:\n",
" print(...)"
] ]
} }
], ],

6529
04_iteration.ipynb Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,941 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"# Chapter 4: Iteration"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Content Review"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Read [Chapter 4](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/04_iteration.ipynb) of the book. Then work through the fourteen review questions."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Essay Questions "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Answer the following questions briefly with *at most* 300 characters per question!"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q1**: What is so \"special\" about the number **7919**?"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
" "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q2**: Solving a problem with a **recursion** is not only popular in computer science and math. Name some examples from the fields of business or economics where problems are also solved in a **backwards** fashion!"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
" "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q3**: Explain what **duck typing** means! Why can it cause problems? Why it is [not a bug but a feature](https://www.urbandictionary.com/define.php?term=It%27s%20not%20a%20bug%2C%20it%27s%20a%20feature)?"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
" "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q4**: What is **syntactic sugar**?"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
" "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q5**: Describe in your own words why the **recursive** version of `fibonacci()`, the \"Easy at first Glance\" example in the chapter, is computationally **inefficient**! Why does the **iterative** version of `fibonacci()`, the \"Hard at first Glance\" example, run so much faster?"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
" "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q6**: What is the conceptual difference between a **container** and a **list**?"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
" "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q7**: What is a good use case for the `for`-loop's optional `else`-clause?"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
" "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### True / False Questions"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Motivate your answer with *one short* sentence!"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q8**: When a **recursion** does **not** reach the base case, this is an example of the **early exit** strategy."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
" "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q9**: Any programming language **without** looping constructs like the `for` or `while` statements is **not** Turing complete."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
" "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q10**: A **recursive** formulation is the same as a **circular** one: The terms are **synonyms**."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
" "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q11**: Formulating a computational problem as a **recursion** results in an **efficient** implementation."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
" "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q12**: Whereas a **recursion** may accidently result in a **never ending** program, `while`-loops and `for`-loops are guaranteed to **terminate**."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
" "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q13**: Before writing **any** kind of **loop**, we **always** need to think about a **stopping criterion** ahead of time."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
" "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q14**: **Container** types such as `list` objects are characterized by their **support** for **being looped over**, for example as in:"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"```python\n",
"for element in container:\n",
" # do something for every element\n",
" ...\n",
"```"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
" "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Coding Exercises"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Towers of Hanoi"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"A popular example for a problem that is solved with a recursion art the **[Towers of Hanoi](https://en.wikipedia.org/wiki/Tower_of_Hanoi)**.\n",
"\n",
"In its basic version, a tower consisting of, for example, four disks with increasing radii, is placed on the left-most of **three** adjacent spots. In the following, we refer to the number of disks as $n$, so here $n = 4$.\n",
"\n",
"The task is to move the entire tower to the right-most spot whereby **two rules** must be obeyed:\n",
"\n",
"1. Disks can only be moved individually, and\n",
"2. a disk with a larger radius must *never* be placed on a disk with a smaller one.\n",
"\n",
"Although the **[Towers of Hanoi](https://en.wikipedia.org/wiki/Tower_of_Hanoi)** are a **classic** example, introduced by the mathematician [Édouard Lucas](https://en.wikipedia.org/wiki/%C3%89douard_Lucas) already in 1883, it is still **actively** researched as this scholarly [article](https://www.worldscientific.com/doi/abs/10.1142/S1793830919300017?journalCode=dmaa&) published in January 2019 shows.\n",
"\n",
"Despite being so easy to formulate, the game is quite hard to solve.\n",
"\n",
"Below is an interactive illustration of the solution with the minimal number of moves for $n = 4$."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<img src=\"static/towers_of_hanoi.gif\" width=\"60%\" align=\"left\">"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Watch the following video by [MIT](https://www.mit.edu/)'s professor [Richard Larson](https://idss.mit.edu/staff/richard-larson/) for a comprehensive introduction.\n",
"\n",
"The [MIT Blossoms Initative](https://blossoms.mit.edu/) is primarily aimed at high school students and does not have any prerequisites.\n",
"\n",
"The video consists of three segments the last of which is *not* necessary to have watched in order to solve the tasks below. So, watch the video until 37:55."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from IPython.display import YouTubeVideo\n",
"YouTubeVideo(\"UuIneNBbscc\", width=\"60%\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Video Review Questions"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q15.1**: Explain for the $n = 3$ case why it can be solved as a **recursion**!"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
" "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q15.2**: How does the number of minimal moves needed to solve a problem with three spots and $n$ disks grow as a function of $n$? How does this relate to the answer to **Q15.1**?"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
" "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q15.3**: The **[Towers of Hanoi](https://en.wikipedia.org/wiki/Tower_of_Hanoi)** problem is of **exponential growth**. What does that mean? What does that imply for large $n$?"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
" "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q15.4**: The video introduces the recursive relationship $Sol(4, 1, 3) = Sol(3, 1, 2) ~ \\bigoplus ~ Sol(1, 1, 3) ~ \\bigoplus ~ Sol(3, 2, 3)$. The $\\bigoplus$ is to be interpreted as some sort of \"plus\" operation. How does this \"plus\" operation work? How does this way of expressing the problem relate to the answer to **Q15.1**?"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
" "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Naive Translation to Python"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"As most likely the first couple of tries will result in *semantic* errors, it is advisable to have some sort of **visualization tool** for the progam's output: For example, an online version of the game can be found **[here](https://www.mathsisfun.com/games/towerofhanoi.html)**."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let's first **generalize** the mathematical relationship from above.\n",
"\n",
"While the first number of $Sol(\\cdot)$ is the number of `disks` $n$, the second and third \"numbers\" are actually the **labels** for the three spots. Instead of spots `1`, `2`, and `3` we could also call them `\"left\"`, `\"center\"`, and `\"right\"` in our Python implementation. When \"passed\" to the $Sol(\\cdot)$ \"function\" they take on the role of an `origin` (= $o$) and `destination` (= $d$) pair.\n",
"\n",
"So, the expression $Sol(4, 1, 3)$ is the same as $Sol(4, \\text{\"left\"}, \\text{\"right\"})$ and describes the problem of moving a tower consisting of $n = 4$ disks from `origin` `1` / `\"left\"` to `destination` `3` / `right`. As we have seen in the video, we need some `intermediate` (= $i$) spot."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"In summary, the generalized functional relationship can be expressed as:\n",
"\n",
"$Sol(n, o, d) = Sol(n-1, o, i) ~ \\bigoplus ~ Sol(1, o, d) ~ \\bigoplus ~ Sol(n-1, i, d)$"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"In words, this means that in order to move a tower consisting of $n$ disks from an `origin` $o$ to a `destination` $d$, we three steps must be executed:\n",
"\n",
"1. Move the top most $n - 1$ disks of the tower temporarily from $o$ to $i$ (= sub-problem 1)\n",
"2. Move the remaining and largest disk from $o$ to $d$\n",
"3. Move the the $n - 1$ disks from the temporary spot $i$ to $d$ (= sub-problem 2)\n",
"\n",
"The two sub-problems can be solved via the same recursive logic."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"$Sol(\\cdot)$ can be written in Python as a function `sol()` that takes three arguments `disks`, `origin`, and `destination` that mirror $n$, $o$, and $d$.\n",
"\n",
"Assume that all arguments to `sol()` will be `int` objects!\n",
"\n",
"Once completed, `sol()` should print out all the moves in the correct order. With **printing a move**, we simply mean a line like \"1 -> 3\", short for \"Move the top-most disk from spot 1 to spot 3\".\n",
"\n",
"Write your answers to **Q15.5** to **Q15.7** into the subsequent code cell and finalize `sol()`! No need to write a docstring or validate the input here."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def sol(disks, origin, destination):\n",
"\n",
" # answer to Q15.5\n",
" # ...\n",
"\n",
" # answer to Q15.6\n",
" # ...\n",
" # ...\n",
" # ...\n",
" # ...\n",
" # ...\n",
" # ...\n",
" # ...\n",
" # ...\n",
" # ...\n",
" # ...\n",
" # ...\n",
"\n",
" # answer to Q15.7\n",
" # ...\n",
" # ..."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q15.5**: What is the `disks` argument when the function reaches its **base case**? Check for the base case with a simple `if` statement and return from the function using the **early exit** pattern!"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q15.6**: If not in the base case, `sol()` needs to determine the `intermediate` spot given concrete `origin` and `destination` arguments. For example, if called with `origin=1` and `destination=2`, `intermediate` must be `3`.\n",
"\n",
"Add **one** compound `if` statement to `sol()` that has a branch for **every** possible `origin`-`destination` pair that sets a variable `intermediate` to the correct temporary spot. **How many** branches will there be?"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q15.7**: `sol()` needs to call itself **two more times** with the correct 2-pairs chosen from the three available spots `origin`, `intermediate`, and `destination`.\n",
"\n",
"In between the two recursive function calls, write a `print()` statement that prints out from where to where the \"remaining and largest\" disk has to be moved!"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q15.8**: Execute the code cells below and confirm that the printed moves are correct!"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"sol(1, 1, 3)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"sol(2, 1, 3)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"sol(3, 1, 3)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"sol(4, 1, 3)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Pythonic Re-Factoring"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The previous `sol()` implementation does the job but the conditional statement needed in unnecessarily tedious. \n",
"\n",
"Let's create a more concise `hanoi()` function that in addition to a positional `disks` argument takes three keyword-only arguments `origin`, `intermediate`, and `destination` with default values `\"left\"`, `\"center\"`, and `\"right\"`.\n",
"\n",
"Write your answers to **Q15.9** and **Q15.10** into the subsequent code cell and finalize `hanoi()`! No need to write a docstring or validate the input here."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def hanoi(disks, *, origin=\"left\", intermediate=\"center\", destination=\"right\"):\n",
"\n",
" # answer to Q15.9\n",
" # ...\n",
"\n",
" # answer to Q15.10\n",
" # ...\n",
" # ..."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q15.9**: Copy the base case from `sol()`."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q15.10**: Instead of conditional logic, `hanoi()` calls itself **two times** with the **three** arguments `origin`, `intermediate`, and `destination` passed on in a **different** order.\n",
"\n",
"Figure out how the arguments are passed on in the two recursive `hanoi()` calls!\n",
"\n",
"Also, write a `print()` statement analogous to the one in `sol()` in between the two recursive function calls. Is it ok to just copy and paste it?"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q15.11**: Execute the code cells below and confirm that the printed moves are correct!"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"hanoi(1)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"hanoi(2)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"hanoi(3)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"hanoi(4)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We could of course also use **numeric labels** for the three steps like so."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"hanoi(3, origin=1, intermediate=2, destination=3)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Passing a Value \"up\" the Recursion Tree"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let's say, we did not know about the **analytical formula** for the number of **minimal moves** given $n$.\n",
"\n",
"In such cases, we could modify a recursive function to return a count value to be passed up the recursion tree.\n",
"\n",
"In fact, this is similar to what we do in the recursive versions of `factorial()` and `fibonacci()` in [Chapter 4](https://github.com/webartifex/intro-to-python/blob/master/04_iteration.ipynb) where we pass up an intermediate result.\n",
"\n",
"Let's create a `hanoi_moves()` function that follows the same internal logic as `hanoi()` but instead of printing out the moves returns the number of steps done so far in the recursion.\n",
"\n",
"Write your answers to **Q15.12** to **Q15.14** into the subsequent code cell and finalize `hanoi_moves()`! No need to write a docstring or validate the input here."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def hanoi_moves(disks, *, origin=\"left\", intermediate=\"center\", destination=\"right\"):\n",
"\n",
" # answer to Q15.12\n",
" # ...\n",
"\n",
" moves = ... # <- answer to Q15.13\n",
" moves += hanoi_moves(...) # <- answer to Q15.14 between the ()\n",
" moves += hanoi_moves(...) # <- answer to Q15.14 between the ()\n",
"\n",
" return moves"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q15.12**: Copy the base case from `hanoi()`! What count should be returned when it is reached?"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q15.13**: Initialize the variable `moves` with an appropriate count! This is the number of moves that corresponds to **one** recursive function call."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q15.14**: `moves` is updated with the counts passed up from the two recursive calls.\n",
"\n",
"Complete the two recursive function calls with the same arguments as in `hanoi()`!"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q15.15**: Write a `for`-loop that prints out the **minimum number** of moves needed to solve Towers of Hanoi for any number of `disks` from `1` through `20` to confirm your answer to **Q15.2**."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"##### Time Complexity"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Observe how quickly the `hanoi_moves()` function slows down for increasing `disks` arguments.\n",
"\n",
"With `disks` in the range from `24` through `26` the computation time roughly doubles for each increase of `disks` by 1.\n",
"\n",
"**Q15.16**: Execute the code cells below and see for yourself!"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"%%timeit -n 1 -r 1\n",
"print(\"Number of moves:\", hanoi_moves(24))"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"%%timeit -n 1 -r 1\n",
"print(\"Number of moves:\", hanoi_moves(25))"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"%%timeit -n 1 -r 1\n",
"print(\"Number of moves:\", hanoi_moves(26))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Passing a Value \"down\" the Recursion Tree (Advanced)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The above `hanoi()` prints the optimal solution's moves in the correct order but fails to label each move with an order number. This can be build in by passing on one more argument `offset` down the recursion tree. As the logic gets a bit \"involved\", `hanoi_ordered()` below is almost finished.\n",
"\n",
"Write your answers to **Q15.17** and **Q15.18** into the subsequent code cell and finalize `hanoi_ordered()`! No need to write a docstring or validate the input here."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def hanoi_ordered(disks, *, origin=\"left\", intermediate=\"center\", destination=\"right\", offset=None):\n",
"\n",
" # answer to Q15.17\n",
" # ...\n",
"\n",
" total = (2 ** disks - 1)\n",
" half = (2 ** (disks - 1) - 1)\n",
" count = total - half\n",
"\n",
" if offset is not None:\n",
" count += offset\n",
"\n",
" hanoi_ordered(..., offset=offset) # <- answer to Q15.18 between the ()\n",
" # answer to Q15.18\n",
" hanoi_ordered(..., offset=count) # <- answer to Q15.18 between the ()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q15.17**: Copy the base case from the original `hanoi()`!"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q15.18**: Complete the two recursive function calls with the same arguments as in `hanoi()` or `hanoi_moves()`! Do not change the already filled in `offset` arguments!\n",
"\n",
"Then, copy the `print()` statement from `hanoi()` and adjust it to print out `count` as well!"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q15.19**: Execute the code cells below and confirm that the order numbers are correct!"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"hanoi_ordered(1)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"hanoi_ordered(2)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"hanoi_ordered(3)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"hanoi_ordered(4)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Lastly, it is to be mentioned that for problem instances with a small `disks` argument it is easier to collect all the moves first in a list and then add the order number with the [enumerate()](https://docs.python.org/3/library/functions.html#enumerate) built-in."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Open Question"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q15.20**: Conducting your own research on the internet (max. 15 minutes), what can you say about generalizing the **[Towers of Hanoi](https://en.wikipedia.org/wiki/Tower_of_Hanoi)** problem to a setting with **more than three** landing spots?"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
" "
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.3"
},
"toc": {
"base_numbering": 1,
"nav_menu": {},
"number_sections": false,
"sideBar": true,
"skip_h1_title": true,
"title_cell": "Table of Contents",
"title_sidebar": "Contents",
"toc_cell": false,
"toc_position": {},
"toc_section_display": false,
"toc_window_display": false
}
},
"nbformat": 4,
"nbformat_minor": 2
}

View file

@ -18,6 +18,7 @@ As such they can be viewed in a plain web browser:
- [01 - Elements of a Program](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb) - [01 - Elements of a Program](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb)
- [02 - Functions & Modularization](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/02_functions.ipynb) - [02 - Functions & Modularization](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/02_functions.ipynb)
- [03 - Conditionals & Exceptions](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/03_conditionals.ipynb) - [03 - Conditionals & Exceptions](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/03_conditionals.ipynb)
- [04 - Recursion & Looping](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/04_iteration.ipynb)
However, it is recommended that students **install Python and Jupyter However, it is recommended that students **install Python and Jupyter
locally** and run the code in the notebooks on their own. locally** and run the code in the notebooks on their own.

BIN
static/towers_of_hanoi.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 KiB