{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"**Important**: Click on \"*Kernel*\" > \"*Restart Kernel and Clear All Outputs*\" *before* reading this chapter in [JupyterLab ](https://jupyterlab.readthedocs.io/en/stable/) (e.g., in the cloud on [MyBinder ](https://mybinder.org/v2/gh/webartifex/intro-to-python/master?urlpath=lab/tree/08_mfr_00_content.ipynb))"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"# Chapter 8: Map, Filter, & Reduce"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"In this chapter, we continue the study of sequential data by looking at memory efficient ways to process the elements in a sequence. That is an important topic for the data science practitioner who must be able to work with data that does *not* fit into a single computer's memory.\n",
"\n",
"As shown in [Chapter 4 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/04_iteration_00_content.ipynb#Containers-vs.-Iterables), both the `list` objects `[0, 1, 2, 3, 4]` and `[1, 3, 5, 7, 9]` on the one side and the `range` objects `range(5)` and `range(1, 10, 2)` on the other side allow us to loop over the same numbers. However, the latter two only create *one* `int` object in every iteration while the former two create *all* `int` objects before the loop even starts. In this aspect, we consider `range` objects to be \"rules\" in memory that know how to calculate numbers *without* calculating them.\n",
"\n",
"In [Chapter 7 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/07_sequences_00_content.ipynb#The-list-Type), we see how the built-in [list() ](https://docs.python.org/3/library/functions.html#func-list) constructor **materializes** the `range(1, 13)` object into the `list` object `[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]`. In other words, we make `range(1, 13)` calculate *all* numbers at once and store them in a `list` object for further processing.\n",
"\n",
"In many cases, however, it is not necessary to do that, and, in this chapter, we look at other types of \"rules\" in memory and how we can compose different \"rules\" together to implement bigger computations.\n",
"\n",
"Next, we take a step back and continue with a simple example involving the familiar `numbers` list. Then, we iteratively exchange `list` objects with \"rule\"-like objects *without* changing the overall computation at all. As computations involving sequential data are commonly classified into three categories **map**, **filter**, or **reduce**, we do so too for our `numbers` example."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [],
"source": [
"numbers = [7, 11, 8, 5, 3, 12, 2, 6, 9, 10, 1, 4]"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## Mapping"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"**Mapping** refers to the idea of applying a transformation to every element in a sequence.\n",
"\n",
"For example, let's square each element in `numbers` and add `1` to the squares. In essence, we apply the transformation $y := x^2 + 1$ expressed with the `transform()` function below."
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [],
"source": [
"def transform(element):\n",
" \"\"\"Map elements to their squares plus 1.\"\"\"\n",
" return (element ** 2) + 1"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"With the syntax we know so far, we revert to a `for`-loop that iteratively appends the transformed elements to an initially empty `transformed_numbers` list."
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"transformed_numbers = []\n",
"\n",
"for old in numbers:\n",
" new = transform(old)\n",
" transformed_numbers.append(new)"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"[50, 122, 65, 26, 10, 145, 5, 37, 82, 101, 2, 17]"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"transformed_numbers"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"As this kind of data processing is so common, Python provides the [map() ](https://docs.python.org/3/library/functions.html#map) built-in. In its simplest usage form, it takes two arguments: A transformation `function` that takes one positional argument and an `iterable`.\n",
"\n",
"We call [map() ](https://docs.python.org/3/library/functions.html#map) with a reference to the `transform()` function and the `numbers` list as the arguments and store the result in the variable `transformer` to inspect it."
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [],
"source": [
"transformer = map(transform, numbers)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"We might expect to get back a materialized sequence (i.e., all elements exist in memory), and a `list` object would feel the most natural because of the type of the `numbers` argument. However, `transformer` is an object of type `map`."
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
""
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"transformer"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"map"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"type(transformer)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Like `range` objects, `map` objects generate a series of objects \"on the fly\" (i.e., one by one), and we use the built-in [next() ](https://docs.python.org/3/library/functions.html#next) function to obtain the next object in line. So, we should think of a `map` object as a \"rule\" stored in memory that only knows how to calculate the next object of possibly *infinitely* many."
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"50"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"next(transformer)"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"122"
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"next(transformer)"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"65"
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"next(transformer)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"It is essential to understand that by creating a `map` object with the [map() ](https://docs.python.org/3/library/functions.html#map) built-in, nothing happens in memory except the creation of the `map` object. In particular, no second `list` object derived from `numbers` is created. Also, we may view `range` objects as a special case of `map` objects: They are constrained to generating `int` objects only, and the `iterable` argument is replaced with `start`, `stop`, and `step` arguments.\n",
"\n",
"If we are sure that a `map` object generates a *finite* number of elements, we may materialize them into a `list` object with the built-in [list() ](https://docs.python.org/3/library/functions.html#func-list) constructor. In the example, this is the case as `transformer` is derived from a *finite* `list` object.\n",
"\n",
"In summary, instead of creating an empty list first and appending it in a `for`-loop as above, we write the following one-liner and obtain an equal `transformed_numbers` list."
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [],
"source": [
"transformed_numbers = list(map(transform, numbers))"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"[50, 122, 65, 26, 10, 145, 5, 37, 82, 101, 2, 17]"
]
},
"execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"transformed_numbers"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## Filtering"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"**Filtering** refers to the idea of creating a subset of a sequence with a **boolean filter** `function` that indicates if an element should be kept (i.e., `True`) or not (i.e., `False`).\n",
"\n",
"In the example, let's only keep the even elements in `numbers`. The `is_even()` function implements that as a filter."
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [],
"source": [
"def is_even(element):\n",
" \"\"\"Filter out odd numbers.\"\"\"\n",
" if element % 2 == 0:\n",
" return True\n",
" return False"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"As `element % 2 == 0` is already a boolean expression, we could shorten `is_even()` like so."
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"source": [
"def is_even(element):\n",
" \"\"\"Filter out odd numbers.\"\"\"\n",
" return element % 2 == 0"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"As before, we first use a `for`-loop that appends the elements to be kept iteratively to an initially empty `even_numbers` list."
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"even_numbers = []\n",
"\n",
"for number in transformed_numbers:\n",
" if is_even(number):\n",
" even_numbers.append(number)"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"[50, 122, 26, 10, 82, 2]"
]
},
"execution_count": 16,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"even_numbers"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Analogously to the `map` object above, we use the [filter() ](https://docs.python.org/3/library/functions.html#filter) built-in to create an object of type `filter` and assign it to `evens`."
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [],
"source": [
"evens = filter(is_even, transformed_numbers)"
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
""
]
},
"execution_count": 18,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"evens"
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"filter"
]
},
"execution_count": 19,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"type(evens)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"`evens` works like `transformer` above: With the built-in [next() ](https://docs.python.org/3/library/functions.html#next) function we obtain the even numbers one by one. So, the \"next\" element in line is simply the next even `int` object the `filter` object encounters."
]
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"[50, 122, 65, 26, 10, 145, 5, 37, 82, 101, 2, 17]"
]
},
"execution_count": 20,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"transformed_numbers"
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"50"
]
},
"execution_count": 21,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"next(evens)"
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"122"
]
},
"execution_count": 22,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"next(evens)"
]
},
{
"cell_type": "code",
"execution_count": 23,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"26"
]
},
"execution_count": 23,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"next(evens)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"As above, we could create a materialized `list` object with the [list() ](https://docs.python.org/3/library/functions.html#func-list) constructor."
]
},
{
"cell_type": "code",
"execution_count": 24,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [
{
"data": {
"text/plain": [
"[50, 122, 26, 10, 82, 2]"
]
},
"execution_count": 24,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"list(filter(is_even, transformed_numbers))"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"We may also chain `map` and `filter` objects derived from the original `numbers` list."
]
},
{
"cell_type": "code",
"execution_count": 25,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"[50, 122, 26, 10, 82, 2]"
]
},
"execution_count": 25,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"list(\n",
" filter(\n",
" is_even,\n",
" map(transform, numbers),\n",
" )\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Using the [map() ](https://docs.python.org/3/library/functions.html#map) and [filter() ](https://docs.python.org/3/library/functions.html#filter) built-ins, we can quickly switch the order: Filter first and then transform the remaining elements. This variant equals the \"*A simple Filter*\" example in [Chapter 4 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/04_iteration_00_content.ipynb#Example:-A-simple-Filter). On the contrary, code with `for`-loops and `if` statements is more tedious to adapt. Additionally, `map` and `filter` objects loop \"at the C level\" and are a lot faster because of that. Because of that, Pythonistas tend to *not* use explicit `for`-loops so much."
]
},
{
"cell_type": "code",
"execution_count": 26,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"[65, 145, 5, 37, 101, 17]"
]
},
"execution_count": 26,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"list(\n",
" map(\n",
" transform,\n",
" filter(is_even, numbers),\n",
" )\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## Reducing"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Lastly, **reducing** sequential data means to summarize the elements into a single statistic.\n",
"\n",
"A simple example is the built-in [sum() ](https://docs.python.org/3/library/functions.html#sum) function."
]
},
{
"cell_type": "code",
"execution_count": 27,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [
{
"data": {
"text/plain": [
"370"
]
},
"execution_count": 27,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"sum(\n",
" map(\n",
" transform,\n",
" filter(is_even, numbers),\n",
" )\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Other straightforward examples are the built-in [min() ](https://docs.python.org/3/library/functions.html#min) or [max() ](https://docs.python.org/3/library/functions.html#max) functions."
]
},
{
"cell_type": "code",
"execution_count": 28,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"5"
]
},
"execution_count": 28,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"min(map(transform, filter(is_even, numbers)))"
]
},
{
"cell_type": "code",
"execution_count": 29,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"145"
]
},
"execution_count": 29,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"max(map(transform, filter(is_even, numbers)))"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"[sum() ](https://docs.python.org/3/library/functions.html#sum), [min() ](https://docs.python.org/3/library/functions.html#min), and [max() ](https://docs.python.org/3/library/functions.html#max) can be regarded as special cases.\n",
"\n",
"The generic way of reducing a sequence is to apply a function of *two* arguments on a rolling horizon: Its first argument is the reduction of the elements processed so far, and the second the next element to be reduced.\n",
"\n",
"For illustration, let's replicate [sum() ](https://docs.python.org/3/library/functions.html#sum) as such a function, called `sum_alt()`. Its implementation only adds two numbers."
]
},
{
"cell_type": "code",
"execution_count": 30,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [],
"source": [
"def sum_alt(sum_so_far, next_number):\n",
" \"\"\"Reduce a sequence by addition.\"\"\"\n",
" return sum_so_far + next_number"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Further, we create a *new* `map` object derived from `numbers` ..."
]
},
{
"cell_type": "code",
"execution_count": 31,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"evens_transformed = map(transform, filter(is_even, numbers))"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"... and loop over all *but* the first element it generates. The latter is captured separately as the initial `result` with the [next() ](https://docs.python.org/3/library/functions.html#next) function. We know from above that `evens_transformed` generates *six* elements. That is why we see *five* growing `result` values resembling a [cumulative sum](http://mathworld.wolfram.com/CumulativeSum.html). The first `210` is the sum of the first two elements generated by `evens_transformed`, `65` and `145`.\n",
"\n",
"So, we also learn that `map` objects, and analogously `filter` objects, are *iterable* as we may loop over them."
]
},
{
"cell_type": "code",
"execution_count": 32,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"210 215 252 353 370 "
]
}
],
"source": [
"result = next(evens_transformed)\n",
"\n",
"for number in evens_transformed:\n",
" result = sum_alt(result, number)\n",
" print(result, end=\" \") # line added for didactical purposes"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"The final `result` is the same `370` as above."
]
},
{
"cell_type": "code",
"execution_count": 33,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"370"
]
},
"execution_count": 33,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"result"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"The [reduce() ](https://docs.python.org/3/library/functools.html#functools.reduce) function in the [functools ](https://docs.python.org/3/library/functools.html) module in the [standard library ](https://docs.python.org/3/library/index.html) provides more convenience (and speed) replacing the `for`-loop. It takes two arguments, `function` and `iterable`, in the same way as the [map() ](https://docs.python.org/3/library/functions.html#map) and [filter() ](https://docs.python.org/3/library/functions.html#filter) built-ins.\n",
"\n",
"[reduce() ](https://docs.python.org/3/library/functools.html#functools.reduce) is **[eager ](https://en.wikipedia.org/wiki/Eager_evaluation)** meaning that all computations implied by the contained `map` and `filter` \"rules\" are executed immediately, and the code cell evaluates to `370`. On the contrary, [map() ](https://docs.python.org/3/library/functions.html#map) and [filter() ](https://docs.python.org/3/library/functions.html#filter) create **[lazy ](https://en.wikipedia.org/wiki/Lazy_evaluation)** `map` and `filter` objects, and we have to use the [next() ](https://docs.python.org/3/library/functions.html#next) function to obtain the elements."
]
},
{
"cell_type": "code",
"execution_count": 34,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [],
"source": [
"from functools import reduce"
]
},
{
"cell_type": "code",
"execution_count": 35,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"370"
]
},
"execution_count": 35,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"reduce(\n",
" sum_alt,\n",
" map(\n",
" transform,\n",
" filter(is_even, numbers),\n",
" )\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## Lambda Expressions"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"[map() ](https://docs.python.org/3/library/functions.html#map), [filter() ](https://docs.python.org/3/library/functions.html#filter), and [reduce() ](https://docs.python.org/3/library/functools.html#functools.reduce) take a `function` object as their first argument, and we defined `transform()`, `is_even()`, and `sum_alt()` to be used precisely for that.\n",
"\n",
"Often, such functions are used *only once* in a program. However, the primary purpose of functions is to *reuse* them. In such cases, it makes more sense to define them \"anonymously\" right at the position where the first argument goes.\n",
"\n",
"As mentioned in [Chapter 2 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/02_functions_00_content.ipynb#Anonymous-Functions), we use `lambda` expressions to create `function` objects *without* a name referencing them.\n",
"\n",
"So, the above `sum_alt()` function could be rewritten as a `lambda` expression like so ..."
]
},
{
"cell_type": "code",
"execution_count": 36,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [
{
"data": {
"text/plain": [
"(sum_so_far, next_number)>"
]
},
"execution_count": 36,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"lambda sum_so_far, next_number: sum_so_far + next_number"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"... or even shorter."
]
},
{
"cell_type": "code",
"execution_count": 37,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"(x, y)>"
]
},
"execution_count": 37,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"lambda x, y: x + y"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"With the new concepts in this section, we can rewrite the entire example in just a few lines of code *without* any `for`, `if`, and `def` statements. The resulting code is concise, easy to read, quick to modify, and even faster in execution. Most importantly, it is optimized to handle big amounts of data as *no* temporary `list` objects are materialized in memory."
]
},
{
"cell_type": "code",
"execution_count": 38,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [
{
"data": {
"text/plain": [
"370"
]
},
"execution_count": 38,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"numbers = [7, 11, 8, 5, 3, 12, 2, 6, 9, 10, 1, 4]\n",
"evens = filter(lambda x: x % 2 == 0, numbers)\n",
"transformed = map(lambda x: (x ** 2) + 1, evens)\n",
"sum(transformed)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"If `numbers` comes as a sorted sequence of whole numbers, we may use the [range() ](https://docs.python.org/3/library/functions.html#func-range) built-in and get away *without* any `list` object in memory at all!"
]
},
{
"cell_type": "code",
"execution_count": 39,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"370"
]
},
"execution_count": 39,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"numbers = range(1, 13)\n",
"evens = filter(lambda x: x % 2 == 0, numbers)\n",
"transformed = map(lambda x: (x ** 2) + 1, evens)\n",
"sum(transformed)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"To additionally save the temporary variables, `numbers`, `evens`, and `transformed`, we write the entire computation as *one* expression."
]
},
{
"cell_type": "code",
"execution_count": 40,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"370"
]
},
"execution_count": 40,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"sum(\n",
" map(\n",
" lambda x: (x ** 2) + 1,\n",
" filter(\n",
" lambda x: x % 2 == 0,\n",
" range(1, 13),\n",
" )\n",
" )\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"PythonTutor visualizes the differences in the number of computational steps and memory usage:\n",
"- [Version 1 ](http://pythontutor.com/visualize.html#code=def%20is_even%28element%29%3A%0A%20%20%20%20if%20element%20%25%202%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%20True%0A%20%20%20%20return%20False%0A%0Adef%20transform%28element%29%3A%0A%20%20%20%20return%20%28element%20**%202%29%20%2B%201%0A%0Anumbers%20%3D%20list%28range%281,%2013%29%29%0A%0Aevens%20%3D%20%5B%5D%0Afor%20number%20in%20numbers%3A%0A%20%20%20%20if%20is_even%28number%29%3A%0A%20%20%20%20%20%20%20%20evens.append%28number%29%0A%0Atransformed%20%3D%20%5B%5D%0Afor%20number%20in%20evens%3A%0A%20%20%20%20transformed.append%28transform%28number%29%29%0A%0Aresult%20%3D%20sum%28transformed%29&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false): With `for`-loops, `if` statements, and named functions -> **116** steps and **3** `list` objects\n",
"- [Version 2 ](http://pythontutor.com/visualize.html#code=numbers%20%3D%20range%281,%2013%29%0Aevens%20%3D%20filter%28lambda%20x%3A%20x%20%25%202%20%3D%3D%200,%20numbers%29%0Atransformed%20%3D%20map%28lambda%20x%3A%20%28x%20**%202%29%20%2B%201,%20evens%29%0Aresult%20%3D%20sum%28transformed%29&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false): With named `map` and `filter` objects -> **58** steps and **no** `list` object\n",
"- [Version 3 ](http://pythontutor.com/visualize.html#code=result%20%3D%20sum%28map%28lambda%20x%3A%20%28x%20**%202%29%20%2B%201,%20filter%28lambda%20x%3A%20x%20%25%202%20%3D%3D%200,%20range%281,%2013%29%29%29%29&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false): Everything in *one* expression -> **55** steps and **no** `list` object\n",
"\n",
"Versions 2 and 3 are the same, except for the three additional steps required to create the temporary variables. The *major* downside of Version 1 is that, in the worst case, it may need *three times* the memory as compared to the other two versions!\n",
"\n",
"An experienced Pythonista would probably go with Version 2 in a production system to keep the code readable and maintainable.\n",
"\n",
"The map-filter-reduce paradigm has caught attention in recent years as it enables **[parallel computing ](https://en.wikipedia.org/wiki/Parallel_computing)**, and this gets important when dealing with big amounts of data. The workings in the memory as shown in this section provide an idea why."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## List Comprehensions"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"For [map() ](https://docs.python.org/3/library/functions.html#map) and [filter() ](https://docs.python.org/3/library/functions.html#filter), Python provides an alternative syntax appealing to people who like mathematical notations.\n",
"\n",
"Consider again the original \"*A simple Filter*\" example from [Chapter 4 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/04_iteration_00_content.ipynb#Example:-A-simple-Filter), re-written such that both the mapping and the filtering are done in *one* `for`-loop instead of the *two* above."
]
},
{
"cell_type": "code",
"execution_count": 41,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [],
"source": [
"numbers = [7, 11, 8, 5, 3, 12, 2, 6, 9, 10, 1, 4]"
]
},
{
"cell_type": "code",
"execution_count": 42,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"evens_transformed = []\n",
"\n",
"for number in numbers:\n",
" if number % 2 == 0:\n",
" evens_transformed.append((number ** 2) + 1)"
]
},
{
"cell_type": "code",
"execution_count": 43,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"370"
]
},
"execution_count": 43,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"sum(evens_transformed)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"**List comprehensions**, or **listcomps** for short, are *expressions* to derive *new* `list` objects out of *existing* ones (cf., [reference ](https://docs.python.org/3/reference/expressions.html#displays-for-lists-sets-and-dictionaries)). Practically, this means that we place the `for` and `if` inside brackets `[` and `]`.\n",
"\n",
"So, the example can be written in a single *expression* like below replacing the compound `for` *statement* above."
]
},
{
"cell_type": "code",
"execution_count": 44,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"[65, 145, 5, 37, 101, 17]"
]
},
"execution_count": 44,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"[(n ** 2) + 1 for n in numbers if n % 2 == 0]"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"A list comprehension may be used in place of any `list` object.\n",
"\n",
"For example, let's rewrite the \"*A simple Filter*\" example from [Chapter 4 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/04_iteration_00_content.ipynb#Example:-A-simple-Filter) in just one line. As a caveat, the code below *materializes* all elements in memory *before* summing them up, and may, therefore, cause a `MemoryError` when executed with a bigger `numbers` list. We see with [PythonTutor ](http://pythontutor.com/visualize.html#code=numbers%20%3D%20range%281,%2013%29%0Aresult%20%3D%20sum%28%5B%28n%20**%202%29%20%2B%201%20for%20n%20in%20numbers%20if%20n%20%25%202%20%3D%3D%200%5D%29&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false) how a `list` object exists in memory at step 17 and then \"gets lost\" right after. As the next section shows, this downside may be mitigated."
]
},
{
"cell_type": "code",
"execution_count": 45,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"370"
]
},
"execution_count": 45,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"sum([(n ** 2) + 1 for n in numbers if n % 2 == 0])"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"#### Example: Nested Lists"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"List comprehensions may come with several `for`'s and `if`'s.\n",
"\n",
"The cell below creates a `list` object that contains three other `list` objects with a series of numbers in them. The first and last numbers in each inner `list` object are offset by `1`."
]
},
{
"cell_type": "code",
"execution_count": 46,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"source": [
"nested_numbers = [list(range(x, y + 1)) for x, y in zip([1, 2, 3], [7, 8, 9])]"
]
},
{
"cell_type": "code",
"execution_count": 47,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"[[1, 2, 3, 4, 5, 6, 7], [2, 3, 4, 5, 6, 7, 8], [3, 4, 5, 6, 7, 8, 9]]"
]
},
"execution_count": 47,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"nested_numbers"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"To do something meaningful with the numbers, we have to remove the inner layer of `list` objects and **flatten** (i.e., \"un-nest\") the data.\n",
"\n",
"Without list comprehensions, we achieve that with two nested `for`-loops."
]
},
{
"cell_type": "code",
"execution_count": 48,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"source": [
"flat_numbers = []\n",
"\n",
"for inner_numbers in nested_numbers:\n",
" for number in inner_numbers:\n",
" flat_numbers.append(number)"
]
},
{
"cell_type": "code",
"execution_count": 49,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"[1, 2, 3, 4, 5, 6, 7, 2, 3, 4, 5, 6, 7, 8, 3, 4, 5, 6, 7, 8, 9]"
]
},
"execution_count": 49,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"flat_numbers"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"That translates into a list comprehension like below. The order of the `for`'s may be confusing at first but is the *same* as writing out the nested `for`-loops as above."
]
},
{
"cell_type": "code",
"execution_count": 50,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"[1, 2, 3, 4, 5, 6, 7, 2, 3, 4, 5, 6, 7, 8, 3, 4, 5, 6, 7, 8, 9]"
]
},
"execution_count": 50,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"[number for inner_numbers in nested_numbers for number in inner_numbers]"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Now, we may use the `list` object resulting from the list comprehension in any way we want.\n",
"\n",
"For example, to add up the flattened numbers with [sum() ](https://docs.python.org/3/library/functions.html#sum). The same caveat holds as before: The `list` object passed into [sum() ](https://docs.python.org/3/library/functions.html#sum) is *materialized* with *all* its elements *before* the sum is calculated!"
]
},
{
"cell_type": "code",
"execution_count": 51,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"105"
]
},
"execution_count": 51,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"sum([number for inner_numbers in nested_numbers for number in inner_numbers])"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"In this particular example, however, we can exploit the fact that any sum of numbers can be expressed as the sum of sums of mutually exclusive and collectively exhaustive subsets of these numbers and get away with just *one* `for` in the list comprehension."
]
},
{
"cell_type": "code",
"execution_count": 52,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"105"
]
},
"execution_count": 52,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"sum([sum(inner_numbers) for inner_numbers in nested_numbers])"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"#### Example: Working with Cartesian Products"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"A popular use case of nested list comprehensions is applying a transformation to each $n$-tuple in the [Cartesian product ](https://en.wikipedia.org/wiki/Cartesian_product) created from elements of $n$ iterables. In the generic illustration below, a transformation $f(x, y)$ is applied to each $2$-tuple in the Cartesian product $X \\times Y$ where $x$ is an element in $X = \\{x_1, x_2, x_3\\}$ and $y$ is an element in $Y = \\{y_1, y_2, y_3\\}$."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"|$Y$ \\ $X$| $x_1$ | $x_2$ | $x_3$ |\n",
"|---------|--------------|--------------|--------------|\n",
"| $y_1$ |f($x_1$,$y_1$)|f($x_2$,$y_1$)|f($x_3$,$y_1$)|\n",
"| $y_2$ |f($x_1$,$y_2$)|f($x_2$,$y_2$)|f($x_3$,$y_2$)|\n",
"| $y_3$ |f($x_1$,$y_3$)|f($x_2$,$y_3$)|f($x_3$,$y_3$)|"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"For example, let's add each quotient obtained by taking the numerator $x$ from `[10, 20, 30]` and the denominator $y$ from `[40, 50, 60]` to `1`, and then find the overall product. This transformation can be described mathematically as the function $z = f(x, y)$ below."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"$$z = f(x, y) = \\prod{ (1 + \\frac{x}{y} )}$$"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Further, the table below visualizes the calculations: The result is the product of *nine* entries."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"|`y` \\ `x`|**10**|**20**|**30**|\n",
"|------|------|------|------|\n",
"|**40**| 1.25 | 1.50 | 1.75 |\n",
"|**50**| 1.20 | 1.40 | 1.60 |\n",
"|**60**| 1.17 | 1.33 | 1.50 |"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"To express that in Python, we start by creating two `list` objects, `first` and `second`."
]
},
{
"cell_type": "code",
"execution_count": 53,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [],
"source": [
"first = [10, 20, 30]\n",
"second = [40, 50, 60]"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"For a Cartesian product, we must loop over *all* possible $2$-tuples where one element is drawn from `first` and the other from `second`. We achieve that with two nested `for`-loops, in which we calculate each `quotient` and append it to an initially empty `cartesian_product` list."
]
},
{
"cell_type": "code",
"execution_count": 54,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"[1.25, 1.2, 1.1666666666666667, 1.5, 1.4, 1.3333333333333333, 1.75, 1.6, 1.5]"
]
},
"execution_count": 54,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"cartesian_product = []\n",
"\n",
"for numerator in first:\n",
" for denominator in second:\n",
" quotient = 1 + (numerator / denominator)\n",
" cartesian_product.append(quotient)\n",
"\n",
"cartesian_product"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Next, we convert the two explicit `for`-loops into one list comprehensions and use `x` and `y` as shorter variable names."
]
},
{
"cell_type": "code",
"execution_count": 55,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"[1.25, 1.2, 1.1666666666666667, 1.5, 1.4, 1.3333333333333333, 1.75, 1.6, 1.5]"
]
},
"execution_count": 55,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"[1 + (x / y) for x in first for y in second]"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"The order of the `for`'s is *important*: The list comprehension above divides numbers from `first` by numbers from `second`, whereas the list comprehension below does the opposite."
]
},
{
"cell_type": "code",
"execution_count": 56,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"[5.0, 3.0, 2.333333333333333, 6.0, 3.5, 2.666666666666667, 7.0, 4.0, 3.0]"
]
},
"execution_count": 56,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"[1 + (x / y) for x in second for y in first]"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"To find the overall product, we *unpack* the list comprehension into the `product()` function from the \"*Function Definitions & Calls*\" sub-section in [Chapter 7 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/07_sequences_00_content.ipynb#Function-Definitions-&-Calls)."
]
},
{
"cell_type": "code",
"execution_count": 57,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [],
"source": [
"def product(*args):\n",
" \"\"\"Multiply all arguments.\"\"\"\n",
" result = args[0]\n",
"\n",
" for arg in args[1:]:\n",
" result *= arg\n",
"\n",
" return result"
]
},
{
"cell_type": "code",
"execution_count": 58,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"20.58"
]
},
"execution_count": 58,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"product(*[1 + (x / y) for x in first for y in second])"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Alternatively, we use a `lambda` expression with the [reduce() ](https://docs.python.org/3/library/functools.html#functools.reduce) function from the [functools ](https://docs.python.org/3/library/functools.html) module."
]
},
{
"cell_type": "code",
"execution_count": 59,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"20.58"
]
},
"execution_count": 59,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"reduce(lambda x, y: x * y, [1 + (x / y) for x in first for y in second])"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"While this example is stylized, Cartesian products are hidden in many applications, and it shows how the various language features introduced in this chapter can be seamlessly combined to process sequential data."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## Generator Expressions"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Because of the high memory consumption, Pythonistas avoid materialized `list` objects, and, thus, also list comprehensions, whenever possible. Instead, they prefer to work with **[generator expressions ](https://docs.python.org/3/reference/expressions.html#generator-expressions)**, or **genexps** for short. Syntactically, they work like list comprehensions except that parentheses, `(` and `)`, replace brackets, `[` and `]`.\n",
"\n",
"Let's go back to the original \"*A simple Filter*\" example from [Chapter 4 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/04_iteration_00_content.ipynb#Example:-A-simple-Filter) one more time, apply the transformation $y := x^2 + 1$ to all even `numbers`, and sum them up."
]
},
{
"cell_type": "code",
"execution_count": 60,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [],
"source": [
"numbers = [7, 11, 8, 5, 3, 12, 2, 6, 9, 10, 1, 4]"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"To filter and transform `numbers`, we wrote a list comprehension above ..."
]
},
{
"cell_type": "code",
"execution_count": 61,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"[65, 145, 5, 37, 101, 17]"
]
},
"execution_count": 61,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"[(n ** 2) + 1 for n in numbers if n % 2 == 0]"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"... that now becomes a generator expression."
]
},
{
"cell_type": "code",
"execution_count": 62,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
" at 0x7f001cf938d0>"
]
},
"execution_count": 62,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"((n ** 2) + 1 for n in numbers if n % 2 == 0)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"A generator expression evaluates to yet another \"rule\"-like object in memory that knows how to generate the individual objects in a series one by one. Whereas a list comprehension materializes its elements in memory *when* it is evaluated, the opposite holds true for generator expressions: *No* object is created in memory except the \"rule\" itself. Because of this behavior, we describe generator expressions as *lazy* and list comprehensions as *eager*.\n",
"\n",
"To materialize the elements specified by a generator expression, we use the [list() ](https://docs.python.org/3/library/functions.html#func-list) constructor as seen above."
]
},
{
"cell_type": "code",
"execution_count": 63,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"[65, 145, 5, 37, 101, 17]"
]
},
"execution_count": 63,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"list(((n ** 2) + 1 for n in numbers if n % 2 == 0))"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Whenever a generator expression is the only argument in a function call, we may merge the double parentheses, `((` and `))`, into just `(` and `)`."
]
},
{
"cell_type": "code",
"execution_count": 64,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"[65, 145, 5, 37, 101, 17]"
]
},
"execution_count": 64,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"list((n ** 2) + 1 for n in numbers if n % 2 == 0)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"A common use case is to reduce the elements into a single object instead, for example, by adding them up with [sum() ](https://docs.python.org/3/library/functions.html#sum) as in the original \"*A simple Filter*\" example. [PythonTutor ](http://pythontutor.com/visualize.html#code=numbers%20%3D%20range%281,%2013%29%0Asum_with_list%20%3D%20sum%28%5B%28n%20**%202%29%20%2B%201%20for%20n%20in%20numbers%20if%20n%20%25%202%20%3D%3D%200%5D%29%0Asum_with_gen%20%3D%20sum%28%28n%20**%202%29%20%2B%201%20for%20n%20in%20numbers%20if%20n%20%25%202%20%3D%3D%200%29&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false) shows how the code cell below does *not* create a temporary `list` object in memory whereas a list comprehension would do so (cf., step 17)."
]
},
{
"cell_type": "code",
"execution_count": 65,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"370"
]
},
"execution_count": 65,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"sum((n ** 2) + 1 for n in numbers if n % 2 == 0)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Let's assign the object to which the generator expression below evaluates to to a variable and inspect it."
]
},
{
"cell_type": "code",
"execution_count": 66,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [],
"source": [
"gen = ((n ** 2) + 1 for n in numbers if n % 2 == 0)"
]
},
{
"cell_type": "code",
"execution_count": 67,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
" at 0x7f001cf93b50>"
]
},
"execution_count": 67,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"gen"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Unsurprisingly, generator expressions create objects of type `generator`."
]
},
{
"cell_type": "code",
"execution_count": 68,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"generator"
]
},
"execution_count": 68,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"type(gen)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"`generator` objects work just like the `map` and `filter` objects above. So, with the [next() ](https://docs.python.org/3/library/functions.html#next) function, we can generate elements one by one."
]
},
{
"cell_type": "code",
"execution_count": 69,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"65"
]
},
"execution_count": 69,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"next(gen)"
]
},
{
"cell_type": "code",
"execution_count": 70,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"145"
]
},
"execution_count": 70,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"next(gen)"
]
},
{
"cell_type": "code",
"execution_count": 71,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"5"
]
},
"execution_count": 71,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"next(gen)"
]
},
{
"cell_type": "code",
"execution_count": 72,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"37"
]
},
"execution_count": 72,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"next(gen)"
]
},
{
"cell_type": "code",
"execution_count": 73,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"101"
]
},
"execution_count": 73,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"next(gen)"
]
},
{
"cell_type": "code",
"execution_count": 74,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"17"
]
},
"execution_count": 74,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"next(gen)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Once a `generator` object runs out of elements, it raises a `StopIteration` exception, and we say that the `generator` object is **exhausted**. To loop over its elements again, we must create a *new* one."
]
},
{
"cell_type": "code",
"execution_count": 75,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"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\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mnext\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mgen\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[0;31mStopIteration\u001b[0m: "
]
}
],
"source": [
"next(gen)"
]
},
{
"cell_type": "code",
"execution_count": 76,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"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\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mnext\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mgen\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[0;31mStopIteration\u001b[0m: "
]
}
],
"source": [
"next(gen)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Calling the [next() ](https://docs.python.org/3/library/functions.html#next) function repeatedly with the *same* `generator` object as the argument is essentially what a `for` statement automates for us. So, `generator` objects are *iterable*. We look into this in detail further below in the \"*The `for` Statement (revisited)*\" section."
]
},
{
"cell_type": "code",
"execution_count": 77,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"65 145 5 37 101 17 "
]
}
],
"source": [
"for number in ((n ** 2) + 1 for n in numbers if n % 2 == 0):\n",
" print(number, end=\" \")"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"#### Example: Nested Lists (revisited)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"If we are only interested in a *reduction* of `nested_numbers` into a single statistic, as the overall sum in the \"*Nested Lists*\" example above, we should replace lists or list comprehensions with generator expressions wherever possible. The result is the *same*, but no intermediate `list` objects are materialized! That makes our code scale to large amounts of data.\n",
"\n",
"Let's adapt the example reuse `nested_numbers` from above."
]
},
{
"cell_type": "code",
"execution_count": 78,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"[[1, 2, 3, 4, 5, 6, 7], [2, 3, 4, 5, 6, 7, 8], [3, 4, 5, 6, 7, 8, 9]]"
]
},
"execution_count": 78,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"nested_numbers"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Compared to the implementation with the list comprehension above, we simply remove the brackets, `[` and `]`: The argument to [sum() ](https://docs.python.org/3/library/functions.html#sum) becomes a generator expression."
]
},
{
"cell_type": "code",
"execution_count": 79,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"105"
]
},
"execution_count": 79,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"sum(number for inner_numbers in nested_numbers for number in inner_numbers)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"That also holds for the alternative formulation as a sum of sums."
]
},
{
"cell_type": "code",
"execution_count": 80,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"105"
]
},
"execution_count": 80,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"sum(sum(inner_numbers) for inner_numbers in nested_numbers)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Because `nested_numbers` has an internal structure (i.e., the inner lists are series of consecutive integers), we can even provide an effectively **memoryless** implementation by expressing it as a generator expression derived from `range` objects. [PythonTutor ](http://pythontutor.com/visualize.html#code=nested_numbers%20%3D%20%28%28range%28x,%20y%20%2B%201%29%29%20for%20x,%20y%20in%20zip%28range%281,%204%29,%20range%287,%2010%29%29%29%0Aresult%20%3D%20sum%28number%20for%20inner_numbers%20in%20nested_numbers%20for%20number%20in%20inner_numbers%29&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false) confirms that no `list` objects materialize at any point in time."
]
},
{
"cell_type": "code",
"execution_count": 81,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"source": [
"nested_numbers = ((range(x, y + 1)) for x, y in zip(range(1, 4), range(7, 10)))"
]
},
{
"cell_type": "code",
"execution_count": 82,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
" at 0x7f001cfb10d0>"
]
},
"execution_count": 82,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"nested_numbers"
]
},
{
"cell_type": "code",
"execution_count": 83,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"105"
]
},
"execution_count": 83,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"sum(number for inner_numbers in nested_numbers for number in inner_numbers)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"We must be careful when assigning a `generator` object to a variable: If we use `nested_numbers` again, for example, in the alternative formulation below, [sum() ](https://docs.python.org/3/library/functions.html#sum) returns `0` because `nested_numbers` is exhausted after executing the previous code cell. [PythonTutor ](http://pythontutor.com/visualize.html#code=nested_numbers%20%3D%20%28%28range%28x,%20y%20%2B%201%29%29%20for%20x,%20y%20in%20zip%28range%281,%204%29,%20range%287,%2010%29%29%29%0Aresult%20%3D%20sum%28number%20for%20inner_numbers%20in%20nested_numbers%20for%20number%20in%20inner_numbers%29%0Ano_result%20%3D%20sum%28sum%28inner_numbers%29%20for%20inner_numbers%20in%20nested_numbers%29&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false) also shows that."
]
},
{
"cell_type": "code",
"execution_count": 84,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"0"
]
},
"execution_count": 84,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"sum(sum(inner_numbers) for inner_numbers in nested_numbers)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"#### Example: Working with Cartesian Products (revisited)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Let's also rewrite the \"*Working with Cartesian Products*\" example from above with generator expressions.\n",
"\n",
"As a first optimization, we replace the materialized `list` objects, `first` and `second`, with memoryless `range` objects."
]
},
{
"cell_type": "code",
"execution_count": 85,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [],
"source": [
"first = range(10, 31, 10) # \"==\" [10, 20, 30]\n",
"second = range(40, 61, 10) # \"==\" [40, 50, 60]"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Now, the first of the two alternative solutions may be more appealing to many readers. In general, many practitioners seem to dislike `lambda` expressions.\n",
"\n",
"In the first solution, we *unpack* the elements produced by `(1 + (x / y) for x in first for y in second)` into the `product()` function from the \"*Function Definitions & Calls*\" sub-section in [Chapter 7 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/07_sequences_00_content.ipynb#Function-Definitions-&-Calls). However, inside `product()`, the elements are *packed* into `args`, a *materialized* `tuple` object! So, all the memory efficiency gained by using a generator expression is lost! [PythonTutor ](http://pythontutor.com/visualize.html#code=def%20product%28*args%29%3A%0A%20%20%20%20result%20%3D%20args%5B0%5D%0A%20%20%20%20for%20arg%20in%20args%5B1%3A%5D%3A%0A%20%20%20%20%20%20%20%20result%20*%3D%20arg%0A%20%20%20%20return%20result%0A%0Afirst%20%3D%20range%2810,%2031,%2010%29%0Asecond%20%3D%20range%2840,%2061,%2010%29%0A%0Aresult%20%3D%20product%28*%281%20%2B%20%28x%20/%20y%29%20for%20x%20in%20first%20for%20y%20in%20second%29%29&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false) shows how a `tuple` object exists in steps 38-58."
]
},
{
"cell_type": "code",
"execution_count": 86,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"20.58"
]
},
"execution_count": 86,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"product(*(1 + (x / y) for x in first for y in second)) # not memory efficient!"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"On the contrary, the second solution with the [reduce() ](https://docs.python.org/3/library/functools.html#functools.reduce) function from the [functools ](https://docs.python.org/3/library/functools.html) module and the `lambda` expression works *without* all elements materialized at the same time, and [PythonTutor ](http://pythontutor.com/visualize.html#code=from%20functools%20import%20reduce%0A%0Afirst%20%3D%20range%2810,%2031,%2010%29%0Asecond%20%3D%20range%2840,%2061,%2010%29%0A%0Aresult%20%3D%20reduce%28%0A%20%20%20%20lambda%20x,%20y%3A%20x%20*%20y,%0A%20%20%20%20%281%20%2B%20%28x%20/%20y%29%20for%20x%20in%20first%20for%20y%20in%20second%29%0A%29&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false) confirms that. So, only the second alternative is truly memory-efficient!"
]
},
{
"cell_type": "code",
"execution_count": 87,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"20.58"
]
},
"execution_count": 87,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"reduce(lambda x, y: x * y, (1 + (x / y) for x in first for y in second))"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"In summary, we learn from this example that unpacking `generator` objects *may* be a *bad* idea."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"#### Example: Averaging all even Numbers in a List (revisited)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"With the new concepts in this chapter, let's rewrite the book's introductory \"*Averaging all even Numbers in a List*\" example from [Chapter 1 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements_00_content.ipynb#Example:-Averaging-all-even-Numbers-in-a-List) such that it efficiently handles a large sequence of numbers. We continue from its latest implementation, the `average_evens()` function in the \"*Keyword-only Arguments*\" section in [Chapter 2 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/02_functions_00_content.ipynb#Keyword-only-Arguments).\n",
"\n",
"We assume that `average_evens()` is called with a *finite* and *iterable* object that generates a **stream** of numeric objects that can be cast as `int` objects. After all, the idea of even and odd numbers makes sense only in the context of whole numbers.\n",
"\n",
"The generator expression `(round(n) for n in numbers)` implements the type casting, and when it is evaluated, *nothing* happens except that a `generator` object is assigned to `integers`. Then, with the [reduce() ](https://docs.python.org/3/library/functools.html#functools.reduce) function from the [functools ](https://docs.python.org/3/library/functools.html) module, we *simultaneously* add up *and* count the even numbers with the `generator` object to which the inner generator expression `((n, 1) for n in integers if n % 2 == 0)` evaluates to. That `generator` object takes the `integers` generator as its source and produces `tuple` objects consisting of the next *even* number in line and `1`. Two such `tuple` objects are then iteratively passed to the `function` object to which the `lambda` expression evaluates to. `x` represents the total and the count of the even numbers processed so far, while `y`'s first element, `y[0]`, is the next *even* number to be added to the running total. The result of calling [reduce() ](https://docs.python.org/3/library/functools.html#functools.reduce) is again a `tuple` object, namely the final `total` and `count`. Lastly, we calculate the simple average and scale it.\n",
"\n",
"In summary, this implementation of `average_evens()` does *not* keep materialized `list` objects internally like its predecessors in [Chapter 2 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/02_functions_00_content.ipynb) but processes the elements of the `numbers` argument on a one-by-one basis."
]
},
{
"cell_type": "code",
"execution_count": 88,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [],
"source": [
"def average_evens(numbers, *, scalar=1):\n",
" \"\"\"Calculate the average of all even numbers.\n",
"\n",
" Args:\n",
" numbers (iterable): a finite stream of the numbers to be averaged;\n",
" if non-whole numbers are provided, they are rounded\n",
" scalar (float, optional): multiplies the average; defaults to 1\n",
"\n",
" Returns:\n",
" float: (scaled) average\n",
" \"\"\"\n",
" integers = (round(n) for n in numbers)\n",
" total, count = reduce(\n",
" lambda x, y: (x[0] + y[0], x[1] + y[1]),\n",
" ((n, 1) for n in integers if n % 2 == 0)\n",
" )\n",
" return scalar * total / count"
]
},
{
"cell_type": "code",
"execution_count": 89,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [
{
"data": {
"text/plain": [
"7.0"
]
},
"execution_count": 89,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"average_evens([7, 11, 8, 5, 3, 12, 2, 6, 9, 10, 1, 4], scalar=1)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"We may provide an optional `scalar` argument as before."
]
},
{
"cell_type": "code",
"execution_count": 90,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"14.0"
]
},
"execution_count": 90,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"average_evens([7, 11, 8, 5, 3, 12, 2, 6, 9, 10, 1, 4], scalar=2)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"An argument with `float` objects works just as well."
]
},
{
"cell_type": "code",
"execution_count": 91,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"7.0"
]
},
"execution_count": 91,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"average_evens([7., 11., 8., 5., 3., 12., 2., 6., 9., 10., 1., 4.])"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"To show that `average_evens()` can process a large stream of data, we simulate `10_000_000` randomly drawn integers between `0` and `100` with the [randint() ](https://docs.python.org/3/library/random.html#random.randint) function from the [random ](https://docs.python.org/3/library/random.html) module. We use a generator expression derived from a `range` object as the `numbers` argument. So, at *no* point in time is there a materialized `list` or `tuple` object in memory. The result approaching `50` confirms that [randint() ](https://docs.python.org/3/library/random.html#random.randint) must be based on a uniform distribution."
]
},
{
"cell_type": "code",
"execution_count": 92,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [],
"source": [
"import random"
]
},
{
"cell_type": "code",
"execution_count": 93,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"source": [
"random.seed(42)"
]
},
{
"cell_type": "code",
"execution_count": 94,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"49.994081434519636"
]
},
"execution_count": 94,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"average_evens(random.randint(0, 100) for _ in range(10_000_000))"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"To show that `average_evens()` filters out odd numbers, we simulate another stream of `10_000_000` randomly drawn odd integers between `1` and `99`. As no function in the [random ](https://docs.python.org/3/library/random.html) module does that \"out of the box,\" we must be creative: Doubling a number drawn from `random.randint(0, 49)` results in an even number between `0` and `98`, and adding `1` makes it odd. Then, `average_evens()` raises a `TypeError`, essentially because `(int(n) for n in numbers)` does not generate any element."
]
},
{
"cell_type": "code",
"execution_count": 95,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"ename": "TypeError",
"evalue": "reduce() of empty sequence with no initial value",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)",
"\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0maverage_evens\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m2\u001b[0m \u001b[0;34m*\u001b[0m \u001b[0mrandom\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrandint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m49\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0;36m1\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0m_\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mrange\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m10_000_000\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[0;32m\u001b[0m in \u001b[0;36maverage_evens\u001b[0;34m(numbers, scalar)\u001b[0m\n\u001b[1;32m 13\u001b[0m total, count = reduce(\n\u001b[1;32m 14\u001b[0m \u001b[0;32mlambda\u001b[0m \u001b[0mx\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0my\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0my\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mx\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0my\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 15\u001b[0;31m \u001b[0;34m(\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mn\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mn\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mintegers\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mn\u001b[0m \u001b[0;34m%\u001b[0m \u001b[0;36m2\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;36m0\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 16\u001b[0m )\n\u001b[1;32m 17\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mscalar\u001b[0m \u001b[0;34m*\u001b[0m \u001b[0mtotal\u001b[0m \u001b[0;34m/\u001b[0m \u001b[0mcount\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;31mTypeError\u001b[0m: reduce() of empty sequence with no initial value"
]
}
],
"source": [
"average_evens(2 * random.randint(0, 49) + 1 for _ in range(10_000_000))"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"## \"Tuple Comprehensions\""
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"There is no syntax to derive *new* `tuple` objects out of existing ones. However, we can mimic such a construct by combining the built-in [tuple() ](https://docs.python.org/3/library/functions.html#func-tuple) constructor with a generator expression.\n",
"\n",
"So, to convert the list comprehension `[(n ** 2) + 1 for n in numbers if n % 2 == 0]` from above into a \"tuple comprehension,\" we write the following."
]
},
{
"cell_type": "code",
"execution_count": 96,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"(65, 145, 5, 37, 101, 17)"
]
},
"execution_count": 96,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"tuple((n ** 2) + 1 for n in numbers if n % 2 == 0)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## Boolean Reducers"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Besides [min() ](https://docs.python.org/3/library/functions.html#min), [max() ](https://docs.python.org/3/library/functions.html#max), and [sum() ](https://docs.python.org/3/library/functions.html#sum), Python provides two boolean reduce functions: [all() ](https://docs.python.org/3/library/functions.html#all) and [any() ](https://docs.python.org/3/library/functions.html#any).\n",
"\n",
"Let's look at straightforward examples involving `numbers` again."
]
},
{
"cell_type": "code",
"execution_count": 97,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [],
"source": [
"numbers = [7, 11, 8, 5, 3, 12, 2, 6, 9, 10, 1, 4]"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"[all() ](https://docs.python.org/3/library/functions.html#all) takes an `iterable` argument and returns `True` if *all* elements are *truthy*.\n",
"\n",
"For example, let's check if the square of each element in `numbers` is below `100` or `150`, respectively. We express the computation with a generator expression passed as the only argument to [all() ](https://docs.python.org/3/library/functions.html#all)."
]
},
{
"cell_type": "code",
"execution_count": 98,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"False"
]
},
"execution_count": 98,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"all(x ** 2 < 100 for x in numbers)"
]
},
{
"cell_type": "code",
"execution_count": 99,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 99,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"all(x ** 2 < 150 for x in numbers)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"[all() ](https://docs.python.org/3/library/functions.html#all) can be viewed as syntactic sugar replacing a `for`-loop: Internally, [all() ](https://docs.python.org/3/library/functions.html#all) implements the *short-circuiting* strategy explained in [Chapter 3 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/03_conditionals_00_content.ipynb#Short-Circuiting), and we mimic that by testing for the *opposite* condition in the `if` statement and stopping the `for`-loop early with the `break` statement. In the worst case, if `threshold` were, for example, `150`, we would loop over *all* elements in the `iterable` argument, which must be *finite* for the code to work. So, [all() ](https://docs.python.org/3/library/functions.html#all) is a *linear search* in disguise."
]
},
{
"cell_type": "code",
"execution_count": 100,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"False"
]
},
"execution_count": 100,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"threshold = 100\n",
"\n",
"for number in numbers:\n",
" if number ** 2 >= threshold: # the opposite of what we are checking for\n",
" all_below_threshold = False\n",
" break\n",
"else:\n",
" all_below_threshold = True\n",
"\n",
"all_below_threshold"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"The documentation of [all() ](https://docs.python.org/3/library/functions.html#all) shows in another way what it does with code: By placing a `return` statement in a `for`-loop's body inside a function, iteration is stopped prematurely once an element does *not* meet the condition. That is the familiar *early exit* pattern at work."
]
},
{
"cell_type": "code",
"execution_count": 101,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"source": [
"def all_alt(iterable):\n",
" \"\"\"Alternative implementation of the built-in all() function.\"\"\"\n",
" for element in iterable:\n",
" if not element: # the opposite of what we are checking for\n",
" return False\n",
" return True"
]
},
{
"cell_type": "code",
"execution_count": 102,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"False"
]
},
"execution_count": 102,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"all_alt(x ** 2 < 100 for x in numbers)"
]
},
{
"cell_type": "code",
"execution_count": 103,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 103,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"all_alt(x ** 2 < 150 for x in numbers)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Similarly, [any() ](https://docs.python.org/3/library/functions.html#any) checks if *at least* one element in the `iterable` argument is *truthy*.\n",
"\n",
"To continue the example, let's check if the square of *any* element in `numbers` is above `100` or `150`, respectively."
]
},
{
"cell_type": "code",
"execution_count": 104,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 104,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"any(x ** 2 > 100 for x in numbers)"
]
},
{
"cell_type": "code",
"execution_count": 105,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"False"
]
},
"execution_count": 105,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"any(x ** 2 > 150 for x in numbers)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"The code cell below shows how [any() ](https://docs.python.org/3/library/functions.html#any) works internally: It also follows the *short-circuiting* strategy. Here, we do *not* need to check for the opposite condition."
]
},
{
"cell_type": "code",
"execution_count": 106,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 106,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"threshold = 100\n",
"\n",
"for number in numbers:\n",
" if number ** 2 > threshold:\n",
" any_above_threshold = True\n",
" break\n",
"else:\n",
" any_above_threshold = False\n",
"\n",
"any_above_threshold"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"The alternative formulation in the documentation of [any() ](https://docs.python.org/3/library/functions.html#any) is straightforward and also uses the early exit pattern."
]
},
{
"cell_type": "code",
"execution_count": 107,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"source": [
"def any_alt(iterable):\n",
" \"\"\"Alternative implementation of the built-in any() function.\"\"\"\n",
" for element in iterable:\n",
" if element:\n",
" return True\n",
" return False"
]
},
{
"cell_type": "code",
"execution_count": 108,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 108,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"any_alt(x ** 2 > 100 for x in numbers)"
]
},
{
"cell_type": "code",
"execution_count": 109,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"False"
]
},
"execution_count": 109,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"any_alt(x ** 2 > 150 for x in numbers)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## Iterators vs. Iterables"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Similarly to how we classify different *concrete* data types like `list` or `str` by how they behave *abstractly* in a given context in [Chapter 7 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/07_sequences_00_content.ipynb#Collections-vs.-Sequences), we also do so for the data types we have introduced in this chapter.\n",
"\n",
"Here, the `map`, `filter`, and `generator` types all behave like \"rules\" in memory that govern how objects are produced \"on the fly.\" Their main commonality is their support for the built-in [next() ](https://docs.python.org/3/library/functions.html#next) function. In computer science terminology, such data types are called **[iterators ](https://en.wikipedia.org/wiki/Iterator)**, and the [collections.abc ](https://docs.python.org/3/library/collections.abc.html) module formalizes them with the `Iterator` ABC in Python.\n",
"\n",
"So, one example of an iterator is `evens_transformed` below, an object of type `generator`."
]
},
{
"cell_type": "code",
"execution_count": 110,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [],
"source": [
"numbers = [7, 11, 8, 5, 3, 12, 2, 6, 9, 10, 1, 4]"
]
},
{
"cell_type": "code",
"execution_count": 111,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"source": [
"evens_transformed = ((x ** 2) + 1 for x in numbers if x % 2 == 0)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Let's first confirm that `evens_transformed` is indeed an `Iterator`, \"abstractly speaking.\""
]
},
{
"cell_type": "code",
"execution_count": 112,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"source": [
"import collections.abc as abc"
]
},
{
"cell_type": "code",
"execution_count": 113,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 113,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"isinstance(evens_transformed, abc.Iterator)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"In Python, iterators are *always* also iterables. The reverse is *not* true! To be precise, iterators are *specializations* of iterables. That is what the \"Inherits from\" column means in the [collections.abc ](https://docs.python.org/3/library/collections.abc.html) module's documentation."
]
},
{
"cell_type": "code",
"execution_count": 114,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 114,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"isinstance(evens_transformed, abc.Iterable)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Furthermore, we sharpen our definition of an *iterable* from [Chapter 7 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/07_sequences_00_content.ipynb#Collections-vs.-Sequences): Just as we defined an *iterator* to be any object that supports the [next() ](https://docs.python.org/3/library/functions.html#next) function, we define an *iterable* to be any object that supports the built-in [iter() ](https://docs.python.org/3/library/functions.html#iter) function.\n",
"\n",
"The confused reader may now be wondering how the two concepts relate to each other.\n",
"\n",
"In short, the [iter() ](https://docs.python.org/3/library/functions.html#iter) function is the general way to create an *iterator* object out of a given *iterable* object. Then, the *iterator* object manages the iteration over the *iterable* object. In real-world code, we hardly ever see [iter() ](https://docs.python.org/3/library/functions.html#iter) as Python calls it for us in the background. \n",
"\n",
"For illustration, let's do that ourselves and create *two* iterators out of the iterable `numbers` and see what we can do with them."
]
},
{
"cell_type": "code",
"execution_count": 115,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"iterator1 = iter(numbers)"
]
},
{
"cell_type": "code",
"execution_count": 116,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"iterator2 = iter(numbers)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"`iterator1` and `iterator2` are of type `list_iterator`."
]
},
{
"cell_type": "code",
"execution_count": 117,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"list_iterator"
]
},
"execution_count": 117,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"type(iterator1)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"*Iterators* are useful for only *one* thing: Get the next object from the associated *iterable*.\n",
"\n",
"By calling [next() ](https://docs.python.org/3/library/functions.html#next) three times with `iterator1` as the argument, we obtain the first three elements of `numbers`."
]
},
{
"cell_type": "code",
"execution_count": 118,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"(7, 11, 8)"
]
},
"execution_count": 118,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"next(iterator1), next(iterator1), next(iterator1)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"`iterator1` and `iterator2` keep their *states* separate. So, we could loop over an *iterable* in parallel."
]
},
{
"cell_type": "code",
"execution_count": 119,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"(5, 7)"
]
},
"execution_count": 119,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"next(iterator1), next(iterator2)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"We can also play a \"trick\" and exchange some elements in `numbers`. `iterator1` and `iterator2` do *not* see these changes and present us with the new elements. So, *iterators* not only have state on their own but also keep this separate from the underlying *iterable*."
]
},
{
"cell_type": "code",
"execution_count": 120,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"source": [
"numbers[1], numbers[4] = 99, 99"
]
},
{
"cell_type": "code",
"execution_count": 121,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"(99, 99)"
]
},
"execution_count": 121,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"next(iterator1), next(iterator2)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Let's re-assign the elements in `numbers` so that they are in order. Now, the numbers returned from [next() ](https://docs.python.org/3/library/functions.html#next) also tell us how often [next() ](https://docs.python.org/3/library/functions.html#next) was called with `iterator1` or `iterator2`. We conclude that `list_iterator` objects work by simply keeping track of the *last* index obtained from the underlying *iterable*."
]
},
{
"cell_type": "code",
"execution_count": 122,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"source": [
"numbers[:] = list(range(1, 13))"
]
},
{
"cell_type": "code",
"execution_count": 123,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]"
]
},
"execution_count": 123,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"numbers"
]
},
{
"cell_type": "code",
"execution_count": 124,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"(6, 3)"
]
},
"execution_count": 124,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"next(iterator1), next(iterator2)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"With the concepts introduced in this section, we can now understand the first sentence in the documentation on the [zip() ](https://docs.python.org/3/library/functions.html#zip) built-in better: \"Make an *iterator* that aggregates elements from each of the *iterables*.\"\n",
"\n",
"Because *iterators* are always also *iterables*, we may pass `iterator1` and `iterator2` as arguments to [zip() ](https://docs.python.org/3/library/functions.html#zip).\n",
"\n",
"The returned `zipper` object is of type `zip` and, \"abstractly speaking,\" an `Iterator` as well."
]
},
{
"cell_type": "code",
"execution_count": 125,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [],
"source": [
"zipper = zip(iterator1, iterator2)"
]
},
{
"cell_type": "code",
"execution_count": 126,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
""
]
},
"execution_count": 126,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"zipper"
]
},
{
"cell_type": "code",
"execution_count": 127,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"zip"
]
},
"execution_count": 127,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"type(zipper)"
]
},
{
"cell_type": "code",
"execution_count": 128,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 128,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"isinstance(zipper, abc.Iterator)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"So far, we have always used [zip() ](https://docs.python.org/3/library/functions.html#zip) in a `for`-loop. That was our earlier definition of an *iterable*. Our revised definition in this section states that an *iterable* is an object that supports the [iter() ](https://docs.python.org/3/library/functions.html#iter) function. So, let's see what happens if we pass `zipper` to [iter() ](https://docs.python.org/3/library/functions.html#iter)."
]
},
{
"cell_type": "code",
"execution_count": 129,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"source": [
"zipper_iterator = iter(zipper)"
]
},
{
"cell_type": "code",
"execution_count": 130,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
""
]
},
"execution_count": 130,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"zipper_iterator"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"`zipper_iterator` references the *same* object as `zipper`! That is true for *iterators* in general: Any *iterator* created from an existing *iterator* with [iter() ](https://docs.python.org/3/library/functions.html#iter) is the *iterator* itself."
]
},
{
"cell_type": "code",
"execution_count": 131,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 131,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"zipper is zipper_iterator"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"The Python core developers made that design decision so that *iterators* may also be looped over.\n",
"\n",
"The `for`-loop below prints out only *six* more `tuple` objects derived from the now ordered `numbers` because the `iterator1` object hidden inside `zipper` already returned its first *six* elements. So, the respective first elements of the `tuple` objects printed range from `7` to `12`. Similarly, as `iterator2` already returned its first *three* elements from `numbers`, we see the respective second elements in the range from `4` to `9`."
]
},
{
"cell_type": "code",
"execution_count": 132,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"7 and 4 8 and 5 9 and 6 10 and 7 11 and 8 12 and 9 "
]
}
],
"source": [
"for x, y in zipper:\n",
" print(x, \"and\", y, end=\" \")"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"`zipper` is now *exhausted*. So, the `for`-loop below does *not* make any iteration at all."
]
},
{
"cell_type": "code",
"execution_count": 133,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"source": [
"for x, y in zipper:\n",
" raise RuntimeError(\"We won't see this error\")"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"We verify that `iterator1` is exhausted by passing it to [next() ](https://docs.python.org/3/library/functions.html#next) again, which raises a `StopIteration` exception."
]
},
{
"cell_type": "code",
"execution_count": 134,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"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\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mnext\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0miterator1\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[0;31mStopIteration\u001b[0m: "
]
}
],
"source": [
"next(iterator1)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"On the contrary, `iterator2` is *not* yet exhausted."
]
},
{
"cell_type": "code",
"execution_count": 135,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"10"
]
},
"execution_count": 135,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"next(iterator2)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Understanding *iterators* and *iterables* is helpful for any data science practitioner that deals with large amounts of data. Even without that, these two terms occur everywhere in Python-related texts and documentation. So, a beginner should regularly review this section until it becomes second nature."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"### The `for` Statement (revisited)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"In [Chapter 4 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/04_iteration_00_content.ipynb#The-for-Statement), we argue that the `for` statement is syntactic sugar, replacing the `while` statement in many common scenarios. In particular, a `for`-loop saves us two tasks: Managing an index variable *and* obtaining the individual elements by indexing. In this sub-section, we look at a more realistic picture, using the new terminology as well.\n",
"\n",
"Let's print out the elements of a `list` object as the *iterable* being looped over."
]
},
{
"cell_type": "code",
"execution_count": 136,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [],
"source": [
"iterable = [0, 1, 2, 3, 4]"
]
},
{
"cell_type": "code",
"execution_count": 137,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"0 1 2 3 4 "
]
}
],
"source": [
"for element in iterable:\n",
" print(element, end=\" \")"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Our previous and equivalent formulation with a `while` statement is like so."
]
},
{
"cell_type": "code",
"execution_count": 138,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"0 1 2 3 4 "
]
}
],
"source": [
"index = 0\n",
"\n",
"while index < len(iterable):\n",
" element = iterable[index]\n",
" print(element, end=\" \")\n",
" index += 1\n",
"\n",
"del index"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"What actually happens behind the scenes in the Python interpreter is shown below.\n",
"\n",
"First, Python calls [iter() ](https://docs.python.org/3/library/functions.html#iter) with the `iterable` to be looped over as the argument. The returned `iterator` contains the entire logic of how the `iterable` is looped over. In particular, the `iterator` may or may not pick the `iterable`'s elements in a predictable order. That is up to the \"rule\" it models.\n",
"\n",
"Second, Python enters an *indefinite* `while`-loop. It tries to obtain the next element with [next() ](https://docs.python.org/3/library/functions.html#next). If that succeeds, the `for`-loop's code block is executed. Below, that code is placed within the `else`-clause that runs only if *no* exception is raised in the `try`-clause. Then, Python jumps into the next iteration and tries to obtain the next element from the `iterator`, and so on. Once the `iterator` is exhausted, it raises a `StopIteration` exception, and Python stops the `while`-loop with the `break` statement."
]
},
{
"cell_type": "code",
"execution_count": 139,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"0 1 2 3 4 "
]
}
],
"source": [
"iterator = iter(iterable)\n",
"\n",
"while True:\n",
" try:\n",
" element = next(iterator)\n",
" except StopIteration:\n",
" break\n",
" else:\n",
" print(element, end=\" \")\n",
"\n",
"del iterator"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"### sorted() vs. reversed()"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Now that we know the concept of an *iterator*, let's compare some of the built-ins introduced in [Chapter 7 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/07_sequences_00_content.ipynb) in detail and make sure we understand what is going on in memory. This sub-section also serves as a summary of all the concepts in this chapter.\n",
"\n",
"We use two simple examples, `numbers` and `memoryless`: `numbers` creates *thirteen* objects in memory and `memoryless` only *one* (cf., [PythonTutor ](http://pythontutor.com/visualize.html#code=numbers%20%3D%20%5B7,%2011,%208,%205,%203,%2012,%202,%206,%209,%2010,%201,%204%5D%0Amemoryless%20%3D%20range%281,%2013%29&cumulative=false&curInstr=2&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false))."
]
},
{
"cell_type": "code",
"execution_count": 140,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [],
"source": [
"numbers = [7, 11, 8, 5, 3, 12, 2, 6, 9, 10, 1, 4]"
]
},
{
"cell_type": "code",
"execution_count": 141,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"memoryless = range(1, 13)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"The [sorted() ](https://docs.python.org/3/library/functions.html#sorted) function takes a *finite* `iterable` argument and *materializes* its elements into a *new* `list` object that is returned.\n",
"\n",
"The argument may already be materialized, as is the case with `numbers`, but may also be an *iterable* without any objects in it, such as `memoryless`. In both cases, we end up with materialized `list` objects with the elements sorted in *forward* order (cf., [PythonTutor ](http://pythontutor.com/visualize.html#code=numbers%20%3D%20%5B7,%2011,%208,%205,%203,%2012,%202,%206,%209,%2010,%201,%204%5D%0Amemoryless%20%3D%20range%281,%2013%29%0Aresult1%20%3D%20sorted%28numbers%29%0Aresult2%20%3D%20sorted%28memoryless%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false))."
]
},
{
"cell_type": "code",
"execution_count": 142,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [
{
"data": {
"text/plain": [
"[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]"
]
},
"execution_count": 142,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"sorted(numbers)"
]
},
{
"cell_type": "code",
"execution_count": 143,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]"
]
},
"execution_count": 143,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"sorted(memoryless)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"By adding a keyword-only argument `reverse=True`, the materialized `list` objects are sorted in *reverse* order (cf., [PythonTutor ](http://pythontutor.com/visualize.html#code=numbers%20%3D%20%5B7,%2011,%208,%205,%203,%2012,%202,%206,%209,%2010,%201,%204%5D%0Amemoryless%20%3D%20range%281,%2013%29%0Aresult1%20%3D%20sorted%28numbers,%20reverse%3DTrue%29%0Aresult2%20%3D%20sorted%28memoryless,%20reverse%3DTrue%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false))."
]
},
{
"cell_type": "code",
"execution_count": 144,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"[12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]"
]
},
"execution_count": 144,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"sorted(numbers, reverse=True)"
]
},
{
"cell_type": "code",
"execution_count": 145,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"[12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]"
]
},
"execution_count": 145,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"sorted(memoryless, reverse=True)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"The order in `numbers` remains *unchanged*, and `memoryless` is still *not* materialized."
]
},
{
"cell_type": "code",
"execution_count": 146,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"[7, 11, 8, 5, 3, 12, 2, 6, 9, 10, 1, 4]"
]
},
"execution_count": 146,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"numbers"
]
},
{
"cell_type": "code",
"execution_count": 147,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"range(1, 13)"
]
},
"execution_count": 147,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"memoryless"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"The [reversed() ](https://docs.python.org/3/library/functions.html#reversed) built-in takes a `sequence` argument and returns an *iterator*. The argument must be *finite* and *reversible* (i.e., *iterable* in *reverse* order) as otherwise [reversed() ](https://docs.python.org/3/library/functions.html#reversed) could neither determine the last element that becomes the first nor loop in a *predictable* backward fashion. [PythonTutor ](http://pythontutor.com/visualize.html#code=numbers%20%3D%20%5B7,%2011,%208,%205,%203,%2012,%202,%206,%209,%2010,%201,%204%5D%0Amemoryless%20%3D%20range%281,%2013%29%0Aiterator1%20%3D%20reversed%28numbers%29%0Aiterator2%20%3D%20reversed%28memoryless%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false) confirms that [reversed() ](https://docs.python.org/3/library/functions.html#reversed) does *not* materialize any elements but only returns an *iterator*.\n",
"\n",
"**Side Note**: Even though `range` objects, like `memoryless` here, do *not* \"contain\" references to other objects, they count as *sequence* types, and as such, they are also *container* types. The `in` operator works with `range` objects because we can always cast the object to be checked as an `int` and check if that lies within the `range` object's `start` and `stop` values, taking a potential `step` value into account (cf., this [blog post](https://treyhunner.com/2018/02/python-range-is-not-an-iterator/) for more details on the [range() ](https://docs.python.org/3/library/functions.html#func-range) built-in)."
]
},
{
"cell_type": "code",
"execution_count": 148,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [
{
"data": {
"text/plain": [
""
]
},
"execution_count": 148,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"reversed(numbers)"
]
},
{
"cell_type": "code",
"execution_count": 149,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
""
]
},
"execution_count": 149,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"reversed(memoryless)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"To materialize the elements, we can pass the returned *iterators* to, for example, the [list() ](https://docs.python.org/3/library/functions.html#func-list) or [tuple() ](https://docs.python.org/3/library/functions.html#func-tuple) constructors. That creates *new* `list` and `tuple` objects (cf., [PythonTutor ](http://pythontutor.com/visualize.html#code=numbers%20%3D%20%5B7,%2011,%208,%205,%203,%2012,%202,%206,%209,%2010,%201,%204%5D%0Amemoryless%20%3D%20range%281,%2013%29%0Aresult1%20%3D%20list%28reversed%28numbers%29%29%0Aresult2%20%3D%20tuple%28reversed%28memoryless%29%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false)).\n",
"\n",
"To reiterate some more new terminology from this chapter, we describe [reversed() ](https://docs.python.org/3/library/functions.html#reversed) as *lazy* whereas [list() ](https://docs.python.org/3/library/functions.html#func-list) and [tuple() ](https://docs.python.org/3/library/functions.html#func-tuple) are *eager*. The former has no significant side effect in memory, while the latter may require a lot of memory."
]
},
{
"cell_type": "code",
"execution_count": 150,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"[4, 1, 10, 9, 6, 2, 12, 3, 5, 8, 11, 7]"
]
},
"execution_count": 150,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"list(reversed(numbers))"
]
},
{
"cell_type": "code",
"execution_count": 151,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"(12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)"
]
},
"execution_count": 151,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"tuple(reversed(memoryless))"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Of course, we can also loop over the returned *iterators* instead.\n",
"\n",
"That works because *iterators* are always *iterable*; in particular, as the previous \"*The for Statement (revisited)*\" sub-section explains, the `for`-loops below call `iter(reversed(numbers))` and `iter(reversed(memoryless))` behind the scenes. However, the *iterators* returned by [iter() ](https://docs.python.org/3/library/functions.html#iter) are the *same* as the `reversed(numbers)` and `reversed(memoryless)` iterators passed in! In summary, the `for`-loops below involve many subtleties that together make Python the expressive language it is."
]
},
{
"cell_type": "code",
"execution_count": 152,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"4 1 10 9 6 2 12 3 5 8 11 7 "
]
}
],
"source": [
"for number in reversed(numbers):\n",
" print(number, end=\" \")"
]
},
{
"cell_type": "code",
"execution_count": 153,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"12 11 10 9 8 7 6 5 4 3 2 1 "
]
}
],
"source": [
"for element in reversed(memoryless):\n",
" print(element, end=\" \")"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"As with [sorted() ](https://docs.python.org/3/library/functions.html#sorted), the [reversed() ](https://docs.python.org/3/library/functions.html#reversed) built-in does *not* mutate its argument."
]
},
{
"cell_type": "code",
"execution_count": 154,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [
{
"data": {
"text/plain": [
"[7, 11, 8, 5, 3, 12, 2, 6, 9, 10, 1, 4]"
]
},
"execution_count": 154,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"numbers"
]
},
{
"cell_type": "code",
"execution_count": 155,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"range(1, 13)"
]
},
"execution_count": 155,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"memoryless"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"To point out the potentially obvious, we compare the results of *sorting* `numbers` in *reverse* order with *reversing* it: These are *different* concepts!"
]
},
{
"cell_type": "code",
"execution_count": 156,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"[7, 11, 8, 5, 3, 12, 2, 6, 9, 10, 1, 4]"
]
},
"execution_count": 156,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"numbers"
]
},
{
"cell_type": "code",
"execution_count": 157,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"[12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]"
]
},
"execution_count": 157,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"sorted(numbers, reverse=True)"
]
},
{
"cell_type": "code",
"execution_count": 158,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"[4, 1, 10, 9, 6, 2, 12, 3, 5, 8, 11, 7]"
]
},
"execution_count": 158,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"list(reversed(numbers))"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Whereas both [sorted() ](https://docs.python.org/3/library/functions.html#sorted) and [reversed() ](https://docs.python.org/3/library/functions.html#reversed) do *not* mutate their arguments, the *mutable* `list` type comes with two methods, [sort() ](https://docs.python.org/3/library/stdtypes.html#list.sort) and `reverse()`, that implement the same logic but mutate an object, like `numbers` below, *in place*. To indicate that all changes occur *in place*, the [sort() ](https://docs.python.org/3/library/stdtypes.html#list.sort) and `reverse()` methods always return `None`, which is not shown in JupyterLab."
]
},
{
"cell_type": "code",
"execution_count": 159,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"[7, 11, 8, 5, 3, 12, 2, 6, 9, 10, 1, 4]"
]
},
"execution_count": 159,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"numbers"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"The `reverse()` method on the `list` type is *eager*, as opposed to the *lazy* [reversed() ](https://docs.python.org/3/library/functions.html#reversed) built-in. That means the mutations caused by the `reverse()` method are written into memory right away."
]
},
{
"cell_type": "code",
"execution_count": 160,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"source": [
"numbers.reverse()"
]
},
{
"cell_type": "code",
"execution_count": 161,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"[4, 1, 10, 9, 6, 2, 12, 3, 5, 8, 11, 7]"
]
},
"execution_count": 161,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"numbers"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"*Sorting* `numbers` in place occurs eagerly."
]
},
{
"cell_type": "code",
"execution_count": 162,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"source": [
"numbers.sort(reverse=True)"
]
},
{
"cell_type": "code",
"execution_count": 163,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"[12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]"
]
},
"execution_count": 163,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"numbers"
]
},
{
"cell_type": "code",
"execution_count": 164,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"source": [
"numbers.sort()"
]
},
{
"cell_type": "code",
"execution_count": 165,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]"
]
},
"execution_count": 165,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"numbers"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"## TL;DR"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"The operations we do with sequential data commonly follow the **map-filter-reduce paradigm**: We apply the same transformation to all elements, filter some of them out, and calculate summary statistics from the remaining ones.\n",
"\n",
"An essential idea in this chapter is that, in many situations, we need *not* have all the data **materialized** in memory. Instead, **iterators** allow us to process sequential data on a one-by-one basis.\n",
"\n",
"Examples for iterators are the `map`, `filter`, and `generator` types."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"## Further Resources"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"A lecture-style **video presentation** of this chapter is integrated below (cf., the [video ](https://www.youtube.com/watch?v=ePzzq2YBWjY&list=PL-2JV1G3J10lQ2xokyQowcRJI5jjNfW7f) or the entire [playlist ](https://www.youtube.com/playlist?list=PL-2JV1G3J10lQ2xokyQowcRJI5jjNfW7f))."
]
},
{
"cell_type": "code",
"execution_count": 166,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"image/jpeg": "/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAUDBAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIChALCAgOCggIDRYNDhERExMTCAsWGBYSGBASExIBBQUFCAcIDwkJDxgVERUWFxcYExMYGBgVFRgWFRYWGBcVGxIaEhMXFRoYGBISFRcVFRUVFRUVFRUVGBUSFxIVFf/AABEIAWgB4AMBIgACEQEDEQH/xAAdAAEAAgIDAQEAAAAAAAAAAAAABgcFCAIDBAEJ/8QAURAAAQQBAgMBCQgRAgUEAgMAAQACAwQRBRIGEyExBxQYIjJBVZTVCBUXUVJhk9QjMzVCU1RxcnN0dYGSsbKz05G0FiQ2YrU0gqGiY3YlQ0X/xAAcAQEAAgMBAQEAAAAAAAAAAAAAAgQBAwUHBgj/xAA9EQABAwIDBQUGBAYBBQEAAAABAAIRAyEEEjEFQVFhcRMVU4GRBiIyocHwFjRysRQ1QlLR4fEjM0NigpL/2gAMAwEAAhEDEQA/ANMkREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREWzPgVcVfj/D/AK1qPs5PAq4q/H+H/WtR9nIi1mRbM+BVxV+P8P8ArWo+zk8Crir8f4f9a1H2ciLWZFsz4FXFX4/w/wCtaj7OTwKuKvx/h/1rUfZyItZkWzPgVcVfj/D/AK1qPs5PAq4q/H+H/WtR9nIi1mRbM+BVxV+P8P8ArWo+zk8Crir8f4f9a1H2ciLWZFsz4FXFX4/w/wCtaj7OTwKuKvx/h/1rUfZyItZkWzPgVcVfj/D/AK1qPs5PAq4q/H+H/WtR9nIi1mRbM+BVxV+P8P8ArWo+zk8Crir8f4f9a1H2ciLWZFsz4FXFX4/w/wCtaj7OTwKuKvx/h/1rUfZyItZkWzPgVcVfj/D/AK1qPs5PAq4q/H+H/WtR9nIi1mRbM+BVxV+P8P8ArWo+zk8Crir8f4f9a1H2ciLWZFsz4FXF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hodQ0EBkkco21dQ8qJ4e0HOodmWhZBgrB0X6DItAPDV4q9H8P+q6j7RTw1eKvR/D/quo+0VhZW/6LQDw1eKvR/D/AKrqPtFPDV4q9H8P+q6j7RRFv+i0A8NXir0fw/6rqPtFPDV4q9H8P+q6j7RRFv8AotAPDV4q9H8P+q6j7RTw1eKvR/D/AKrqPtFEW/6LQDw1eKvR/D/quo+0U8NXir0fw/6rqPtFEW/6LQDw1eKvR/D/AKrqPtFPDV4q9H8P+q6j7RRFv+i0A8NXir0fw/6rqPtFPDV4q9H8P+q6j7RRFv8AotAPDV4q9H8P+q6j7RTw1eKvR/D/AKrqPtFEW/6LQDw1eKvR/D/quo+0U8NXir0fw/6rqPtFEW/6LQDw1eKvR/D/AKrqPtFPDV4q9H8P+q6j7RRFv+i0A8NXir0fw/6rqPtFPDV4q9H8P+q6j7RRFv8AotAPDV4q9H8P+q6j7RTw1eKvR/D/AKrqPtFEW/6LQDw1eKvR/D/quo+0U8NXir0fw/6rqPtFEW/682q/aJ/0Mn9DloT4avFXo/h/1XUfaK4WPdo8UvY9hoaBh7XNOKuo5w4EHGdR7eqItaURERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERf/2Q==\n",
"text/html": [
"\n",
" VIDEO \n",
" "
],
"text/plain": [
""
]
},
"execution_count": 166,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from IPython.display import YouTubeVideo\n",
"YouTubeVideo(\"ePzzq2YBWjY\", width=\"60%\")"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.4"
},
"livereveal": {
"auto_select": "code",
"auto_select_fragment": true,
"scroll": true,
"theme": "serif"
},
"toc": {
"base_numbering": 1,
"nav_menu": {},
"number_sections": false,
"sideBar": true,
"skip_h1_title": true,
"title_cell": "Table of Contents",
"title_sidebar": "Contents",
"toc_cell": false,
"toc_position": {
"height": "calc(100% - 180px)",
"left": "10px",
"top": "150px",
"width": "384px"
},
"toc_section_display": false,
"toc_window_display": false
}
},
"nbformat": 4,
"nbformat_minor": 4
}