{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"cell_style": "center",
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"# Generator"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "-"
},
"tags": [
"remove-cell"
]
},
"source": [
"**CS1302 Introduction to Computer Programming**\n",
"___"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"ExecuteTime": {
"end_time": "2020-11-27T11:20:04.656873Z",
"start_time": "2020-11-27T11:20:04.651575Z"
},
"slideshow": {
"slide_type": "fragment"
},
"tags": [
"remove-cell"
]
},
"outputs": [],
"source": [
"%reload_ext mytutor"
]
},
{
"cell_type": "markdown",
"metadata": {
"hide_input": true,
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## Recursion"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"Consider computing the [Fibonacci number](https://en.wikipedia.org/wiki/Fibonacci_number) of order $n$:\n",
"\n",
"$$\n",
"F_n := \n",
"\\begin{cases}\n",
"F_{n-1}+F_{n-2} & n>1 \\kern1em \\text{(recurrence)}\\\\\n",
"1 & n=1 \\kern1em \\text{(base case)}\\\\\n",
"0 & n=0 \\kern1em \\text{(base case)}.\n",
"\\end{cases}$$\n",
"Fibonacci numbers have practical applications in generating [pseudorandom numbers](https://en.wikipedia.org/wiki/Lagged_Fibonacci_generator)."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"**Can we define the function by calling the function itself?**"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Try stepping through such a function below to see how it works:"
]
},
{
"cell_type": "code",
"execution_count": 23,
"metadata": {
"ExecuteTime": {
"end_time": "2020-10-31T02:43:46.140516Z",
"start_time": "2020-10-31T02:43:46.133048Z"
},
"code_folding": [],
"slideshow": {
"slide_type": "fragment"
},
"tags": []
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"1\n"
]
},
{
"data": {
"text/html": [
"\n",
" \n",
" "
],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"%%mytutor -r -h 450\n",
"def fibonacci(n):\n",
" if n > 1:\n",
" return fibonacci(n - 1) + fibonacci(n - 2) # recursion\n",
" elif n == 1:\n",
" return 1\n",
" else:\n",
" return 0\n",
"\n",
"\n",
"print(fibonacci(2))"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"A function that calls itself (*recurs*) is called a [*recursion*](https://en.wikipedia.org/wiki/Recursion_(computer_science))."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"**Exercise** Write a function `gcd` that implements the [Euclidean algorithm for the greatest common divisor](https://en.wikipedia.org/wiki/Euclidean_algorithm): \n",
"\n",
"$$\\operatorname{gcd}(a,b)=\\begin{cases}a & b=0\\\\ \\operatorname{gcd}(b, a\\operatorname{mod}b) & \\text{otherwise.} \\end{cases}$$"
]
},
{
"cell_type": "code",
"execution_count": 25,
"metadata": {
"ExecuteTime": {
"end_time": "2020-10-31T02:43:46.711458Z",
"start_time": "2020-10-31T02:43:46.694334Z"
},
"nbgrader": {
"grade": false,
"grade_id": "gcd",
"locked": false,
"schema_version": 3,
"solution": true,
"task": false
},
"slideshow": {
"slide_type": "-"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"5\n"
]
},
{
"data": {
"text/html": [
"\n",
" \n",
" "
],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"%%mytutor -r -h 550\n",
"def gcd(a, b):\n",
" ### BEGIN SOLUTION\n",
" return gcd(b, a % b) if b else a\n",
" ### END SOLUTION\n",
"\n",
"\n",
"print(gcd(3 * 5, 5 * 7)) # gcd = ?"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"**Is recursion strictly necessary?** "
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"E.g., the following computes the Fibonnacci number using a while loop instead."
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"ExecuteTime": {
"end_time": "2020-10-31T02:43:47.362797Z",
"start_time": "2020-10-31T02:43:47.353483Z"
},
"slideshow": {
"slide_type": "-"
}
},
"outputs": [
{
"data": {
"text/plain": [
"2"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
},
{
"data": {
"text/html": [
"\n",
" \n",
" "
],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"%%mytutor -r -h 550\n",
"def fibonacci_iteration(n):\n",
" if n > 1:\n",
" _, F = 0, 1 # next two Fibonacci numbers\n",
" while n > 1:\n",
" _, F, n = F, F + _, n - 1\n",
" return F\n",
" elif n == 1:\n",
" return 1\n",
" else:\n",
" return 0\n",
"\n",
"\n",
"fibonacci_iteration(3)"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"ExecuteTime": {
"end_time": "2020-10-31T02:43:47.372595Z",
"start_time": "2020-10-31T02:43:47.364331Z"
}
},
"outputs": [],
"source": [
"# more tests\n",
"for n in range(5):\n",
" assert fibonacci(n) == fibonacci_iteration(n)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"**Exercise** We can always convert a recursion to an iteration. Why?\n",
"\n",
"*Hint:* See the [Wikipedia](https://en.wikipedia.org/wiki/Recursion_(computer_science)#Recursion_versus_iteration)."
]
},
{
"cell_type": "markdown",
"metadata": {
"nbgrader": {
"grade": true,
"grade_id": "recursion-vs-iteration",
"locked": false,
"points": 0,
"schema_version": 3,
"solution": true,
"task": false
},
"slideshow": {
"slide_type": "-"
}
},
"source": [
"The step-by-step execution of the recursion is indeed how python implements a recursion as an iteration."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"**Exercise** Implement `gcd_iteration` using a while loop instead of a recursion.\n",
"\n",
"*Hint:* See [tail recursion](https://en.wikipedia.org/wiki/Recursion_(computer_science)#Tail-recursive_functions)."
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {
"ExecuteTime": {
"end_time": "2020-10-31T02:43:47.618077Z",
"start_time": "2020-10-31T02:43:47.608529Z"
},
"code_folding": [],
"nbgrader": {
"grade": false,
"grade_id": "gcd_iteration",
"locked": false,
"schema_version": 3,
"solution": true,
"task": false
},
"slideshow": {
"slide_type": "-"
}
},
"outputs": [
{
"data": {
"text/plain": [
"5"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
},
{
"data": {
"text/html": [
"\n",
" \n",
" "
],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"%%mytutor -r -h 550\n",
"def gcd_iteration(a, b):\n",
" ### BEGIN SOLUTION\n",
" while b:\n",
" a, b = b, a % b\n",
" return a\n",
" ### END SOLUTION\n",
"\n",
"\n",
"gcd_iteration(3 * 5, 5 * 7)"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {
"ExecuteTime": {
"end_time": "2020-10-31T02:43:47.626870Z",
"start_time": "2020-10-31T02:43:47.619853Z"
},
"slideshow": {
"slide_type": "fragment"
},
"tags": []
},
"outputs": [],
"source": [
"# test\n",
"for n in range(5):\n",
" assert fibonacci(n) == fibonacci_iteration(n)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"**What are the benefits of recursion?**"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"- Recursion is often shorter and easier to understand.\n",
"- It is also easier to write code by *wishful thinking* or *[declarative programming](https://en.wikipedia.org/wiki/Declarative_programming)* as supposed to [imperative programming](https://en.wikipedia.org/wiki/Imperative_programming)."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"**Is recusion more efficient than iteration?**"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"**Exercise** Find the smallest values of `n` for `fibonacci(n)` and `fibonacci_iteration(n)` respectively to run for more than a second."
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {
"ExecuteTime": {
"end_time": "2020-10-31T02:43:49.568032Z",
"start_time": "2020-10-31T02:43:48.577448Z"
},
"nbgrader": {
"grade": false,
"grade_id": "fib_recursion",
"locked": false,
"schema_version": 3,
"solution": true,
"task": false
},
"slideshow": {
"slide_type": "fragment"
},
"tags": [
"remove-output"
]
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"1.55 s ± 49.6 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n"
]
}
],
"source": [
"%%timeit -n 1\n",
"# Assign n the appropriate value\n",
"### BEGIN SOLUTION\n",
"n = 34\n",
"### END SOLUTION\n",
"fib_recursion = fibonacci(n)"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {
"ExecuteTime": {
"end_time": "2020-10-31T02:43:50.313458Z",
"start_time": "2020-10-31T02:43:49.569918Z"
},
"nbgrader": {
"grade": false,
"grade_id": "fib_iteration",
"locked": false,
"schema_version": 3,
"solution": true,
"task": false
},
"slideshow": {
"slide_type": "fragment"
},
"tags": [
"remove-output"
]
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"1.28 s ± 10.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n"
]
}
],
"source": [
"%%timeit -n 1\n",
"# Assign n\n",
"### BEGIN SOLUTION\n",
"n = 400000\n",
"### END SOLUTION\n",
"fib_iteration = fibonacci_iteration(n)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"To see why recursion is often slower, we will modify `fibonacci` to print each function call as follows."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"ExecuteTime": {
"end_time": "2020-10-31T02:43:50.324561Z",
"start_time": "2020-10-31T02:43:50.315516Z"
},
"slideshow": {
"slide_type": "-"
},
"tags": []
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"fibonacci(5)\n",
"fibonacci(4)\n",
"fibonacci(3)\n",
"fibonacci(2)\n",
"fibonacci(1)\n",
"fibonacci(0)\n",
"fibonacci(1)\n",
"fibonacci(2)\n",
"fibonacci(1)\n",
"fibonacci(0)\n",
"fibonacci(3)\n",
"fibonacci(2)\n",
"fibonacci(1)\n",
"fibonacci(0)\n",
"fibonacci(1)\n"
]
},
{
"data": {
"text/plain": [
"5"
]
},
"execution_count": 1,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"def fibonacci(n):\n",
" \"\"\"Returns the Fibonacci number of order n.\"\"\"\n",
" print(f\"fibonacci({n})\")\n",
" return fibonacci(n - 1) + fibonacci(n - 2) if n > 1 else 1 if n == 1 else 0\n",
"\n",
"\n",
"fibonacci(5)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"- `fibonacci(5)` calls `fibonacci(4)` and `fibonacci(3)`.\n",
"- `fibonacci(4)` then calls `fibonacci(3)` and `fibonacci(2)`.\n",
"- `fibonacci(3)` is called twice."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## Global Variables and Closures"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"Consider generating a sequence of Fibonacci numbers:"
]
},
{
"cell_type": "code",
"execution_count": 61,
"metadata": {
"ExecuteTime": {
"end_time": "2020-10-31T02:43:50.334349Z",
"start_time": "2020-10-31T02:43:50.326085Z"
},
"slideshow": {
"slide_type": "-"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"0\n",
"1\n",
"1\n",
"2\n",
"3\n"
]
}
],
"source": [
"for n in range(5):\n",
" print(fibonacci_iteration(n))"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"**Exercise** Is the above loop efficient?"
]
},
{
"cell_type": "markdown",
"metadata": {
"nbgrader": {
"grade": true,
"grade_id": "fibonacci_sequence",
"locked": false,
"points": 0,
"schema_version": 3,
"solution": true,
"task": false
},
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"No. Each call to `fibonacci_iteration(n)` recomputes the last two Fibonacci numbers $F_{n-1}$ and $F_{n-2}$ for $n\\geq 2$."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"**How to avoid redundant computations?**"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"One way is to store the last two computed Fibonacci numbers."
]
},
{
"cell_type": "code",
"execution_count": 66,
"metadata": {
"ExecuteTime": {
"end_time": "2020-10-31T02:43:50.712718Z",
"start_time": "2020-10-31T02:43:50.705710Z"
},
"code_folding": [],
"slideshow": {
"slide_type": "-"
},
"tags": []
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"0\n",
"1\n",
"1\n",
"2\n",
"3\n",
"Global states:\n",
" n : Next Fibonacci number = 5\n",
" Fnn : Next next Fibonacci number = 8\n",
" n : Next order = 5\n"
]
},
{
"data": {
"text/html": [
"\n",
" \n",
" "
],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"%%mytutor -r -h 600\n",
"Fn, Fnn, n = 0, 1, 0 # global variables\n",
"\n",
"\n",
"def print_fibonacci_state():\n",
" print(\n",
" f\"\"\"Global states:\n",
" Fn : Next Fibonacci number = {Fn}\n",
" Fnn : Next next Fibonacci number = {Fnn}\n",
" n : Next order = {n}\"\"\"\n",
" )\n",
"\n",
"\n",
"def next_fibonacci():\n",
" \"\"\"Returns the next Fibonacci number.\"\"\"\n",
" global Fn, Fnn, n # global declaration\n",
" value, Fn, Fnn, n = Fn, Fnn, Fn + Fnn, n + 1\n",
" return value\n",
"\n",
"\n",
"for i in range(5):\n",
" print(next_fibonacci())\n",
"print_fibonacci_state()"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"Rules for [*global/local variables*](https://docs.python.org/3/faq/programming.html#what-are-the-rules-for-local-and-global-variables-in-python):\n",
"1. A local variable must be defined within a function.\n",
"1. An assignment defines a local variable except after a [`global` statement](https://docs.python.org/3/reference/simple_stmts.html#the-global-statement)."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"**Why `global` is NOT needed in `print_fibonacci_state`?**"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"Without ambiguity, `Fn, Fnn, n` in `print_fibonacci_state` are not local variables by Rule 1 because they are not defined within the function."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"**Why `global` is needed in `next_fibonacci`?**"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"What happens otherwise:"
]
},
{
"cell_type": "code",
"execution_count": 65,
"metadata": {
"ExecuteTime": {
"end_time": "2020-10-31T02:43:52.064075Z",
"start_time": "2020-10-31T02:43:52.022388Z"
},
"slideshow": {
"slide_type": "-"
},
"tags": []
},
"outputs": [
{
"ename": "UnboundLocalError",
"evalue": "local variable 'n' referenced before assignment",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mUnboundLocalError\u001b[0m Traceback (most recent call last)",
"\u001b[0;32m/tmp/ipykernel_4023/717954880.py\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 6\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mvalue\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 7\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 8\u001b[0;31m \u001b[0mnext_fibonacci\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[0;32m/tmp/ipykernel_4023/717954880.py\u001b[0m in \u001b[0;36mnext_fibonacci\u001b[0;34m()\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[0;34m'''Returns the next Fibonacci number.'''\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3\u001b[0m \u001b[0;31m# global Fn, Fnn, n\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 4\u001b[0;31m \u001b[0mvalue\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mn\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 5\u001b[0m \u001b[0mn\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mFnn\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mFnn\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0mFnn\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 6\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mvalue\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;31mUnboundLocalError\u001b[0m: local variable 'n' referenced before assignment"
]
}
],
"source": [
"def next_fibonacci():\n",
" \"\"\"Returns the next Fibonacci number.\"\"\"\n",
" # global Fn, Fnn, n\n",
" value = n\n",
" n, Fnn, n = Fnn, n + Fnn, n + 1\n",
" return value\n",
"\n",
"\n",
"next_fibonacci()"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"Why is there an `UnboundLocalError`?"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"- The assignment defines `n` as a local variable by Rule 2. \n",
"- However, the assignment requires first evaluating `n`, which is not yet defined."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"**Are global variables preferred over local ones?**"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"Consider rewriting the for loop as a while loop:"
]
},
{
"cell_type": "code",
"execution_count": 70,
"metadata": {
"ExecuteTime": {
"end_time": "2020-10-31T02:43:53.266823Z",
"start_time": "2020-10-31T02:43:53.262288Z"
},
"code_folding": [],
"slideshow": {
"slide_type": "-"
},
"tags": []
},
"outputs": [
{
"data": {
"text/html": [
"\n",
" \n",
" "
],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"%%mytutor -h 600\n",
"Fn, Fnn, n = 0, 1, 0 # global variables\n",
"\n",
"\n",
"def print_fibonacci_state():\n",
" print(\n",
" f\"\"\"Global states:\n",
" Fn : Next Fibonacci number = {Fn}\n",
" Fnn : Next next Fibonacci number = {Fnn}\n",
" n : Next order = {n}\"\"\"\n",
" )\n",
"\n",
"\n",
"def next_fibonacci():\n",
" \"\"\"Returns the next Fibonacci number.\"\"\"\n",
" global Fn, Fnn, n # global declaration\n",
" value, Fn, Fnn, n = Fn, Fnn, Fn + Fnn, n + 1\n",
" return value\n",
"\n",
"\n",
"n = 0\n",
"while n < 5:\n",
" print(next_fibonacci())\n",
" n += 1\n",
"print_fibonacci_state()"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"**Exercise** Why does the while loop prints only 3 numbers instead of 5 Fibonacci numbers?"
]
},
{
"cell_type": "markdown",
"metadata": {
"nbgrader": {
"grade": true,
"grade_id": "global_bug",
"locked": false,
"points": 0,
"schema_version": 3,
"solution": true,
"task": false
},
"slideshow": {
"slide_type": "-"
}
},
"source": [
"There is a name collision. `n` is also incremented by `next_fibonacci()`, and so the while loop is only executed 3 times in total. "
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"To avoid such error, a convention in python is use a leading underscore for variable names that are [*private*](https://www.python.org/dev/peps/pep-0008) (for internal use): \n",
"> _single_leading_underscore: weak \"internal use\" indicator. E.g. from M import * does not import objects whose names start with an underscore."
]
},
{
"cell_type": "code",
"execution_count": 72,
"metadata": {
"slideshow": {
"slide_type": "-"
}
},
"outputs": [
{
"data": {
"text/html": [
"\n",
" \n",
" "
],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"%%mytutor -h 600\n",
"_Fn, _Fnn, _n = 0, 1, 0 # global variables\n",
"\n",
"\n",
"def print_fibonacci_state():\n",
" print(\n",
" f\"\"\"Global states:\n",
" _Fn : Next Fibonacci number = {_Fn}\n",
" _Fnn : Next next Fibonacci number = {_Fnn}\n",
" _n : Next order = {_n}\"\"\"\n",
" )\n",
"\n",
"\n",
"def next_fibonacci():\n",
" \"\"\"Returns the next Fibonacci number.\"\"\"\n",
" global _Fn, _Fnn, _n # global declaration\n",
" value, _Fn, _Fnn, _n = _Fn, _Fnn, _Fn + _Fnn, _n + 1\n",
" return value\n",
"\n",
"\n",
"n = 0\n",
"while n < 5:\n",
" print(next_fibonacci())\n",
" n += 1\n",
"print_fibonacci_state()"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"With global variables:\n",
"- codes are less predictable, more difficult to reuse/extend, and\n",
"- tests cannot be isolated, making debugging difficult."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"**Is it possible to store the function states without using global variables?**"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"We can use nested functions and [`nonlocal` variables](https://docs.python.org/3/reference/simple_stmts.html#grammar-token-nonlocal-stmt)."
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"ExecuteTime": {
"end_time": "2020-10-31T02:43:54.554619Z",
"start_time": "2020-10-31T02:43:54.548561Z"
},
"code_folding": [],
"slideshow": {
"slide_type": "-"
},
"tags": []
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"0\n",
"1\n",
"1\n",
"2\n",
"3\n",
"States:\n",
" Next Fibonacci number = 5\n",
" Next next Fibonacci number = 8\n",
" Next order = 5\n"
]
}
],
"source": [
"def fibonacci_sequence(Fn, Fnn):\n",
" def next_fibonacci():\n",
" \"\"\"Returns the next (generalized) Fibonacci number starting with\n",
" Fn and Fnn as the first two numbers.\"\"\"\n",
" nonlocal Fn, Fnn, n # declare nonlocal variables\n",
" value = Fn\n",
" Fn, Fnn, n = Fnn, Fn + Fnn, n + 1\n",
" return value\n",
"\n",
" def print_fibonacci_state():\n",
" print(\n",
" \"\"\"States:\n",
" Next Fibonacci number = {}\n",
" Next next Fibonacci number = {}\n",
" Next order = {}\"\"\".format(\n",
" Fn, Fnn, n\n",
" )\n",
" )\n",
"\n",
" n = 0 # Fn and Fnn specified in the function arguments\n",
" return next_fibonacci, print_fibonacci_state\n",
"\n",
"\n",
"next_fibonacci, print_fibonacci_state = fibonacci_sequence(0, 1)\n",
"n = 0\n",
"while n < 5:\n",
" print(next_fibonacci())\n",
" n += 1\n",
"print_fibonacci_state()"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"The state variables `Fn, Fnn, n` are now [*encapsulated*](https://en.wikipedia.org/wiki/Encapsulation_(computer_programming)), and the functions returned by `fibonacci_sequence` no longer depends on any global variables."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"Another benefit of using nested functions is that we can also create different Fibonacci sequence with different base cases."
]
},
{
"cell_type": "code",
"execution_count": 75,
"metadata": {
"ExecuteTime": {
"end_time": "2020-10-31T02:43:55.098142Z",
"start_time": "2020-10-31T02:43:55.091975Z"
},
"slideshow": {
"slide_type": "-"
},
"tags": []
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"cs\n",
"1302\n",
"cs1302\n",
"1302cs1302\n",
"cs13021302cs1302\n",
"States:\n",
" Next Fibonacci number = 1302cs1302cs13021302cs1302\n",
" Next next Fibonacci number = cs13021302cs13021302cs1302cs13021302cs1302\n",
" Next order = 5\n"
]
}
],
"source": [
"my_next_fibonacci, my_print_fibonacci_state = fibonacci_sequence(\"cs\", \"1302\")\n",
"for n in range(5):\n",
" print(my_next_fibonacci())\n",
"my_print_fibonacci_state()"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"`next_fibonacci` and `print_fibonacci_state` are *local functions* of `fibonacci_sequence`. \n",
"- They can access (*capture*) the other local variables of `fibonacci_sequence` by forming the so-called *closures*.\n",
"- Similar to the use of `global` statement, a [`non-local` statement](https://docs.python.org/3/reference/simple_stmts.html#the-nonlocal-statement) is needed for assigning nonlocal variables."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"Each local function has an attribute named `__closure__` that stores the captured local variables."
]
},
{
"cell_type": "code",
"execution_count": 76,
"metadata": {
"ExecuteTime": {
"end_time": "2020-10-31T02:43:55.587635Z",
"start_time": "2020-10-31T02:43:55.580308Z"
},
"slideshow": {
"slide_type": "-"
},
"tags": []
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"closure of next_fibonacci\n",
" content: 5\n",
" content: 8\n",
" content: 5\n",
"closure of print_fibonacci_state\n",
" content: 5\n",
" content: 8\n",
" | content: 5\n"
]
}
],
"source": [
"def print_closure(f):\n",
" \"\"\"Print the closure of a function.\"\"\"\n",
" print(\"closure of \", f.__name__)\n",
" for cell in f.__closure__:\n",
" print(\" {} content: {!r}\".format(cell, cell.cell_contents))\n",
"\n",
"\n",
"print_closure(next_fibonacci)\n",
"print_closure(print_fibonacci_state)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## Generator"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"An easier way to generate a sequence of objects one-by-one is to write a *generator*."
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"ExecuteTime": {
"end_time": "2020-10-31T02:43:56.056953Z",
"start_time": "2020-10-31T02:43:56.053130Z"
},
"code_folding": [],
"slideshow": {
"slide_type": "-"
},
"tags": []
},
"outputs": [
{
"data": {
"text/plain": [
" at 0x7ff3980dd510>"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"fibonacci_generator = (fibonacci_iteration(n) for n in range(3))\n",
"fibonacci_generator"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"The above uses a [*generator expression*](https://docs.python.org/3/reference/expressions.html#grammar-token-generator-expression) to define `fibonacci_generator`."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"**How to obtain items from a generator?**"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"We can use the [`next` function](https://docs.python.org/3/library/functions.html#next)."
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {
"ExecuteTime": {
"end_time": "2020-10-31T02:43:56.941808Z",
"start_time": "2020-10-31T02:43:56.933889Z"
},
"slideshow": {
"slide_type": "-"
}
},
"outputs": [
{
"ename": "StopIteration",
"evalue": "",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mStopIteration\u001b[0m Traceback (most recent call last)",
"\u001b[0;32m/tmp/ipykernel_4269/1490110649.py\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;32mwhile\u001b[0m \u001b[0;32mTrue\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnext\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfibonacci_generator\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;31m# raises StopIterationException eventually\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[0;31mStopIteration\u001b[0m: "
]
}
],
"source": [
"while True:\n",
" print(next(fibonacci_generator)) # raises StopIterationException eventually"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"A generator object is [*iterable*](https://www.programiz.com/python-programming/iterator), i.e., it implements both `__iter__` and `__next__` methods that are automatically called in a `for` loop as well as the `next` function."
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {
"ExecuteTime": {
"end_time": "2020-10-31T02:43:57.261618Z",
"start_time": "2020-10-31T02:43:57.256104Z"
},
"slideshow": {
"slide_type": "-"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"0\n",
"1\n",
"1\n",
"2\n",
"3\n"
]
}
],
"source": [
"fibonacci_generator = (fibonacci_iteration(n) for n in range(5))\n",
"for fib in fibonacci_generator: # StopIterationException handled by for loop\n",
" print(fib)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"**Is `fibonacci_generator` efficient?**"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"No, again due to redundant computations. A better way to define the generator is to use the keyword [`yield`](https://docs.python.org/3/reference/expressions.html?highlight=yield#yield-expressions):"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {
"ExecuteTime": {
"end_time": "2020-10-31T02:43:57.716343Z",
"start_time": "2020-10-31T02:43:57.711570Z"
},
"slideshow": {
"slide_type": "-"
},
"tags": []
},
"outputs": [
{
"data": {
"text/html": [
"\n",
" \n",
" "
],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"%%mytutor -h 450\n",
"def fibonacci_sequence(Fn, Fnn, stop):\n",
" \"\"\"Return a generator that generates Fibonacci numbers\n",
" starting from Fn and Fnn until stop (exclusive).\"\"\"\n",
" while Fn < stop:\n",
" yield Fn # return Fn and pause execution\n",
" Fn, Fnn = Fnn, Fnn + Fn\n",
"\n",
"\n",
"for fib in fibonacci_sequence(0, 1, 5):\n",
" print(fib)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"1. `yield` causes the function to return a *generator* without executing the function body.\n",
"1. Calling `__next__` resumes the execution, which \n",
" - pauses at the next `yield` expression, or\n",
" - raises the `StopIterationException` at the end."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"**Exercise** `yield` can be both a statement and an expression. As an expression: \n",
"- The value of a `yield` expression is `None` by default, but \n",
"- it can be set by the `generator.send` method.\n",
"\n",
"Add the document string to the following function. In particular, explain the effect of calling the method `send` on the returned generator."
]
},
{
"cell_type": "code",
"execution_count": 30,
"metadata": {
"ExecuteTime": {
"end_time": "2020-10-31T02:43:58.225871Z",
"start_time": "2020-10-31T02:43:58.216334Z"
},
"code_folding": [],
"nbgrader": {
"grade": false,
"grade_id": "send",
"locked": false,
"schema_version": 3,
"solution": true,
"task": false
},
"slideshow": {
"slide_type": "-"
},
"tags": []
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"0\n",
"2\n",
"2\n",
"4\n"
]
}
],
"source": [
"def fibonacci_sequence(Fn, Fnn, stop):\n",
" ### BEGIN SOLUTION\n",
" \"\"\"Return a generator that generates Fibonacci numbers\n",
" starting from Fn and Fnn to stop (exclusive).\n",
" generator.send(value) sets and returns the next number as value.\"\"\"\n",
" ### END SOLUTION\n",
" while Fn < stop:\n",
" value = yield Fn\n",
" if value is not None:\n",
" Fnn = value # set next number to the value of yield expression\n",
" Fn, Fnn = Fnn, Fnn + Fn\n",
"\n",
"\n",
"fibonacci_generator = fibonacci_sequence(0, 1, 5)\n",
"print(next(fibonacci_generator))\n",
"print(fibonacci_generator.send(2))\n",
"for fib in fibonacci_generator:\n",
" print(fib)"
]
}
],
"metadata": {
"celltoolbar": "Create Assignment",
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.12"
},
"latex_envs": {
"LaTeX_envs_menu_present": true,
"autoclose": false,
"autocomplete": true,
"bibliofile": "biblio.bib",
"cite_by": "apalike",
"current_citInitial": 1,
"eqLabelWithNumbers": true,
"eqNumInitial": 1,
"hotkeys": {
"equation": "Ctrl-E",
"itemize": "Ctrl-I"
},
"labels_anchors": false,
"latex_user_defs": false,
"report_style_numbering": false,
"user_envs_cfg": false
},
"rise": {
"enable_chalkboard": true,
"scroll": true,
"theme": "white"
},
"toc": {
"base_numbering": 1,
"nav_menu": {
"height": "195px",
"width": "330px"
},
"number_sections": true,
"sideBar": true,
"skip_h1_title": true,
"title_cell": "Table of Contents",
"title_sidebar": "Contents",
"toc_cell": false,
"toc_position": {
"height": "454.418px",
"left": "1533px",
"top": "110.284px",
"width": "261px"
},
"toc_section_display": true,
"toc_window_display": false
},
"widgets": {
"application/vnd.jupyter.widget-state+json": {
"state": {},
"version_major": 2,
"version_minor": 0
}
}
},
"nbformat": 4,
"nbformat_minor": 4
}
| | | | | |