{ "cells": [ { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# Chapter 8: Mappings & Sets" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "While [Chapter 7 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/07_sequences_00_content.ipynb) focuses on one special kind of *collection* types, namely *sequences*, this chapter introduces two more kinds: **Mappings** and **sets**. Both are presented in this chapter as they share the *same* underlying implementation.\n", "\n", "The `dict` type (cf, [documentation ](https://docs.python.org/3/library/stdtypes.html#dict)) introduced in the next section is an essential part in a data scientist's toolbox for two reasons: First, Python employs `dict` objects basically \"everywhere\" internally. Second, after the many concepts involving *sequential* data, *mappings* provide a different perspective on data and enhance our general problem solving skills." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## The `dict` Type" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "A *mapping* is a one-to-one correspondence from a set of **keys** to a set of **values**. In other words, a *mapping* is a *collection* of **key-value pairs**, also called **items** for short.\n", "\n", "In the context of mappings, the term *value* has a meaning different from the *value* every object has: In the \"bag\" analogy from [Chapter 1 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements_00_content.ipynb#Value-/-\"Meaning\"), we describe an object's value to be the semantic meaning of the $0$s and $1$s it contains. Here, the terms *key* and *value* mean the *role* an object takes within a mapping. Both, *keys* and *values*, are *objects* on their own with distinct *values*.\n", "\n", "Let's continue with an example. To create a `dict` object, we commonly use the literal notation, `{..: .., ..: .., ...}`, and list all the items. `to_words` below maps the `int` objects `0`, `1`, and `2` to their English word equivalents, `\"zero\"`, `\"one\"`, and `\"two\"`, and `from_words` does the opposite. A stylistic side note: Pythonistas often expand `dict` or `list` definitions by writing each item or element on a line on their own. Also, the commas `,` after the respective *last* items, `2: \"two\"` and `\"two\": 2`, are *not* a mistake although they *may* be left out. Besides easier reading, such a style has technical advantages that we do not go into detail about here (cf., [source ](https://www.python.org/dev/peps/pep-0008/#when-to-use-trailing-commas))." ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [], "source": [ "to_words = {\n", " 0: \"zero\",\n", " 1: \"one\",\n", " 2: \"two\",\n", "}" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "from_words = {\n", " \"zero\": 0,\n", " \"one\": 1,\n", " \"two\": 2,\n", "}" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "As before, `dict` objects are objects on their own: They have an identity, a type, and a value." ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "text/plain": [ "140024292070576" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "id(to_words)" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "text/plain": [ "dict" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "type(to_words)" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [ { "data": { "text/plain": [ "{0: 'zero', 1: 'one', 2: 'two'}" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "to_words" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "text/plain": [ "140024292122864" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "id(from_words)" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "text/plain": [ "dict" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "type(from_words)" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "{'zero': 0, 'one': 1, 'two': 2}" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from_words" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "The built-in [dict() ](https://docs.python.org/3/library/functions.html#func-dict) constructor gives us an alternative way to create a `dict` object. It is versatile and can be used in different ways.\n", "\n", "First, we may pass it any *mapping* type, for example, a `dict` object, to obtain a *new* `dict` object. That is the easiest way to obtain a *shallow* copy of a `dict` object or convert any other mapping object into a `dict` one." ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "text/plain": [ "{'zero': 0, 'one': 1, 'two': 2}" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "dict(from_words)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "Second, we may pass it a *finite* `iterable` providing *iterables* with *two* elements each. So, both of the following two code cells work: A `list` of `tuple` objects, or a `tuple` of `list` objects. More importantly, we could use an *iterator*, for example, a `generator` object, that produces the inner iterables \"on the fly.\"" ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [ { "data": { "text/plain": [ "{'zero': 0, 'one': 1, 'two': 2}" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "dict([(\"zero\", 0), (\"one\", 1), (\"two\", 2)])" ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "text/plain": [ "{'zero': 0, 'one': 1, 'two': 2}" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "dict(([\"zero\", 0], [\"one\", 1], [\"two\", 2]))" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "Lastly, [dict() ](https://docs.python.org/3/library/functions.html#func-dict) may also be called with *keyword* arguments: The keywords become the keys and the arguments the values." ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "{'zero': 0, 'one': 1, 'two': 2}" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "dict(zero=0, one=1, two=2)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Nested Data" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "Often, `dict` objects occur in a nested form and combined with other collection types, such as `list` or `tuple` objects, to model more complex entities \"from the real world.\"\n", "\n", "The reason for this popularity is that many modern [ReST APIs ](https://en.wikipedia.org/wiki/Representational_state_transfer#Applied_to_Web_services) on the internet (e.g., [Google Maps API](https://cloud.google.com/maps-platform/), [Yelp API](https://www.yelp.com/developers/documentation/v3), [Twilio API](https://www.twilio.com/docs/usage/api)) provide their data in the popular [JSON ](https://en.wikipedia.org/wiki/JSON) format, which looks almost like a combination of `dict` and `list` objects in Python. \n", "\n", "The `people` example below models three groups of people: `\"mathematicians\"`, `\"physicists\"`, and `\"programmers\"`. Each person may have an arbitrary number of email addresses. In the example, [Leonhard Euler ](https://en.wikipedia.org/wiki/Leonhard_Euler) has not lived long enough to get one whereas [Guido ](https://en.wikipedia.org/wiki/Guido_van_Rossum) has more than one.\n", "\n", "`people` makes many implicit assumptions about the structure of the data. For example, there are a [one-to-many ](https://en.wikipedia.org/wiki/One-to-many_%28data_model%29) relationship between a person and their email addresses and a [one-to-one ](https://en.wikipedia.org/wiki/One-to-one_%28data_model%29) relationship between each person and their name." ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [], "source": [ "people = {\n", " \"mathematicians\": [\n", " {\n", " \"name\": \"Gilbert Strang\",\n", " \"emails\": [\"gilbert@mit.edu\"],\n", " },\n", " {\n", " \"name\": \"Leonhard Euler\",\n", " \"emails\": [],\n", " },\n", " ],\n", " \"physicists\": [],\n", " \"programmers\": [\n", " {\n", " \"name\": \"Guido\",\n", " \"emails\": [\"guido@python.org\", \"guido@dropbox.com\"],\n", " },\n", " ],\n", "}" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "The literal notation of such a nested `dict` object may be hard to read ..." ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [ { "data": { "text/plain": [ "{'mathematicians': [{'name': 'Gilbert Strang', 'emails': ['gilbert@mit.edu']},\n", " {'name': 'Leonhard Euler', 'emails': []}],\n", " 'physicists': [],\n", " 'programmers': [{'name': 'Guido',\n", " 'emails': ['guido@python.org', 'guido@dropbox.com']}]}" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "people" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "... but the [pprint ](https://docs.python.org/3/library/pprint.html) module in the [standard library ](https://docs.python.org/3/library/index.html) provides a [pprint() ](https://docs.python.org/3/library/pprint.html#pprint.pprint) function for \"pretty printing.\"" ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [], "source": [ "from pprint import pprint" ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{'mathematicians': [{'emails': ['gilbert@mit.edu'],\n", " 'name': 'Gilbert Strang'},\n", " {'emails': [],\n", " 'name': 'Leonhard Euler'}],\n", " 'physicists': [],\n", " 'programmers': [{'emails': ['guido@python.org',\n", " 'guido@dropbox.com'],\n", " 'name': 'Guido'}]}\n" ] } ], "source": [ "pprint(people, indent=1, width=60)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Hash Tables & (Key) Hashability" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "In [Chapter 0 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/00_intro_00_content.ipynb#Isn't-C-a-lot-faster?), we argue that a major advantage of using Python is that it takes care of the memory managment for us. In line with that, we have never talked about the C level implementation thus far in the book. However, the `dict` type, among others, exhibits some behaviors that may seem \"weird\" for a beginner. To build some intuition, we describe the underlying implementation details on a conceptual level.\n", "\n", "The first unintuitive behavior is that we may *not* use a *mutable* object as a key. That results in a `TypeError`." ] }, { "cell_type": "code", "execution_count": 17, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [ { "ename": "TypeError", "evalue": "unhashable type: 'list'", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 1\u001b[0m {\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0;34m[\u001b[0m\u001b[0;34m\"zero\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"one\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 3\u001b[0m }\n", "\u001b[0;31mTypeError\u001b[0m: unhashable type: 'list'" ] } ], "source": [ "{\n", " [\"zero\", \"one\"]: [0, 1],\n", "}" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "Similarly surprising is that items with the *same* key get merged together. The resulting `dict` object keeps the position of the *first* mention of the `\"zero\"` key while only the *last* mention of the corresponding values, `999`, survives." ] }, { "cell_type": "code", "execution_count": 18, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [ { "data": { "text/plain": [ "{'zero': 999, 'one': 1, 'two': 2}" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "{\n", " \"zero\": 0,\n", " \"one\": 1,\n", " \"two\": 2,\n", " \"zero\": 999, # to illustrate a point\n", "}" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "The reason for that is that the `dict` type is implemented with so-called [hash tables ](https://en.wikipedia.org/wiki/Hash_table).\n", "\n", "Conceptually, when we create a *new* `dict` object, Python creates a \"bag\" in memory that takes significantly more space than needed to store the references to all the key and value objects. This bag is a **contiguous array** similar to the `list` type's implementation. Whereas in the `list` case the array is divided into equally sized *slots* capable of holding *one* reference, a `dict` object's array is divided into equally sized **buckets** with enough space to store *two* references each: One for an item's key and one for the mapped value. The buckets are labeled with *index* numbers. Because Python knows how wide each bucket, it can jump directly into *any* bucket by calculating its *offset* from the start.\n", "\n", "The figure below visualizes how we should think of hash tables. An empty `dict` object, created with the literal `{}`, still takes a lot of memory: It is essentially one big, contiguous, and empty table." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "| Bucket | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |\n", "| :---: |:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|\n", "| **Key** |*...*|*...*|*...*|*...*|*...*|*...*|*...*|*...*|\n", "|**Value**|*...*|*...*|*...*|*...*|*...*|*...*|*...*|*...*|" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "To insert a key-value pair, the key must be translated into a bucket's index.\n", "\n", "As the first step to do so, the built-in [hash() ](https://docs.python.org/3/library/functions.html#hash) function maps any **hashable** object to its **hash value**, a long and \"random\" `int` number, similar to the ones returned by the built-in [id() ](https://docs.python.org/3/library/functions.html#id) function. This hash value is a *summary* of all the $0$s and $1$s inside the object.\n", "\n", "According to the official [glossary ](https://docs.python.org/3/glossary.html#term-hashable), an object is hashable *only if* \"it has a hash value which *never* changes during its *lifetime*.\" So, hashability implies immutability! Without this formal requirement an object may end up in *different* buckets depending on its current value. As the name of the `dict` type (i.e., \"dictionary\") suggests, a primary purpose of it is to insert objects and look them up later on. Without a *unique* bucket, this is of course not doable. The exact logic behind [hash() ](https://docs.python.org/3/library/functions.html#hash) is beyond the scope of this book.\n", "\n", "Let's calculate the hash value of `\"zero\"`, an immutable `str` object. Hash values have *no* semantic meaning. Also, every time we re-start Python, we see *different* hash values for the *same* objects. That is a security measure, and we do not go into the technicalities here (cf. [source ](https://docs.python.org/3/using/cmdline.html#envvar-PYTHONHASHSEED))." ] }, { "cell_type": "code", "execution_count": 19, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [ { "data": { "text/plain": [ "2513450610271817024" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "hash(\"zero\")" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "For numeric objects, we can sometimes predict the hash values. However, we must *never* interpret any meaning into them." ] }, { "cell_type": "code", "execution_count": 20, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "0" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "hash(0)" ] }, { "cell_type": "code", "execution_count": 21, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "text/plain": [ "0" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "hash(0.0)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "The [glossary ](https://docs.python.org/3/glossary.html#term-hashable) states a second requirement for hashability, namely that \"objects which *compare equal* must have the *same* hash value.\" The purpose of this is to ensure that if we put, for example, `1` as a key in a `dict` object, we can look it up later with `1.0`. In other words, we can look up keys by their object's semantic value. The converse statement does *not* hold: Two objects *may* (accidentally) have the *same* hash value and *not* compare equal. However, that rarely happens." ] }, { "cell_type": "code", "execution_count": 22, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 22, "metadata": {}, "output_type": "execute_result" } ], "source": [ "1 == 1.0" ] }, { "cell_type": "code", "execution_count": 23, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ "hash(1) == hash(1.0)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "Because `list` objects are not immutable, they are *never* hashable, as indicated by the `TypeError`." ] }, { "cell_type": "code", "execution_count": 24, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [ { "ename": "TypeError", "evalue": "unhashable type: 'list'", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mhash\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m\"zero\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"one\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mTypeError\u001b[0m: unhashable type: 'list'" ] } ], "source": [ "hash([\"zero\", \"one\"])" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "If we need keys composed of several objects, we can use `tuple` objects instead." ] }, { "cell_type": "code", "execution_count": 25, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "4408973713070187748" ] }, "execution_count": 25, "metadata": {}, "output_type": "execute_result" } ], "source": [ "hash((\"zero\", \"one\"))" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "There is no such restiction on objects inserted into `dict` objects as *values*." ] }, { "cell_type": "code", "execution_count": 26, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "{('zero', 'one'): [0, 1]}" ] }, "execution_count": 26, "metadata": {}, "output_type": "execute_result" } ], "source": [ "{\n", " (\"zero\", \"one\"): [0, 1],\n", "}" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "After obtaining the key object's hash value, Python must still convert that into a bucket index. We do not cover this step in technical detail but provide a conceptual description of it.\n", "\n", "The `buckets()` function below shows how we can obtain indexes from the binary representation of a hash value by simply extracting its least significant `bits` and interpreting them as index numbers. Alternatively, the hash value may also be divided with the `%` operator by the number of available buckets. We show this idea in the `buckets_alt()` function that takes the number of buckets, `n_buckets`, as its second argument." ] }, { "cell_type": "code", "execution_count": 27, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [], "source": [ "def buckets(mapping, *, bits):\n", " \"\"\"Calculate the bucket indices for a mapping's keys.\"\"\"\n", " for key in mapping: # cf., next section for details on looping\n", " hash_value = hash(key)\n", " binary = bin(hash_value)\n", " address = binary[-bits:]\n", " bucket = int(\"0b\" + address, base=2)\n", " print(key, hash_value, \"0b...\" + binary[-8:], address, bucket, sep=\"\\t\")" ] }, { "cell_type": "code", "execution_count": 28, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "def buckets_alt(mapping, *, n_buckets):\n", " \"\"\"Calculate the bucket indices for a mapping's keys.\"\"\"\n", " for key in mapping: # cf., next section for details on looping\n", " hash_value = hash(key)\n", " bucket = hash_value % n_buckets\n", " print(key, hash_value, bucket, sep=\"\\t\")" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "With an infinite number of possible keys being mapped to a limited number of buckets, there is a realistic chance that two or more keys end up in the *same* bucket. That is called a **hash collision**. In such cases, Python uses a perturbation rule to rearrange the bits, and if the corresponding next bucket is empty, places an item there. Then, the nice offsetting logic from above breaks down and Python needs more time on average to place items into a hash table or look them up. The remedy is to use a bigger hash table as then the chance of collisions decreases. Python does all that for us in the background, and the main cost we pay for that is a *high* memory usage of `dict` objects in general.\n", "\n", "Because keys with the *same* semantic value have the *same* hash value, they end up in the *same* bucket. That is why the item that gets inserted last *overwrites* all previously inserted items whose keys compare equal, as we saw with the two `\"zero\"` keys above.\n", "\n", "Thus, to come up with indexes for 4 buckets, we need to extract 2 bits from the hash value (i.e., $2^2 = 4$)." ] }, { "cell_type": "code", "execution_count": 29, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "zero\t2513450610271817024\t0b...01000000\t00\t0\n", "one\t-153603400453665003\t0b...11101011\t11\t3\n", "two\t1981459398511907500\t0b...10101100\t00\t0\n" ] } ], "source": [ "buckets(from_words, bits=2)" ] }, { "cell_type": "code", "execution_count": 30, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "zero\t2513450610271817024\t0\n", "one\t-153603400453665003\t1\n", "two\t1981459398511907500\t0\n" ] } ], "source": [ "buckets_alt(from_words, n_buckets=4)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "Similarly, 3 bits provide indexes for 8 buckets (i.e., $2^3 = 8$) ..." ] }, { "cell_type": "code", "execution_count": 31, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "zero\t2513450610271817024\t0b...01000000\t000\t0\n", "one\t-153603400453665003\t0b...11101011\t011\t3\n", "two\t1981459398511907500\t0b...10101100\t100\t4\n" ] } ], "source": [ "buckets(from_words, bits=3)" ] }, { "cell_type": "code", "execution_count": 32, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "zero\t2513450610271817024\t0\n", "one\t-153603400453665003\t5\n", "two\t1981459398511907500\t4\n" ] } ], "source": [ "buckets_alt(from_words, n_buckets=8)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "... while 4 bits do so for 16 buckets (i.e., $2^4 = 16$)." ] }, { "cell_type": "code", "execution_count": 33, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "zero\t2513450610271817024\t0b...01000000\t0000\t0\n", "one\t-153603400453665003\t0b...11101011\t1011\t11\n", "two\t1981459398511907500\t0b...10101100\t1100\t12\n" ] } ], "source": [ "buckets(from_words, bits=4)" ] }, { "cell_type": "code", "execution_count": 34, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "zero\t2513450610271817024\t0\n", "one\t-153603400453665003\t5\n", "two\t1981459398511907500\t12\n" ] } ], "source": [ "buckets_alt(from_words, n_buckets=16)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "Python allocates the memory for a `dict` object's hash table according to some internal heuristics: Whenever a hash table is roughly 2/3 full, it creates a *new* one with twice the space, and re-inserts all items, one by one, from the *old* one. So, during its lifetime, a `dict` object may have several hash tables.\n", "\n", "Although hash tables seem quite complex at first sight, they help us to make certain operations very fast as we see further below." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Mappings are Collections without Predictable Order" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "In [Chapter 7 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/07_sequences_00_content.ipynb#Collections-vs.-Sequences), we see how a *sequence* is a special kind of a *collection*, and that *collections* can be described as\n", "- *iterable*\n", "- *containers*\n", "- with a *finite* number of elements.\n", "\n", "The `dict` type is another *collection* type. So, it has the three properties as well.\n", "\n", "For example, we may pass `to_words` or `from_words` to the built-in [len() ](https://docs.python.org/3/library/functions.html#len) function to obtain the number of *items* they contain. In the terminology of the [collections.abc ](https://docs.python.org/3/library/collections.abc.html) module in the [standard library ](https://docs.python.org/3/library/index.html), both are `Sized` objects." ] }, { "cell_type": "code", "execution_count": 35, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "text/plain": [ "3" ] }, "execution_count": 35, "metadata": {}, "output_type": "execute_result" } ], "source": [ "len(to_words)" ] }, { "cell_type": "code", "execution_count": 36, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [ { "data": { "text/plain": [ "3" ] }, "execution_count": 36, "metadata": {}, "output_type": "execute_result" } ], "source": [ "len(from_words)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "Also, `dict` objects may be looped over, for example, with the `for` statement. So, in the terminology of the [collections.abc ](https://docs.python.org/3/library/collections.abc.html) module, they are `Iterable` objects.\n", "\n", "For technical reasons, we must *not* rely on the iteration order to be *predictable* in any form until Python 3.7 in 2018. Looping over the *same* `dict` objects multiple times during its lifetime could result in *different* iteration orders every time. That behavior is intentional as `dict` objects are optimized for use cases where order does not matter. Starting with Python 3.7, `dict` objects remember the order in that items are *inserted* (cf., [Python 3.7 release notes ](https://www.python.org/downloads/release/python-370/)).\n", "\n", "Because of that, the order in the two `for`-loops below is the *same* as in the *source code* that defines `to_words` and `from_words` above. In that sense, it is \"*predictable*.\" However, if we fill `dict` objects with data from real-world sources, that kind of predictability is not helpful as such data are not written as source code, and, thus, we consider the order of items in `dict` objects to be *unpredictable*." ] }, { "cell_type": "code", "execution_count": 37, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Python 3.7.4\n" ] } ], "source": [ "!python --version # the order in the for-loops is predictable only for Python 3.7 or higher" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "By convention, iteration goes over the *keys* in the `dict` object only. The \"*Dictionary Methods*\" section below shows how to loop over the *items* or the *values* instead." ] }, { "cell_type": "code", "execution_count": 38, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0\n", "1\n", "2\n" ] } ], "source": [ "for number in to_words:\n", " print(number)" ] }, { "cell_type": "code", "execution_count": 39, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "zero\n", "one\n", "two\n" ] } ], "source": [ "for word in from_words:\n", " print(word)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "Without a predictable *forward* order, `dict` objects are not *reversible* either. So, passing a `dict` object to the [reversed() ](https://docs.python.org/3/library/functions.html#reversed) built-in raises a `TypeError`. In terminology of the [collections.abc ](https://docs.python.org/3/library/collections.abc.html) module, they are *no* `Reversible` objects." ] }, { "cell_type": "code", "execution_count": 40, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "ename": "TypeError", "evalue": "'dict' object is not reversible", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0;32mfor\u001b[0m \u001b[0mnumber\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mreversed\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mto_words\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 2\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnumber\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;31mTypeError\u001b[0m: 'dict' object is not reversible" ] } ], "source": [ "for number in reversed(to_words):\n", " print(number)" ] }, { "cell_type": "code", "execution_count": 41, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "ename": "TypeError", "evalue": "'dict' object is not reversible", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0;32mfor\u001b[0m \u001b[0mword\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mreversed\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfrom_words\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 2\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mword\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;31mTypeError\u001b[0m: 'dict' object is not reversible" ] } ], "source": [ "for word in reversed(from_words):\n", " print(word)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "Of course, we could use the built-in [sorted() ](https://docs.python.org/3/library/functions.html#sorted) function to loop over, for example, `from_words` in a *predictable* order. However, that creates a temporary `list` object in memory and an order that has *nothing* to do with how the items are ordered inside the hash table." ] }, { "cell_type": "code", "execution_count": 42, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "one\n", "two\n", "zero\n" ] } ], "source": [ "for word in sorted(from_words):\n", " print(word)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "To show the `Container` behavior of *collection* types, we use the boolean `in` operator to check if a given object evaluates equal to a *key* in `to_words` or `from_words`." ] }, { "cell_type": "code", "execution_count": 43, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 43, "metadata": {}, "output_type": "execute_result" } ], "source": [ "1.0 in to_words # 1.0 is not a key but compares equal to a key" ] }, { "cell_type": "code", "execution_count": 44, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "text/plain": [ "False" ] }, "execution_count": 44, "metadata": {}, "output_type": "execute_result" } ], "source": [ "-1 in to_words" ] }, { "cell_type": "code", "execution_count": 45, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 45, "metadata": {}, "output_type": "execute_result" } ], "source": [ "\"one\" in from_words" ] }, { "cell_type": "code", "execution_count": 46, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "text/plain": [ "False" ] }, "execution_count": 46, "metadata": {}, "output_type": "execute_result" } ], "source": [ "\"ten\" in from_words" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "#### Membership Testing: Lists vs. Dictionaries" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "Because of the [hash table ](https://en.wikipedia.org/wiki/Hash_table) implementation, the `in` operator is *extremely* fast: Python does *not* need to initiate a [linear search ](https://en.wikipedia.org/wiki/Linear_search) as in the `list` case but immediately knows the only places in memory where the searched object must be located if present in the hash table at all. Conceptually, that is like comparing the searched object against *all* key objects with the `==` operator *without* doing it.\n", "\n", "To show the speed, we run an experiment. We create a `haystack`, a `list` object, with `10_000_001` elements in it, *one* of which is the `needle`, namely `42`. Once again, the [randint() ](https://docs.python.org/3/library/random.html#random.randint) function in the [random ](https://docs.python.org/3/library/random.html) module is helpful." ] }, { "cell_type": "code", "execution_count": 47, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [], "source": [ "import random" ] }, { "cell_type": "code", "execution_count": 48, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "random.seed(87)" ] }, { "cell_type": "code", "execution_count": 49, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "needle = 42" ] }, { "cell_type": "code", "execution_count": 50, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "haystack = [random.randint(99, 9999) for _ in range(10_000_000)]\n", "haystack.append(needle)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "We put the elements in `haystack` in a *random* order with the [shuffle() ](https://docs.python.org/3/library/random.html#random.shuffle) function in the [random ](https://docs.python.org/3/library/random.html) module." ] }, { "cell_type": "code", "execution_count": 51, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "random.shuffle(haystack)" ] }, { "cell_type": "code", "execution_count": 52, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "[8126, 7370, 3735, 213, 7922, 1434, 8557, 9609, 9704, 9564]" ] }, "execution_count": 52, "metadata": {}, "output_type": "execute_result" } ], "source": [ "haystack[:10]" ] }, { "cell_type": "code", "execution_count": 53, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "text/plain": [ "[7237, 886, 5945, 4014, 4998, 2055, 3531, 6919, 7875, 1944]" ] }, "execution_count": 53, "metadata": {}, "output_type": "execute_result" } ], "source": [ "haystack[-10:]" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "As modern computers are generally fast, we search the `haystack` a total of `10` times." ] }, { "cell_type": "code", "execution_count": 54, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "4.18 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n" ] } ], "source": [ "%%timeit -n 1 -r 1\n", "for _ in range(10):\n", " needle in haystack" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "Now, we convert the elements of the `haystack` into the keys of a `magic_haystack`, a `dict` object. We use `None` as a dummy value for all items." ] }, { "cell_type": "code", "execution_count": 55, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "magic_haystack = dict((x, None) for x in haystack)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "To show the *massive* effect of the hash table implementation, we search the `magic_haystack` not `10` but `10_000_000` times. The code cell still runs in only a fraction of the time its counterpart does above." ] }, { "cell_type": "code", "execution_count": 56, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "430 ms ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n" ] } ], "source": [ "%%timeit -n 1 -r 1\n", "for _ in range(10_000_000):\n", " needle in magic_haystack" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "However, there is no fast way to look up the values the keys are mapped to. To achieve that, we have to loop over *all* items and check for each value object if it compares equal to the searched object. That is, by definition, a linear search, as well, and rather slow. In the context of `dict` objects, we call that a **reverse look-up**." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Indexing -> Key Look-up" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "The same efficient key look-up executed in the background with the `in` operator is also behind the indexing operator `[]`. Instead of returning either `True` or `False`, it returns the value object the looked up key maps to.\n", "\n", "To show the similarity to indexing into `list` objects, we provide another example with `to_words_list`." ] }, { "cell_type": "code", "execution_count": 57, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "to_words_list = [\"zero\", \"one\", \"two\"]" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "Without the above definitions, we could not tell the difference between `to_words` and `to_words_list`: The usage of the `[]` is the same." ] }, { "cell_type": "code", "execution_count": 58, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "text/plain": [ "'zero'" ] }, "execution_count": 58, "metadata": {}, "output_type": "execute_result" } ], "source": [ "to_words[0]" ] }, { "cell_type": "code", "execution_count": 59, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "text/plain": [ "'zero'" ] }, "execution_count": 59, "metadata": {}, "output_type": "execute_result" } ], "source": [ "to_words_list[0]" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "Because key objects can be of any immutable type and are, in particular, not constrained to just the `int` type, the word \"*indexing*\" is an understatement here. Therefore, in the context of `dict` objects, we view the `[]` operator as a generalization of the indexing operator and refer to it as the **(key) look-up** operator. " ] }, { "cell_type": "code", "execution_count": 60, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [ { "data": { "text/plain": [ "2" ] }, "execution_count": 60, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from_words[\"two\"]" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "If a key is not in a `dict` object, Python raises a `KeyError`. A sequence type would raise an `IndexError` in this situation." ] }, { "cell_type": "code", "execution_count": 61, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "ename": "KeyError", "evalue": "'drei'", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mKeyError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mfrom_words\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m\"drei\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mKeyError\u001b[0m: 'drei'" ] } ], "source": [ "from_words[\"drei\"]" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "While `dict` objects support the `[]` operator to look up a *single* key, the more general concept of *slicing* is *not* available. That is in line with the idea that there is *no* predictable *order* associated with a `dict` object's keys, and slicing requires an order.\n", "\n", "To access deeper levels in nested data, like `people`, we *chain* the look-up operator `[]`. For example, let's view all the `\"mathematicians\"` in `people`." ] }, { "cell_type": "code", "execution_count": 62, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [ { "data": { "text/plain": [ "[{'name': 'Gilbert Strang', 'emails': ['gilbert@mit.edu']},\n", " {'name': 'Leonhard Euler', 'emails': []}]" ] }, "execution_count": 62, "metadata": {}, "output_type": "execute_result" } ], "source": [ "people[\"mathematicians\"]" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "Let's take the first mathematician on the list, ..." ] }, { "cell_type": "code", "execution_count": 63, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "{'name': 'Gilbert Strang', 'emails': ['gilbert@mit.edu']}" ] }, "execution_count": 63, "metadata": {}, "output_type": "execute_result" } ], "source": [ "people[\"mathematicians\"][0]" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "... and output his `\"name\"` ..." ] }, { "cell_type": "code", "execution_count": 64, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "'Gilbert Strang'" ] }, "execution_count": 64, "metadata": {}, "output_type": "execute_result" } ], "source": [ "people[\"mathematicians\"][0][\"name\"]" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "... or his `\"emails\"`." ] }, { "cell_type": "code", "execution_count": 65, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "['gilbert@mit.edu']" ] }, "execution_count": 65, "metadata": {}, "output_type": "execute_result" } ], "source": [ "people[\"mathematicians\"][0][\"emails\"]" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Mutability" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "We may mutate `dict` objects *in place*.\n", "\n", "For example, let's translate the English words in `to_words` to their German counterparts. Behind the scenes, Python determines the bucket of the objects passed to the `[]` operator, looks them up in the hash table, and, if present, *updates* the references to the mapped value objects." ] }, { "cell_type": "code", "execution_count": 66, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [ { "data": { "text/plain": [ "{0: 'zero', 1: 'one', 2: 'two'}" ] }, "execution_count": 66, "metadata": {}, "output_type": "execute_result" } ], "source": [ "to_words" ] }, { "cell_type": "code", "execution_count": 67, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "to_words[0] = \"null\"\n", "to_words[1] = \"eins\"\n", "to_words[2] = \"zwei\"" ] }, { "cell_type": "code", "execution_count": 68, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "{0: 'null', 1: 'eins', 2: 'zwei'}" ] }, "execution_count": 68, "metadata": {}, "output_type": "execute_result" } ], "source": [ "to_words" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "Let's add two more items. Again, Python determines their buckets, but this time finds them to be empty, and *inserts* the references to their key and value objects." ] }, { "cell_type": "code", "execution_count": 69, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [], "source": [ "to_words[3] = \"drei\"\n", "to_words[4] = \"vier\"" ] }, { "cell_type": "code", "execution_count": 70, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "{0: 'null', 1: 'eins', 2: 'zwei', 3: 'drei', 4: 'vier'}" ] }, "execution_count": 70, "metadata": {}, "output_type": "execute_result" } ], "source": [ "to_words" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "None of these operations change the identity of the `to_words` object." ] }, { "cell_type": "code", "execution_count": 71, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "text/plain": [ "140024292070576" ] }, "execution_count": 71, "metadata": {}, "output_type": "execute_result" } ], "source": [ "id(to_words) # same memory location as before" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "The `del` statement removes individual items. Python just removes the *two* references to the key and value objects in the corresponding bucket." ] }, { "cell_type": "code", "execution_count": 72, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [], "source": [ "del to_words[0]" ] }, { "cell_type": "code", "execution_count": 73, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "{1: 'eins', 2: 'zwei', 3: 'drei', 4: 'vier'}" ] }, "execution_count": 73, "metadata": {}, "output_type": "execute_result" } ], "source": [ "to_words" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "We may also change parts of nested data, such as `people`.\n", "\n", "For example, let's add [Albert Einstein ](https://en.wikipedia.org/wiki/Albert_Einstein) to the list of `\"physicists\"`, ..." ] }, { "cell_type": "code", "execution_count": 74, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [ { "data": { "text/plain": [ "[]" ] }, "execution_count": 74, "metadata": {}, "output_type": "execute_result" } ], "source": [ "people[\"physicists\"]" ] }, { "cell_type": "code", "execution_count": 75, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "people[\"physicists\"].append({\"name\": \"Albert Einstein\"})" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "... complete Guido's `\"name\"`, ..." ] }, { "cell_type": "code", "execution_count": 76, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [ { "data": { "text/plain": [ "{'name': 'Guido', 'emails': ['guido@python.org', 'guido@dropbox.com']}" ] }, "execution_count": 76, "metadata": {}, "output_type": "execute_result" } ], "source": [ "people[\"programmers\"][0]" ] }, { "cell_type": "code", "execution_count": 77, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "people[\"programmers\"][0][\"name\"] = \"Guido van Rossum\"" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "... and remove his work email because he retired." ] }, { "cell_type": "code", "execution_count": 78, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "del people[\"programmers\"][0][\"emails\"][1]" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "Now, `people` looks like this." ] }, { "cell_type": "code", "execution_count": 79, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{'mathematicians': [{'emails': ['gilbert@mit.edu'],\n", " 'name': 'Gilbert Strang'},\n", " {'emails': [],\n", " 'name': 'Leonhard Euler'}],\n", " 'physicists': [{'name': 'Albert Einstein'}],\n", " 'programmers': [{'emails': ['guido@python.org'],\n", " 'name': 'Guido van Rossum'}]}\n" ] } ], "source": [ "pprint(people, indent=1, width=60)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Dictionary Methods" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "`dict` objects come with many methods bound on them (cf., [documentation ](https://docs.python.org/3/library/stdtypes.html#dict)), many of which are standardized by the `Mapping` and `MutableMapping` ABCs from the [collections.abc ](https://docs.python.org/3/library/collections.abc.html) module. While the former requires the [keys() ](https://docs.python.org/3/library/stdtypes.html#dict.keys), [values() ](https://docs.python.org/3/library/stdtypes.html#dict.values), [items() ](https://docs.python.org/3/library/stdtypes.html#dict.items), and [get() ](https://docs.python.org/3/library/stdtypes.html#dict.get) methods, which *never* mutate an object, the latter formalizes the [update() ](https://docs.python.org/3/library/stdtypes.html#dict.update), [pop() ](https://docs.python.org/3/library/stdtypes.html#dict.pop), [popitem() ](https://docs.python.org/3/library/stdtypes.html#dict.popitem), [clear() ](https://docs.python.org/3/library/stdtypes.html#dict.clear), and [setdefault() ](https://docs.python.org/3/library/stdtypes.html#dict.setdefault) methods, which *may* do so." ] }, { "cell_type": "code", "execution_count": 80, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "import collections.abc as abc" ] }, { "cell_type": "code", "execution_count": 81, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 81, "metadata": {}, "output_type": "execute_result" } ], "source": [ "isinstance(from_words, abc.Mapping)" ] }, { "cell_type": "code", "execution_count": 82, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 82, "metadata": {}, "output_type": "execute_result" } ], "source": [ "isinstance(from_words, abc.MutableMapping)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "While iteration over a mapping type already goes over its keys, we may emphasize this explicitly by adding the [keys() ](https://docs.python.org/3/library/stdtypes.html#dict.keys) method in the `for`-loop. Again, the iteration order is equivalent to the insertion order but still considered *unpredictable*." ] }, { "cell_type": "code", "execution_count": 83, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "zero\n", "one\n", "two\n" ] } ], "source": [ "for word in from_words.keys():\n", " print(word)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "[keys() ](https://docs.python.org/3/library/stdtypes.html#dict.keys) returns an object of type `dict_keys`. That is a dynamic **view** inside the `from_words`'s hash table, which means it does *not* copy the references to the keys, and changes to `from_words` can be seen through it. View objects behave much like `dict` objects themselves." ] }, { "cell_type": "code", "execution_count": 84, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "text/plain": [ "dict_keys(['zero', 'one', 'two'])" ] }, "execution_count": 84, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from_words.keys()" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "Views can be materialized with the [list() ](https://docs.python.org/3/library/functions.html#func-list) built-in. However, that may introduce *semantic* errors into a program as the newly created `list` object has a \"*predictable*\" order (i.e., indexes `0`, `1`, ...) created from an *unpredictable* one." ] }, { "cell_type": "code", "execution_count": 85, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "text/plain": [ "['zero', 'one', 'two']" ] }, "execution_count": 85, "metadata": {}, "output_type": "execute_result" } ], "source": [ "list(from_words.keys())" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "To loop over the value objects instead, we use the [values() ](https://docs.python.org/3/library/stdtypes.html#dict.values) method. That returns a *view* (i.e., type `dict_values`) on the value objects inside `from_words` without copying them." ] }, { "cell_type": "code", "execution_count": 86, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0\n", "1\n", "2\n" ] } ], "source": [ "for number in from_words.values():\n", " print(number)" ] }, { "cell_type": "code", "execution_count": 87, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "text/plain": [ "dict_values([0, 1, 2])" ] }, "execution_count": 87, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from_words.values()" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "To loop over key-value pairs, we invoke the [items() ](https://docs.python.org/3/library/stdtypes.html#dict.items) method. That returns a view (i.e., type `dict_items`) on the key-value pairs as `tuple` objects, where the first element is the key and the second the value. Because of that, we use tuple unpacking in the `for`-loop." ] }, { "cell_type": "code", "execution_count": 88, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "zero -> 0\n", "one -> 1\n", "two -> 2\n" ] } ], "source": [ "for word, number in from_words.items():\n", " print(f\"{word} -> {number}\")" ] }, { "cell_type": "code", "execution_count": 89, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "text/plain": [ "dict_items([('zero', 0), ('one', 1), ('two', 2)])" ] }, "execution_count": 89, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from_words.items()" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "Above, we see how the look-up operator fails *loudly* with a `KeyError` if a key is *not* in a `dict` object. For example, `to_words` does *not* have a key `0` any more." ] }, { "cell_type": "code", "execution_count": 90, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [ { "data": { "text/plain": [ "{1: 'eins', 2: 'zwei', 3: 'drei', 4: 'vier'}" ] }, "execution_count": 90, "metadata": {}, "output_type": "execute_result" } ], "source": [ "to_words" ] }, { "cell_type": "code", "execution_count": 91, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "ename": "KeyError", "evalue": "0", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mKeyError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mto_words\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mKeyError\u001b[0m: 0" ] } ], "source": [ "to_words[0]" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "That may be mitigated with the [get() ](https://docs.python.org/3/library/stdtypes.html#dict.get) method that takes two arguments: `key` and `default`. It returns the value object `key` maps to if it is in the `dict` object; otherwise, `default` is returned. If not provided, `default` is `None`." ] }, { "cell_type": "code", "execution_count": 92, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "'n/a'" ] }, "execution_count": 92, "metadata": {}, "output_type": "execute_result" } ], "source": [ "to_words.get(0, \"n/a\")" ] }, { "cell_type": "code", "execution_count": 93, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "text/plain": [ "'eins'" ] }, "execution_count": 93, "metadata": {}, "output_type": "execute_result" } ], "source": [ "to_words.get(1, \"n/a\")" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "The [update() ](https://docs.python.org/3/library/stdtypes.html#dict.update) method takes the items of another mapping and either inserts them or overwrites the ones with matching keys already in the `dict` objects. It may be used in the other two ways as the [dict() ](https://docs.python.org/3/library/functions.html#func-dict) constructor allows, as well." ] }, { "cell_type": "code", "execution_count": 94, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [], "source": [ "to_spanish = {\n", " 0: \"cero\",\n", " 1: \"uno\",\n", " 2: \"dos\",\n", " 3: \"tres\",\n", " 4: \"cuatro\",\n", " 5: \"cinco\", \n", "}" ] }, { "cell_type": "code", "execution_count": 95, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "to_words.update(to_spanish)" ] }, { "cell_type": "code", "execution_count": 96, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "{1: 'uno', 2: 'dos', 3: 'tres', 4: 'cuatro', 0: 'cero', 5: 'cinco'}" ] }, "execution_count": 96, "metadata": {}, "output_type": "execute_result" } ], "source": [ "to_words" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "In contrast to the `pop()` method of the `list` type, the [pop() ](https://docs.python.org/3/library/stdtypes.html#dict.pop) method of the `dict` type *requires* a `key` argument to be passed. Then, it removes the corresponding key-value pair *and* returns the value object. If the `key` is not in the `dict` object, a `KeyError` is raised." ] }, { "cell_type": "code", "execution_count": 97, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [ { "data": { "text/plain": [ "{'zero': 0, 'one': 1, 'two': 2}" ] }, "execution_count": 97, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from_words" ] }, { "cell_type": "code", "execution_count": 98, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "number = from_words.pop(\"zero\")" ] }, { "cell_type": "code", "execution_count": 99, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "0" ] }, "execution_count": 99, "metadata": {}, "output_type": "execute_result" } ], "source": [ "number" ] }, { "cell_type": "code", "execution_count": 100, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "{'one': 1, 'two': 2}" ] }, "execution_count": 100, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from_words" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "With an optional `default` argument, the loud `KeyError` may be suppressed and the `default` returned instead, just as with the [get() ](https://docs.python.org/3/library/stdtypes.html#dict.get) method above." ] }, { "cell_type": "code", "execution_count": 101, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "ename": "KeyError", "evalue": "'zero'", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mKeyError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mfrom_words\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpop\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"zero\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mKeyError\u001b[0m: 'zero'" ] } ], "source": [ "from_words.pop(\"zero\")" ] }, { "cell_type": "code", "execution_count": 102, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "text/plain": [ "0" ] }, "execution_count": 102, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from_words.pop(\"zero\", 0)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "Similar to the `pop()` method of the `list` type, the [popitem() ](https://docs.python.org/3/library/stdtypes.html#dict.popitem) method of the `dict` type removes *and* returns an \"arbitrary\" key-value pair as a `tuple` object from a `dict` object. With the preservation of the insertion order in Python 3.7 and higher, this effectively becomes a \"last in, first out\" rule, just as with the `list` type. Once a `dict` object is empty, [popitem() ](https://docs.python.org/3/library/stdtypes.html#dict.popitem) raises a `KeyError`." ] }, { "cell_type": "code", "execution_count": 103, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [], "source": [ "word, number = from_words.popitem()" ] }, { "cell_type": "code", "execution_count": 104, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "('two', 2)" ] }, "execution_count": 104, "metadata": {}, "output_type": "execute_result" } ], "source": [ "word, number" ] }, { "cell_type": "code", "execution_count": 105, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "{'one': 1}" ] }, "execution_count": 105, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from_words" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "The [clear() ](https://docs.python.org/3/library/stdtypes.html#dict.clear) method removes all items but keeps the `dict` object alive in memory." ] }, { "cell_type": "code", "execution_count": 106, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "to_words.clear()" ] }, { "cell_type": "code", "execution_count": 107, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "text/plain": [ "{}" ] }, "execution_count": 107, "metadata": {}, "output_type": "execute_result" } ], "source": [ "to_words" ] }, { "cell_type": "code", "execution_count": 108, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [], "source": [ "from_words.clear()" ] }, { "cell_type": "code", "execution_count": 109, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "{}" ] }, "execution_count": 109, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from_words" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "The [setdefault() ](https://docs.python.org/3/library/stdtypes.html#dict.setdefault) method may have a bit of an unfortunate name but is useful, in particular, with nested `list` objects. It takes two arguments, `key` and `default`, and returns the value mapped to `key` if `key` is in the `dict` object; otherwise, it inserts the `key`-`default` pair *and* returns a reference to the newly created value object. So, it is similar to the [get() ](https://docs.python.org/3/library/stdtypes.html#dict.get) method above but also *mutates* the `dict` object.\n", "\n", "Consider the `people` example again and note how the `dict` object modeling `\"Albert Einstein\"` has *no* `\"emails\"` key in it." ] }, { "cell_type": "code", "execution_count": 110, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{'mathematicians': [{'emails': ['gilbert@mit.edu'],\n", " 'name': 'Gilbert Strang'},\n", " {'emails': [],\n", " 'name': 'Leonhard Euler'}],\n", " 'physicists': [{'name': 'Albert Einstein'}],\n", " 'programmers': [{'emails': ['guido@python.org'],\n", " 'name': 'Guido van Rossum'}]}\n" ] } ], "source": [ "pprint(people, indent=1, width=60)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "Let's say we want to append the imaginary emails `\"leonhard@math.org\"` and `\"albert@physics.org\"`. We cannot be sure if a `dict` object modeling a person has already a `\"emails\"` key or not. To play it safe, we could first use the `in` operator to check for that and create a new `list` object in a second step if one is missing. Then, we would finally append the new email.\n", "\n", "[setdefault() ](https://docs.python.org/3/library/stdtypes.html#dict.setdefault) allows us to do all of the three steps at once. More importantly, behind the scenes Python only needs to make *one* key look-up instead of potentially three. For large nested data, that could speed up the computations significantly.\n", "\n", "So, the first code cell below adds the email to the already existing empty `list` object, while the second one creates a new one first." ] }, { "cell_type": "code", "execution_count": 111, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "people[\"mathematicians\"][1].setdefault(\"emails\", []).append(\"leonhard@math.org\")" ] }, { "cell_type": "code", "execution_count": 112, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "people[\"physicists\"][0].setdefault(\"emails\", []).append(\"albert@physics.org\")" ] }, { "cell_type": "code", "execution_count": 113, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{'mathematicians': [{'emails': ['gilbert@mit.edu'],\n", " 'name': 'Gilbert Strang'},\n", " {'emails': ['leonhard@math.org'],\n", " 'name': 'Leonhard Euler'}],\n", " 'physicists': [{'emails': ['albert@physics.org'],\n", " 'name': 'Albert Einstein'}],\n", " 'programmers': [{'emails': ['guido@python.org'],\n", " 'name': 'Guido van Rossum'}]}\n" ] } ], "source": [ "pprint(people, indent=1, width=60)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "`dict` objects also come with a [copy() ](https://docs.python.org/3/library/stdtypes.html#dict.copy) method on them that creates *shallow* copies." ] }, { "cell_type": "code", "execution_count": 114, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "guido = people[\"programmers\"][0].copy()" ] }, { "cell_type": "code", "execution_count": 115, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "text/plain": [ "{'name': 'Guido van Rossum', 'emails': ['guido@python.org']}" ] }, "execution_count": 115, "metadata": {}, "output_type": "execute_result" } ], "source": [ "guido" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "If we mutate `guido` and, for example, remove all his emails with the `clear()` method on the `list` type, these changes are also visible through `people`." ] }, { "cell_type": "code", "execution_count": 116, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "guido[\"emails\"].clear()" ] }, { "cell_type": "code", "execution_count": 117, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "text/plain": [ "{'name': 'Guido van Rossum', 'emails': []}" ] }, "execution_count": 117, "metadata": {}, "output_type": "execute_result" } ], "source": [ "guido" ] }, { "cell_type": "code", "execution_count": 118, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{'mathematicians': [{'emails': ['gilbert@mit.edu'],\n", " 'name': 'Gilbert Strang'},\n", " {'emails': ['leonhard@math.org'],\n", " 'name': 'Leonhard Euler'}],\n", " 'physicists': [{'emails': ['albert@physics.org'],\n", " 'name': 'Albert Einstein'}],\n", " 'programmers': [{'emails': [],\n", " 'name': 'Guido van Rossum'}]}\n" ] } ], "source": [ "pprint(people, indent=1, width=60)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Packing & Unpacking (continued)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "Just as a single `*` symbol is used for packing and unpacking iterables in [Chapter 7 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/07_sequences_00_content.ipynb#Packing-&-Unpacking), a double `**` symbol implements packing and unpacking for mappings.\n", "\n", "Let's say we have `to_words` and `more_words` as below and want to merge the items together into a *new* `dict` object." ] }, { "cell_type": "code", "execution_count": 119, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [], "source": [ "to_words = {\n", " 0: \"zero\",\n", " 1: \"one\",\n", " 2: \"two\",\n", "}" ] }, { "cell_type": "code", "execution_count": 120, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "more_words = {\n", " 2: \"TWO\", # to illustrate a point\n", " 3: \"three\",\n", " 4: \"four\",\n", "}" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "By *unpacking* the items with `**`, the newly created `dict` object is first filled with the items from `to_words` and then from `more_words`. The item with the key `2` from `more_words` overwrites its counterpart from `to_words` as it is mentioned last." ] }, { "cell_type": "code", "execution_count": 121, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "{0: 'zero', 1: 'one', 2: 'TWO', 3: 'three', 4: 'four'}" ] }, "execution_count": 121, "metadata": {}, "output_type": "execute_result" } ], "source": [ "{**to_words, **more_words}" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "#### Function Definitions & Calls (continued)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "Both, `*` and `**` may be used within the header line of a function definition, for example, as in `print_args1()` below. Here, *positional* arguments not captured by positional parameters are *packed* into the `tuple` object `args`, and *keyword* arguments not captured by keyword parameters are *packed* into the `dict` object `kwargs`.\n", "\n", "For `print_args1()`, all arguments are optional, and ..." ] }, { "cell_type": "code", "execution_count": 122, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [], "source": [ "def print_args1(*args, **kwargs):\n", " \"\"\"Print out all arguments passed in.\"\"\"\n", " for index, arg in enumerate(args):\n", " print(\"position\", index, arg)\n", "\n", " for key, value in kwargs.items():\n", " print(\"keyword\", key, value)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "... we may pass whatever we want to it, or nothing at all." ] }, { "cell_type": "code", "execution_count": 123, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [], "source": [ "print_args1()" ] }, { "cell_type": "code", "execution_count": 124, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "position 0 a\n", "position 1 b\n", "position 2 c\n" ] } ], "source": [ "print_args1(\"a\", \"b\", \"c\")" ] }, { "cell_type": "code", "execution_count": 125, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "keyword first 1\n", "keyword second 2\n", "keyword third 3\n" ] } ], "source": [ "print_args1(first=1, second=2, third=3)" ] }, { "cell_type": "code", "execution_count": 126, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "position 0 x\n", "position 1 y\n", "keyword flag True\n" ] } ], "source": [ "print_args1(\"x\", \"y\", flag=True)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "We may even unpack `dict` and `list` objects." ] }, { "cell_type": "code", "execution_count": 127, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [], "source": [ "flags = {\"flag\": True, \"another_flag\": False}" ] }, { "cell_type": "code", "execution_count": 128, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "keyword flag True\n", "keyword another_flag False\n" ] } ], "source": [ "print_args1(**flags)" ] }, { "cell_type": "code", "execution_count": 129, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "position 0 42\n", "position 1 87\n", "keyword flag True\n", "keyword another_flag False\n" ] } ], "source": [ "print_args1(*[42, 87], **flags)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "The next example, `print_args2()`, requires the caller to pass one positional argument, captured in the `positional` parameter, and one keyword argument, captured in `keyword`. Further, an optional keyword argument `default` may be passed in. Any other positional or keyword arguments are packed into either `args` or `kwargs`." ] }, { "cell_type": "code", "execution_count": 130, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [], "source": [ "def print_args2(positional, *args, keyword, default=True, **kwargs):\n", " \"\"\"Print out all arguments passed in.\"\"\"\n", " print(\"required positional\", positional)\n", "\n", " for index, arg in enumerate(args):\n", " print(\"optional positional\", index, arg)\n", "\n", " print(\"required keyword\", keyword)\n", " print(\"default keyword\", default)\n", "\n", " for key, value in kwargs.items():\n", " print(\"optional keyword\", key, value)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "If the caller does not respect that, a `TypeError` is raised." ] }, { "cell_type": "code", "execution_count": 131, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [ { "ename": "TypeError", "evalue": "print_args2() missing 1 required positional argument: 'positional'", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mprint_args2\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mTypeError\u001b[0m: print_args2() missing 1 required positional argument: 'positional'" ] } ], "source": [ "print_args2()" ] }, { "cell_type": "code", "execution_count": 132, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "ename": "TypeError", "evalue": "print_args2() missing 1 required keyword-only argument: 'keyword'", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mprint_args2\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"p\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mTypeError\u001b[0m: print_args2() missing 1 required keyword-only argument: 'keyword'" ] } ], "source": [ "print_args2(\"p\")" ] }, { "cell_type": "code", "execution_count": 133, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "required positional p\n", "required keyword k\n", "default keyword True\n" ] } ], "source": [ "print_args2(\"p\", keyword=\"k\")" ] }, { "cell_type": "code", "execution_count": 134, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "required positional p\n", "required keyword k\n", "default keyword False\n" ] } ], "source": [ "print_args2(\"p\", keyword=\"k\", default=False)" ] }, { "cell_type": "code", "execution_count": 135, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "required positional p\n", "optional positional 0 x\n", "optional positional 1 y\n", "required keyword k\n", "default keyword True\n", "optional keyword flag True\n" ] } ], "source": [ "print_args2(\"p\", \"x\", \"y\", keyword=\"k\", flag=True)" ] }, { "cell_type": "code", "execution_count": 136, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "required positional p\n", "optional positional 0 x\n", "optional positional 1 y\n", "required keyword k\n", "default keyword False\n", "optional keyword flag True\n" ] } ], "source": [ "print_args2(\"p\", \"x\", \"y\", keyword=\"k\", default=False, flag=True)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "As above, we may unpack `list` or `dict` objects in a function call." ] }, { "cell_type": "code", "execution_count": 137, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [], "source": [ "positionals = [\"x\", \"y\", \"z\"]" ] }, { "cell_type": "code", "execution_count": 138, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "required positional p\n", "optional positional 0 x\n", "optional positional 1 y\n", "optional positional 2 z\n", "required keyword k\n", "default keyword False\n", "optional keyword flag True\n", "optional keyword another_flag False\n" ] } ], "source": [ "print_args2(\"p\", *positionals, keyword=\"k\", default=False, **flags)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Dictionary Comprehensions" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "Analogous to list comprehensions in [Chapter 7 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/07_sequences_00_content.ipynb#List-Comprehensions), **dictionary comprehensions** are a concise literal notation to derive new `dict` objects out of existing ones.\n", "\n", "For example, let's derive `from_words` out of `to_words` below by swapping the keys and values." ] }, { "cell_type": "code", "execution_count": 139, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [], "source": [ "to_words = {\n", " 0: \"zero\",\n", " 1: \"one\",\n", " 2: \"two\",\n", "}" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "Without a dictionary comprehension, we would have to initialize an empty `dict` object, loop over the items of the original one, and insert the key-value pairs one by one in a reversed fashion as value-key pairs. That assumes that the values are unique as otherwise some would be merged." ] }, { "cell_type": "code", "execution_count": 140, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "{'zero': 0, 'one': 1, 'two': 2}" ] }, "execution_count": 140, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from_words = {}\n", "\n", "for number, word in to_words.items():\n", " from_words[word] = number\n", "\n", "from_words" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "While that code is correct, it is also unnecessarily verbose. The dictionary comprehension below works in the same way as list comprehensions except that curly braces `{}` replace the brackets `[]` and a colon `:` is added to separate the keys from the values." ] }, { "cell_type": "code", "execution_count": 141, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "{'zero': 0, 'one': 1, 'two': 2}" ] }, "execution_count": 141, "metadata": {}, "output_type": "execute_result" } ], "source": [ "{v: k for k, v in to_words.items()}" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "We may filter out items with an `if`-clause and transform the remaining key and value objects.\n", "\n", "For no good reason, let's filter out all words starting with a `\"z\"` and upper case the remainin words." ] }, { "cell_type": "code", "execution_count": 142, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "text/plain": [ "{'ONE': 1, 'TWO': 2}" ] }, "execution_count": 142, "metadata": {}, "output_type": "execute_result" } ], "source": [ "{v.upper(): k for k, v in to_words.items() if not v.startswith(\"z\")}" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "Multiple `for`- and/or `if`-clauses are allowed.\n", "\n", "For example, let's find all pairs of two numbers from `1` through `10` whose product is \"close\" to `50` (e.g., within a delta of `5`)." ] }, { "cell_type": "code", "execution_count": 143, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "text/plain": [ "{(5, 9): 45,\n", " (5, 10): 50,\n", " (6, 8): 48,\n", " (6, 9): 54,\n", " (7, 7): 49,\n", " (8, 6): 48,\n", " (9, 5): 45,\n", " (9, 6): 54,\n", " (10, 5): 50}" ] }, "execution_count": 143, "metadata": {}, "output_type": "execute_result" } ], "source": [ "{\n", " (x, y): x * y\n", " for x in range(1, 11) for y in range(1, 11)\n", " if abs(x * y - 50) <= 5\n", "}" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Memoization" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "#### \"Easy at second Glance\" Example: [Fibonacci Numbers ](https://en.wikipedia.org/wiki/Fibonacci_number) (revisited)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "The *recursive* implementation of the [Fibonacci numbers ](https://en.wikipedia.org/wiki/Fibonacci_number) in [Chapter 4 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/04_iteration_00_content.ipynb#\"Easy-at-first-Glance\"-Example:-Fibonacci-Numbers) takes long to compute for large Fibonacci numbers as the number of function calls grows exponentially.\n", "\n", "The graph below visualizes what the problem is and also suggests a solution: Instead of calculating the return value of the `fibonacci()` function for the *same* argument over and over again, it makes sense to **cache** the result and reuse it. This concept is called **[memoization ](https://en.wikipedia.org/wiki/Memoization)**." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "Below is revision of the recursive `fibonacci()` implementation that uses a **globally** defined `dict` object `memo` to store intermediate results and look them up.\n", "\n", "To be precise, the the revised `fibonacci()` first checks if the `i`th Fibonacci number has already been calculated before. If yes, it is in the `memo`. That number is then returned immediately *without* any more calculations. If no, there is no corresponding entry in the `memo` and a recursive function call must be made. The number obtained by recursion is then put into the `memo`.\n", "\n", "When we follow the flow of execution closely, we realize that the intermediate results represented by the left-most path in the graph above are calculated first. `fibonacci(1)` (i.e., the left-most leaf node) is the first base case reached, followed immediately by `fibonacci(0)`. From that moment onwards, the flow of execution moves back up the left-most path while adding together the two corresponding child nodes. Effectively, this mirrors the *iterative* implementation in that the order of all computational steps are *identical*.\n", "\n", "We added a keyword-only argument `debug` that allows the caller to print out a message every time a `i` was *not* in the `memo`." ] }, { "cell_type": "code", "execution_count": 144, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [], "source": [ "memo = {\n", " 0: 0,\n", " 1: 1,\n", "}" ] }, { "cell_type": "code", "execution_count": 145, "metadata": { "code_folding": [], "slideshow": { "slide_type": "slide" } }, "outputs": [], "source": [ "def fibonacci(i, *, debug=False):\n", " \"\"\"Calculate the ith Fibonacci number.\n", "\n", " Args:\n", " i (int): index of the Fibonacci number to calculate\n", " debug (bool): show non-cached calls; defaults to False\n", "\n", " Returns:\n", " ith_fibonacci (int)\n", " \"\"\"\n", " if i in memo:\n", " return memo[i]\n", "\n", " if debug: # added for didactical purposes\n", " print(f\"fibonacci({i}) is calculated\")\n", "\n", " recurse = fibonacci(i - 1, debug=debug) + fibonacci(i - 2, debug=debug)\n", " memo[i] = recurse\n", " return recurse" ] }, { "cell_type": "code", "execution_count": 146, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "fibonacci(12) is calculated\n", "fibonacci(11) is calculated\n", "fibonacci(10) is calculated\n", "fibonacci(9) is calculated\n", "fibonacci(8) is calculated\n", "fibonacci(7) is calculated\n", "fibonacci(6) is calculated\n", "fibonacci(5) is calculated\n", "fibonacci(4) is calculated\n", "fibonacci(3) is calculated\n", "fibonacci(2) is calculated\n" ] }, { "data": { "text/plain": [ "144" ] }, "execution_count": 146, "metadata": {}, "output_type": "execute_result" } ], "source": [ "fibonacci(12, debug=True)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "Now, calling `fibonacci()` has the *side effect* of growing the `memo` in the *global scope*. So, subsequent calls to `fibonacci()` need not calculate any Fibonacci number with an index `i` smaller than the maximum `i` used so far. Because of that, this `fibonacci()` is *not* a *pure* function." ] }, { "cell_type": "code", "execution_count": 147, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "text/plain": [ "144" ] }, "execution_count": 147, "metadata": {}, "output_type": "execute_result" } ], "source": [ "fibonacci(12, debug=True) # no more recursive calls needed" ] }, { "cell_type": "code", "execution_count": 148, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "{0: 0,\n", " 1: 1,\n", " 2: 1,\n", " 3: 2,\n", " 4: 3,\n", " 5: 5,\n", " 6: 8,\n", " 7: 13,\n", " 8: 21,\n", " 9: 34,\n", " 10: 55,\n", " 11: 89,\n", " 12: 144}" ] }, "execution_count": 148, "metadata": {}, "output_type": "execute_result" } ], "source": [ "memo" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "##### Efficiency of Algorithms (continued)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "With memoization, the recursive `fibonacci()` implementation is as fast as its iterative counterpart, even for large numbers.\n", "\n", "The `%%timeit` magic, by default, runs a code cell seven times. Whereas in the first run, *new* Fibonacci numbers (i.e., intermediate results) are added to the `memo`, `fibonacci()` has no work to do in the subsequent six runs. `%%timeit` realizes this and tells us that \"an intermediate result is being cached.\"" ] }, { "cell_type": "code", "execution_count": 149, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The slowest run took 249.03 times longer than the fastest. This could mean that an intermediate result is being cached.\n", "6.11 µs ± 14.5 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" ] } ], "source": [ "%%timeit -n 1\n", "fibonacci(99)" ] }, { "cell_type": "code", "execution_count": 150, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The slowest run took 3416.49 times longer than the fastest. This could mean that an intermediate result is being cached.\n", "82.7 µs ± 202 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" ] } ], "source": [ "%%timeit -n 1\n", "fibonacci(999)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "The iterative implementation still has an advantage as the `RecursionError` shows for even larger `i`.\n", "\n", "This exception occurs as Python must keep track of *every* function call *until* it has returned, and with large enough `i`, the recursion tree above grows too big. By default, Python has a limit of up to 3000 *simultaneous* function calls. So, theoretically this exception is not a bug in the narrow sense but the result of a \"security\" measure that is supposed to keep a computer from crashing. However, practically most high-level languages like Python incur such an overhead cost: It results from the fact that someone (i.e., Python) needs to manage each function call's *local scope*. With the `for`-loop in the iterative version, we do this managing \"manually.\"" ] }, { "cell_type": "code", "execution_count": 151, "metadata": { "scrolled": true, "slideshow": { "slide_type": "skip" } }, "outputs": [ { "ename": "RecursionError", "evalue": "maximum recursion depth exceeded", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mRecursionError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mget_ipython\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrun_cell_magic\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'timeit'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m'-n 1'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m'fibonacci(9999)\\n'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;32m~/.pyenv/versions/anaconda3-2019.10/lib/python3.7/site-packages/IPython/core/interactiveshell.py\u001b[0m in \u001b[0;36mrun_cell_magic\u001b[0;34m(self, magic_name, line, cell)\u001b[0m\n\u001b[1;32m 2357\u001b[0m \u001b[0;32mwith\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mbuiltin_trap\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2358\u001b[0m \u001b[0margs\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0mmagic_arg_s\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mcell\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 2359\u001b[0;31m \u001b[0mresult\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mfn\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 2360\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mresult\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2361\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m\u001b[0m in \u001b[0;36mtimeit\u001b[0;34m(self, line, cell, local_ns)\u001b[0m\n", "\u001b[0;32m~/.pyenv/versions/anaconda3-2019.10/lib/python3.7/site-packages/IPython/core/magic.py\u001b[0m in \u001b[0;36m\u001b[0;34m(f, *a, **k)\u001b[0m\n\u001b[1;32m 185\u001b[0m \u001b[0;31m# but it's overkill for just that one bit of state.\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 186\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mmagic_deco\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0marg\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 187\u001b[0;31m \u001b[0mcall\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mlambda\u001b[0m \u001b[0mf\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0ma\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mk\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mf\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0ma\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mk\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 188\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 189\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mcallable\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0marg\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m~/.pyenv/versions/anaconda3-2019.10/lib/python3.7/site-packages/IPython/core/magics/execution.py\u001b[0m in \u001b[0;36mtimeit\u001b[0;34m(self, line, cell, local_ns)\u001b[0m\n\u001b[1;32m 1160\u001b[0m \u001b[0;32mbreak\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1161\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 1162\u001b[0;31m \u001b[0mall_runs\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mtimer\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrepeat\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mrepeat\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mnumber\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 1163\u001b[0m \u001b[0mbest\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mmin\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mall_runs\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m/\u001b[0m \u001b[0mnumber\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1164\u001b[0m \u001b[0mworst\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mmax\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mall_runs\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m/\u001b[0m \u001b[0mnumber\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m~/.pyenv/versions/anaconda3-2019.10/lib/python3.7/timeit.py\u001b[0m in \u001b[0;36mrepeat\u001b[0;34m(self, repeat, number)\u001b[0m\n\u001b[1;32m 202\u001b[0m \u001b[0mr\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 203\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mi\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mrange\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mrepeat\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 204\u001b[0;31m \u001b[0mt\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtimeit\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnumber\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 205\u001b[0m \u001b[0mr\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mappend\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mt\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 206\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mr\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m~/.pyenv/versions/anaconda3-2019.10/lib/python3.7/site-packages/IPython/core/magics/execution.py\u001b[0m in \u001b[0;36mtimeit\u001b[0;34m(self, number)\u001b[0m\n\u001b[1;32m 167\u001b[0m \u001b[0mgc\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdisable\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 168\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 169\u001b[0;31m \u001b[0mtiming\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0minner\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mit\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtimer\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 170\u001b[0m \u001b[0;32mfinally\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 171\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mgcold\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m\u001b[0m in \u001b[0;36minner\u001b[0;34m(_it, _timer)\u001b[0m\n", "\u001b[0;32m\u001b[0m in \u001b[0;36mfibonacci\u001b[0;34m(i, debug)\u001b[0m\n\u001b[1;32m 15\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34mf\"fibonacci({i}) is calculated\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 16\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 17\u001b[0;31m \u001b[0mrecurse\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mfibonacci\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mi\u001b[0m \u001b[0;34m-\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mdebug\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mdebug\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0mfibonacci\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mi\u001b[0m \u001b[0;34m-\u001b[0m \u001b[0;36m2\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mdebug\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mdebug\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 18\u001b[0m \u001b[0mmemo\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mi\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mrecurse\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 19\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mrecurse\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "... last 1 frames repeated, from the frame below ...\n", "\u001b[0;32m\u001b[0m in \u001b[0;36mfibonacci\u001b[0;34m(i, debug)\u001b[0m\n\u001b[1;32m 15\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34mf\"fibonacci({i}) is calculated\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 16\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 17\u001b[0;31m \u001b[0mrecurse\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mfibonacci\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mi\u001b[0m \u001b[0;34m-\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mdebug\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mdebug\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0mfibonacci\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mi\u001b[0m \u001b[0;34m-\u001b[0m \u001b[0;36m2\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mdebug\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mdebug\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 18\u001b[0m \u001b[0mmemo\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mi\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mrecurse\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 19\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mrecurse\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;31mRecursionError\u001b[0m: maximum recursion depth exceeded" ] } ], "source": [ "%%timeit -n 1\n", "fibonacci(9999)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "We could \"hack\" a bit with Python's default configuration using the [sys ](https://docs.python.org/3/library/sys.html) module in the [standard library ](https://docs.python.org/3/library/index.html) and make it work. As we are good citizens, we reset everything to the defaults after our hack is completed." ] }, { "cell_type": "code", "execution_count": 152, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "import sys" ] }, { "cell_type": "code", "execution_count": 153, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "old_recursion_limit = sys.getrecursionlimit()" ] }, { "cell_type": "code", "execution_count": 154, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "text/plain": [ "3000" ] }, "execution_count": 154, "metadata": {}, "output_type": "execute_result" } ], "source": [ "old_recursion_limit" ] }, { "cell_type": "code", "execution_count": 155, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "sys.setrecursionlimit(99999)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "Computational speed is *not* the problem here." ] }, { "cell_type": "code", "execution_count": 156, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The slowest run took 46787.39 times longer than the fastest. This could mean that an intermediate result is being cached.\n", "1.12 ms ± 2.73 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" ] } ], "source": [ "%%timeit -n 1\n", "fibonacci(9999)" ] }, { "cell_type": "code", "execution_count": 157, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "sys.setrecursionlimit(old_recursion_limit)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "## Specialized Mappings" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "The [collections ](https://docs.python.org/3/library/collections.html) module in the [standard library ](https://docs.python.org/3/library/index.html) provides specialized mapping types for common use cases." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "### The `defaultdict` Type" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "The [defaultdict ](https://docs.python.org/3/library/collections.html#collections.defaultdict) type allows us to define a factory function that creates default values whenever we look up a key that does not yet exist. Ordinary `dict` objects would throw a `KeyError` exception in such situations.\n", "\n", "Let's say we have a `list` with *records* of goals scored during a soccer game. The records consist of the fields \"Country,\" \"Player,\" and the \"Time\" when a goal was scored. Our task is to group the goals by player and/or country." ] }, { "cell_type": "code", "execution_count": 158, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "goals = [\n", " (\"Germany\", \"Müller\", 11), (\"Germany\", \"Klose\", 23),\n", " (\"Germany\", \"Kroos\", 24), (\"Germany\", \"Kroos\", 26),\n", " (\"Germany\", \"Khedira\", 29), (\"Germany\", \"Schürrle\", 69),\n", " (\"Germany\", \"Schürrle\", 79), (\"Brazil\", \"Oscar\", 90),\n", "]" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "Using a normal `dict` object, we have to tediously check if a player has already scored a goal before. If not, we must create a *new* `list` object with the first time the player scored. Otherwise, we append the goal to an already existing `list` object." ] }, { "cell_type": "code", "execution_count": 159, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "text/plain": [ "{'Müller': [11],\n", " 'Klose': [23],\n", " 'Kroos': [24, 26],\n", " 'Khedira': [29],\n", " 'Schürrle': [69, 79],\n", " 'Oscar': [90]}" ] }, "execution_count": 159, "metadata": {}, "output_type": "execute_result" } ], "source": [ "goals_by_player = {}\n", "\n", "for _, player, minute in goals:\n", " if player not in goals_by_player:\n", " goals_by_player[player] = [minute]\n", " else:\n", " goals_by_player[player].append(minute)\n", "\n", "goals_by_player" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "Instead, with a `defaultdict` object, we can portray the code fragment's intent in a concise form. We pass a reference to the [list() ](https://docs.python.org/3/library/functions.html#func-list) built-in to `defaultdict`." ] }, { "cell_type": "code", "execution_count": 160, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "from collections import defaultdict" ] }, { "cell_type": "code", "execution_count": 161, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "text/plain": [ "defaultdict(list,\n", " {'Müller': [11],\n", " 'Klose': [23],\n", " 'Kroos': [24, 26],\n", " 'Khedira': [29],\n", " 'Schürrle': [69, 79],\n", " 'Oscar': [90]})" ] }, "execution_count": 161, "metadata": {}, "output_type": "execute_result" } ], "source": [ "goals_by_player = defaultdict(list)\n", "\n", "for _, player, minute in goals:\n", " goals_by_player[player].append(minute)\n", "\n", "goals_by_player" ] }, { "cell_type": "code", "execution_count": 162, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "text/plain": [ "collections.defaultdict" ] }, "execution_count": 162, "metadata": {}, "output_type": "execute_result" } ], "source": [ "type(goals_by_player)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "The factory function is stored in the `default_factory` attribute." ] }, { "cell_type": "code", "execution_count": 163, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "text/plain": [ "list" ] }, "execution_count": 163, "metadata": {}, "output_type": "execute_result" } ], "source": [ "goals_by_player.default_factory" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "If we want this code to produce a normal `dict` object, we pass `goals_by_player` to the [dict() ](https://docs.python.org/3/library/functions.html#func-dict) constructor." ] }, { "cell_type": "code", "execution_count": 164, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "text/plain": [ "{'Müller': [11],\n", " 'Klose': [23],\n", " 'Kroos': [24, 26],\n", " 'Khedira': [29],\n", " 'Schürrle': [69, 79],\n", " 'Oscar': [90]}" ] }, "execution_count": 164, "metadata": {}, "output_type": "execute_result" } ], "source": [ "dict(goals_by_player)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "Being creative, we use a factory function that returns another [defaultdict ](https://docs.python.org/3/library/collections.html#collections.defaultdict) with [list() ](https://docs.python.org/3/library/functions.html#func-list) as its factory to group on the country and the player level simultaneously." ] }, { "cell_type": "code", "execution_count": 165, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "text/plain": [ "defaultdict(()>,\n", " {'Germany': defaultdict(list,\n", " {'Müller': [11],\n", " 'Klose': [23],\n", " 'Kroos': [24, 26],\n", " 'Khedira': [29],\n", " 'Schürrle': [69, 79]}),\n", " 'Brazil': defaultdict(list, {'Oscar': [90]})})" ] }, "execution_count": 165, "metadata": {}, "output_type": "execute_result" } ], "source": [ "goals_by_country_and_player = defaultdict(lambda: defaultdict(list))\n", "\n", "for country, player, minute in goals:\n", " goals_by_country_and_player[country][player].append(minute)\n", "\n", "goals_by_country_and_player" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "Conversion into a normal and nested `dict` object is now a bit tricky but can be achieved in one line with a comprehension." ] }, { "cell_type": "code", "execution_count": 166, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{'Brazil': {'Oscar': [90]},\n", " 'Germany': {'Khedira': [29],\n", " 'Klose': [23],\n", " 'Kroos': [24, 26],\n", " 'Müller': [11],\n", " 'Schürrle': [69, 79]}}\n" ] } ], "source": [ "pprint({country: dict(by_player) for country, by_player in goals_by_country_and_player.items()})" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "### The `Counter` Type" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "A common task is to count the number of occurrences of elements in an iterable.\n", "\n", "The [Counter ](https://docs.python.org/3/library/collections.html#collections.Counter) type provides an easy-to-use interface that can be called with any iterable and returns a `dict`-like object of type `Counter` that maps each unique elements to the number of times it occurs.\n", "\n", "To continue the previous example, let's create an overview that shows how many goals a player scorred. We use a generator expression as the argument to `Counter`." ] }, { "cell_type": "code", "execution_count": 167, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "text/plain": [ "[('Germany', 'Müller', 11),\n", " ('Germany', 'Klose', 23),\n", " ('Germany', 'Kroos', 24),\n", " ('Germany', 'Kroos', 26),\n", " ('Germany', 'Khedira', 29),\n", " ('Germany', 'Schürrle', 69),\n", " ('Germany', 'Schürrle', 79),\n", " ('Brazil', 'Oscar', 90)]" ] }, "execution_count": 167, "metadata": {}, "output_type": "execute_result" } ], "source": [ "goals" ] }, { "cell_type": "code", "execution_count": 168, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "from collections import Counter" ] }, { "cell_type": "code", "execution_count": 169, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "scorers = Counter(x[1] for x in goals)" ] }, { "cell_type": "code", "execution_count": 170, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "text/plain": [ "Counter({'Müller': 1,\n", " 'Klose': 1,\n", " 'Kroos': 2,\n", " 'Khedira': 1,\n", " 'Schürrle': 2,\n", " 'Oscar': 1})" ] }, "execution_count": 170, "metadata": {}, "output_type": "execute_result" } ], "source": [ "scorers" ] }, { "cell_type": "code", "execution_count": 171, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "text/plain": [ "collections.Counter" ] }, "execution_count": 171, "metadata": {}, "output_type": "execute_result" } ], "source": [ "type(scorers)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "Now we can look up individual players. `scores` behaves like a normal dictionary with regard to key look-ups." ] }, { "cell_type": "code", "execution_count": 172, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "text/plain": [ "1" ] }, "execution_count": 172, "metadata": {}, "output_type": "execute_result" } ], "source": [ "scorers[\"Müller\"]" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "By default, it returns `0` if a key is not found. So, we do not have to handle a `KeyError`." ] }, { "cell_type": "code", "execution_count": 173, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "text/plain": [ "0" ] }, "execution_count": 173, "metadata": {}, "output_type": "execute_result" } ], "source": [ "scorers[\"Lahm\"]" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "`Counter` objects have a [most_common() ](https://docs.python.org/3/library/collections.html#collections.Counter.most_common) method that returns a `list` object containing $2$-element `tuple` objects, where the first element is the element from the original iterable and the second the number of occurrences. The `list` object is sorted in descending order of occurrences." ] }, { "cell_type": "code", "execution_count": 174, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "text/plain": [ "[('Kroos', 2), ('Schürrle', 2)]" ] }, "execution_count": 174, "metadata": {}, "output_type": "execute_result" } ], "source": [ "scorers.most_common(2)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "We can increase the count of individual entries with the [update() ](https://docs.python.org/3/library/collections.html#collections.Counter.update) method: That takes an *iterable* of the elements we want to count.\n", "\n", "Imagine if [Philipp Lahm ](https://en.wikipedia.org/wiki/Philipp_Lahm) had also scored against Brazil." ] }, { "cell_type": "code", "execution_count": 175, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "scorers.update([\"Lahm\"])" ] }, { "cell_type": "code", "execution_count": 176, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "text/plain": [ "Counter({'Müller': 1,\n", " 'Klose': 1,\n", " 'Kroos': 2,\n", " 'Khedira': 1,\n", " 'Schürrle': 2,\n", " 'Oscar': 1,\n", " 'Lahm': 1})" ] }, "execution_count": 176, "metadata": {}, "output_type": "execute_result" } ], "source": [ "scorers" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "If we use a `str` object as the argument instead, each individual character is treated as an element to be updated. That is most likely not what we want." ] }, { "cell_type": "code", "execution_count": 177, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "scorers.update(\"Lahm\")" ] }, { "cell_type": "code", "execution_count": 178, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "text/plain": [ "Counter({'Müller': 1,\n", " 'Klose': 1,\n", " 'Kroos': 2,\n", " 'Khedira': 1,\n", " 'Schürrle': 2,\n", " 'Oscar': 1,\n", " 'Lahm': 1,\n", " 'L': 1,\n", " 'a': 1,\n", " 'h': 1,\n", " 'm': 1})" ] }, "execution_count": 178, "metadata": {}, "output_type": "execute_result" } ], "source": [ "scorers" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "### The `ChainMap` Type" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "Consider `to_words`, `more_words`, and `even_more_words` below. Instead of merging the items of the three `dict` objects together into a *new* one, we want to create an object that behaves as if it contained all the unified items in it without materializing them in memory a second time." ] }, { "cell_type": "code", "execution_count": 179, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "to_words = {\n", " 0: \"zero\",\n", " 1: \"one\",\n", " 2: \"two\",\n", "}" ] }, { "cell_type": "code", "execution_count": 180, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "more_words = {\n", " 2: \"TWO\", # to illustrate a point\n", " 3: \"three\",\n", " 4: \"four\",\n", "}" ] }, { "cell_type": "code", "execution_count": 181, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "even_more_words = {\n", " 4: \"FOUR\", # to illustrate a point\n", " 5: \"five\",\n", " 6: \"six\",\n", "}" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "The [ChainMap ](https://docs.python.org/3/library/collections.html#collections.ChainMap) type allows us to do precisely that." ] }, { "cell_type": "code", "execution_count": 182, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "from collections import ChainMap" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "We simply pass all mappings as positional arguments to `ChainMap` and obtain a **proxy** object that occupies almost no memory but gives us access to the union of all the items." ] }, { "cell_type": "code", "execution_count": 183, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "chain = ChainMap(to_words, more_words, even_more_words)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "Let's loop over the items in `chain` and see what is \"in\" it. The order is obviously *unpredictable* but all seven items we expected are there. Keys of later mappings do *not* overwrite earlier keys." ] }, { "cell_type": "code", "execution_count": 184, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "4 four\n", "5 five\n", "6 six\n", "2 two\n", "3 three\n", "0 zero\n", "1 one\n" ] } ], "source": [ "for number, word in chain.items():\n", " print(number, word)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "When looking up a non-existent key, `ChainMap` objects raise a `KeyError` just like normal `dict` objects would." ] }, { "cell_type": "code", "execution_count": 185, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "ename": "KeyError", "evalue": "10", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mKeyError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mchain\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m10\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;32m~/.pyenv/versions/anaconda3-2019.10/lib/python3.7/collections/__init__.py\u001b[0m in \u001b[0;36m__getitem__\u001b[0;34m(self, key)\u001b[0m\n\u001b[1;32m 914\u001b[0m \u001b[0;32mexcept\u001b[0m \u001b[0mKeyError\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 915\u001b[0m \u001b[0;32mpass\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 916\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__missing__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mkey\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;31m# support subclasses that define __missing__\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 917\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 918\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mget\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mkey\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mdefault\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mNone\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m~/.pyenv/versions/anaconda3-2019.10/lib/python3.7/collections/__init__.py\u001b[0m in \u001b[0;36m__missing__\u001b[0;34m(self, key)\u001b[0m\n\u001b[1;32m 906\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 907\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m__missing__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mkey\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 908\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mKeyError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mkey\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 909\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 910\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m__getitem__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mkey\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;31mKeyError\u001b[0m: 10" ] } ], "source": [ "chain[10]" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## The `set` Type" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "Python's provides a built-in `set` type that resembles [mathematical sets ](https://en.wikipedia.org/wiki/Set_%28mathematics%29): Each element may only be a **member** of a set once, and there is no *predictable* order regarding the elements (cf., [documentation ](https://docs.python.org/3/library/stdtypes.html#set)).\n", "\n", "To create a set, we use the literal notation, `{..., ...}`, and list all the members. The redundant `7`s and `4`s below are discarded." ] }, { "cell_type": "code", "execution_count": 186, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [], "source": [ "numbers = {7, 7, 7, 7, 7, 11, 8, 5, 3, 12, 2, 6, 9, 10, 1, 4, 4, 4, 4, 4}" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "`set` objects are objects on their own." ] }, { "cell_type": "code", "execution_count": 187, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "text/plain": [ "140024257046960" ] }, "execution_count": 187, "metadata": {}, "output_type": "execute_result" } ], "source": [ "id(numbers)" ] }, { "cell_type": "code", "execution_count": 188, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "text/plain": [ "set" ] }, "execution_count": 188, "metadata": {}, "output_type": "execute_result" } ], "source": [ "type(numbers)" ] }, { "cell_type": "code", "execution_count": 189, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}" ] }, "execution_count": 189, "metadata": {}, "output_type": "execute_result" } ], "source": [ "numbers" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "To create an empty set, we must use the built-in [set() ](https://docs.python.org/3/library/functions.html#func-set) constructor as empty curly brackets, `{}`, already create an empty `dict` object." ] }, { "cell_type": "code", "execution_count": 190, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "empty_dict = {}" ] }, { "cell_type": "code", "execution_count": 191, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "text/plain": [ "dict" ] }, "execution_count": 191, "metadata": {}, "output_type": "execute_result" } ], "source": [ "type(empty_dict)" ] }, { "cell_type": "code", "execution_count": 192, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [], "source": [ "empty_set = set()" ] }, { "cell_type": "code", "execution_count": 193, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "set()" ] }, "execution_count": 193, "metadata": {}, "output_type": "execute_result" } ], "source": [ "empty_set" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "The [set() ](https://docs.python.org/3/library/functions.html#func-set) constructor takes any `iterable` and only keeps unique elements.\n", "\n", "For example, we obtain all unique letters of a long word like so." ] }, { "cell_type": "code", "execution_count": 194, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "{'a', 'b', 'c', 'd', 'r'}" ] }, "execution_count": 194, "metadata": {}, "output_type": "execute_result" } ], "source": [ "set(\"abracadabra\")" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Sets are like Dictionaries without Values" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "The curly brackets notation can be viewed as a hint that `dict` objects are conceptually generalizations of `set` objects, and we think of `set` objects as a collection consisting of a `dict` object's keys with all the mapped values removed.\n", "\n", "Like `dict` objects, `set` objects are built on top of [hash tables ](https://en.wikipedia.org/wiki/Hash_table), and, thus, each element must be a *hashable* (i.e., immutable) object and can only be contained in a set once due to the buckets logic." ] }, { "cell_type": "code", "execution_count": 195, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [ { "ename": "TypeError", "evalue": "unhashable type: 'list'", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0;34m{\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m2\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m3\u001b[0m\u001b[0;34m}\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mTypeError\u001b[0m: unhashable type: 'list'" ] } ], "source": [ "{0, [1, 2], 3}" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "[len() ](https://docs.python.org/3/library/functions.html#len) tells us the number of elements in a `set` object." ] }, { "cell_type": "code", "execution_count": 196, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [ { "data": { "text/plain": [ "12" ] }, "execution_count": 196, "metadata": {}, "output_type": "execute_result" } ], "source": [ "len(numbers)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "We may loop over the elements in a `set` object, but we must keep in mind that there is no *predictable* order. In contrast to `dict` objects, the iteration order is also *not* guaranteed to be the insertion order. Because of the special hash values for `int` objects, `numbers` seems to be \"magically\" sorted, which is *not* the case." ] }, { "cell_type": "code", "execution_count": 197, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1 2 3 4 5 6 7 8 9 10 11 12 " ] } ], "source": [ "for number in numbers: # beware the non-order!\n", " print(number, end=\" \")" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "We confirm that `set` objects are not `Reversible`." ] }, { "cell_type": "code", "execution_count": 198, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "ename": "TypeError", "evalue": "'set' object is not reversible", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0;32mfor\u001b[0m \u001b[0mnumber\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mreversed\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnumbers\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 2\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnumber\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mend\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m\" \"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;31mTypeError\u001b[0m: 'set' object is not reversible" ] } ], "source": [ "for number in reversed(numbers):\n", " print(number, end=\" \")" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "The boolean `in` operator checks if a given and immutable object compares equal to an element in a `set` object. As with `dict` objects, membership testing is an *extremely* fast operation. Conceptually, it has the same result as conducting a linear search with the `==` operator behind the scenes without doing it." ] }, { "cell_type": "code", "execution_count": 199, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [ { "data": { "text/plain": [ "False" ] }, "execution_count": 199, "metadata": {}, "output_type": "execute_result" } ], "source": [ "0 in numbers" ] }, { "cell_type": "code", "execution_count": 200, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 200, "metadata": {}, "output_type": "execute_result" } ], "source": [ "1 in numbers" ] }, { "cell_type": "code", "execution_count": 201, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 201, "metadata": {}, "output_type": "execute_result" } ], "source": [ "2.0 in numbers # 2.0 is not a member itself but compares equal to a member" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "There is are `Set` ABC in the [collections.abc ](https://docs.python.org/3/library/collections.abc.html) module that formalizes, in particular, the operators supported by `set` objects (cf., the \"*Set Operations*\" section below). Further, the `MutableSet` ABC standardizes all the methods that mutate a `set` object in place (cf., the \"*Mutability & Set Methods*\" section below)." ] }, { "cell_type": "code", "execution_count": 202, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 202, "metadata": {}, "output_type": "execute_result" } ], "source": [ "isinstance(numbers, abc.Set)" ] }, { "cell_type": "code", "execution_count": 203, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 203, "metadata": {}, "output_type": "execute_result" } ], "source": [ "isinstance(numbers, abc.MutableSet)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### No Indexing, Key Look-up, or Slicing" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "As `set` objects come without a *predictable* order, indexing and slicing are not supported and result in a `TypeError`. In particular, as there are no values to be looked up, these operations are not *semantically* meaningful. Instead, we check membership via the `in` operator, as shown in the previous sub-section." ] }, { "cell_type": "code", "execution_count": 204, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [ { "data": { "text/plain": [ "{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}" ] }, "execution_count": 204, "metadata": {}, "output_type": "execute_result" } ], "source": [ "numbers" ] }, { "cell_type": "code", "execution_count": 205, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "ename": "TypeError", "evalue": "'set' object is not subscriptable", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mnumbers\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mTypeError\u001b[0m: 'set' object is not subscriptable" ] } ], "source": [ "numbers[0]" ] }, { "cell_type": "code", "execution_count": 206, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "ename": "TypeError", "evalue": "'set' object is not subscriptable", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mnumbers\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mTypeError\u001b[0m: 'set' object is not subscriptable" ] } ], "source": [ "numbers[:]" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Mutability & Set Methods" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "Because the `[]` operator does not work for `set` objects, they are mutated mainly via methods (cf., [documentation ](https://docs.python.org/3/library/stdtypes.html#set)).\n", "\n", "We may add new elements to an existing `set` object with the [add() ](https://docs.python.org/3/library/stdtypes.html#frozenset.add) method." ] }, { "cell_type": "code", "execution_count": 207, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [], "source": [ "numbers.add(999)" ] }, { "cell_type": "code", "execution_count": 208, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 999}" ] }, "execution_count": 208, "metadata": {}, "output_type": "execute_result" } ], "source": [ "numbers" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "The [update() ](https://docs.python.org/3/library/stdtypes.html#frozenset.update) method takes an iterable and adds all its elements to a `set` object if they are not already contained in it." ] }, { "cell_type": "code", "execution_count": 209, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "numbers.update(range(5))" ] }, { "cell_type": "code", "execution_count": 210, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 999}" ] }, "execution_count": 210, "metadata": {}, "output_type": "execute_result" } ], "source": [ "numbers" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "To remove an element by value, we use the [remove() ](https://docs.python.org/3/library/stdtypes.html#frozenset.remove) or [discard() ](https://docs.python.org/3/library/stdtypes.html#frozenset.discard) methods. If the element to be removed is not in the `set` object, [remove() ](https://docs.python.org/3/library/stdtypes.html#frozenset.remove) raises a loud `KeyError` while [discard() ](https://docs.python.org/3/library/stdtypes.html#frozenset.discard) stays *silent*." ] }, { "cell_type": "code", "execution_count": 211, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [], "source": [ "numbers.remove(999)" ] }, { "cell_type": "code", "execution_count": 212, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "ename": "KeyError", "evalue": "999", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mKeyError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mnumbers\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mremove\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m999\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mKeyError\u001b[0m: 999" ] } ], "source": [ "numbers.remove(999)" ] }, { "cell_type": "code", "execution_count": 213, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "numbers.discard(0)" ] }, { "cell_type": "code", "execution_count": 214, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "numbers.discard(0)" ] }, { "cell_type": "code", "execution_count": 215, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}" ] }, "execution_count": 215, "metadata": {}, "output_type": "execute_result" } ], "source": [ "numbers" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "The [pop() ](https://docs.python.org/3/library/stdtypes.html#frozenset.pop) method removes an *arbitrary* element. As not even the insertion order is tracked, that removes a \"random\" element." ] }, { "cell_type": "code", "execution_count": 216, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "text/plain": [ "1" ] }, "execution_count": 216, "metadata": {}, "output_type": "execute_result" } ], "source": [ "numbers.pop()" ] }, { "cell_type": "code", "execution_count": 217, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "text/plain": [ "{2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}" ] }, "execution_count": 217, "metadata": {}, "output_type": "execute_result" } ], "source": [ "numbers" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "The [clear() ](https://docs.python.org/3/library/stdtypes.html#frozenset.clear) method discards all elements but keeps the `set` object alive." ] }, { "cell_type": "code", "execution_count": 218, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "numbers.clear()" ] }, { "cell_type": "code", "execution_count": 219, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "text/plain": [ "set()" ] }, "execution_count": 219, "metadata": {}, "output_type": "execute_result" } ], "source": [ "numbers" ] }, { "cell_type": "code", "execution_count": 220, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "text/plain": [ "140024257046960" ] }, "execution_count": 220, "metadata": {}, "output_type": "execute_result" } ], "source": [ "id(numbers) # same memory location as before" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Set Operations" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "The arithmetic and relational operators are overloaded with typical set operations from math. The operators have methods that do the equivalent. We omit them for brevity in this sub-section and only show them as comments in the code cells. Both the operators and the methods return *new* `set` objects without modifying the operands.\n", "\n", "We showcase the set operations with easy math examples." ] }, { "cell_type": "code", "execution_count": 221, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [], "source": [ "numbers = set(range(1, 13))\n", "zero = {0}\n", "evens = set(range(2, 13, 2))" ] }, { "cell_type": "code", "execution_count": 222, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}" ] }, "execution_count": 222, "metadata": {}, "output_type": "execute_result" } ], "source": [ "numbers" ] }, { "cell_type": "code", "execution_count": 223, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "{0}" ] }, "execution_count": 223, "metadata": {}, "output_type": "execute_result" } ], "source": [ "zero" ] }, { "cell_type": "code", "execution_count": 224, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "{2, 4, 6, 8, 10, 12}" ] }, "execution_count": 224, "metadata": {}, "output_type": "execute_result" } ], "source": [ "evens" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "The bitwise OR operator `|` returns the union of two `set` objects." ] }, { "cell_type": "code", "execution_count": 225, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [ { "data": { "text/plain": [ "{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}" ] }, "execution_count": 225, "metadata": {}, "output_type": "execute_result" } ], "source": [ "zero | numbers # zero.union(numbers)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "Of course, the operators may be *chained*." ] }, { "cell_type": "code", "execution_count": 226, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}" ] }, "execution_count": 226, "metadata": {}, "output_type": "execute_result" } ], "source": [ "zero | numbers | evens # zero.union(numbers).union(evens)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "To obtain an intersection of two or more `set` objects, we use the bitwise AND operator `&`." ] }, { "cell_type": "code", "execution_count": 227, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [ { "data": { "text/plain": [ "set()" ] }, "execution_count": 227, "metadata": {}, "output_type": "execute_result" } ], "source": [ "zero & numbers # zero.intersection(numbers)" ] }, { "cell_type": "code", "execution_count": 228, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "{2, 4, 6, 8, 10, 12}" ] }, "execution_count": 228, "metadata": {}, "output_type": "execute_result" } ], "source": [ "numbers & evens # numbers.intersection(evens)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "To calculate a `set` object containing all elements that are in one but not the other `set` object, we use the minus operator `-`. This operation is *not* symmetric." ] }, { "cell_type": "code", "execution_count": 229, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [ { "data": { "text/plain": [ "{1, 3, 5, 7, 9, 11}" ] }, "execution_count": 229, "metadata": {}, "output_type": "execute_result" } ], "source": [ "numbers - evens # numbers.difference(evens)" ] }, { "cell_type": "code", "execution_count": 230, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "set()" ] }, "execution_count": 230, "metadata": {}, "output_type": "execute_result" } ], "source": [ "evens - numbers # evens.difference(numbers)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "The *symmetric* difference is defined as the `set` object containing all elements that are in one but not both `set` objects. It is calculated with the bitwise XOR operator `^`." ] }, { "cell_type": "code", "execution_count": 231, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "{1, 3, 5, 7, 9, 11}" ] }, "execution_count": 231, "metadata": {}, "output_type": "execute_result" } ], "source": [ "numbers ^ evens # numbers.symmetric_difference(evens)" ] }, { "cell_type": "code", "execution_count": 232, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "{1, 3, 5, 7, 9, 11}" ] }, "execution_count": 232, "metadata": {}, "output_type": "execute_result" } ], "source": [ "evens ^ numbers # evens.symmetric_difference(numbers)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "The augmented versions of the four operators (e.g., `|` becomes `|=`) are also defined: They mutate the left operand *in place*." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Set Comprehensions" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "Python provides a literal notation for **set comprehensions**, or **setcomps** for short, that works exactly like the one for dictionary comprehensions described above except that they use a single loop variable instead of a key-value pair.\n", "\n", "For example, let's create a new `set` object that consists of the squares of all the elements of `numbers`." ] }, { "cell_type": "code", "execution_count": 233, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [ { "data": { "text/plain": [ "{1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144}" ] }, "execution_count": 233, "metadata": {}, "output_type": "execute_result" } ], "source": [ "{x ** 2 for x in numbers}" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "As before, we may have multiple `for`- and/or `if`-clauses.\n", "\n", "For example, let's only keep the squares if they turn out to be an even number, or ..." ] }, { "cell_type": "code", "execution_count": 234, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "text/plain": [ "{4, 16, 36, 64, 100, 144}" ] }, "execution_count": 234, "metadata": {}, "output_type": "execute_result" } ], "source": [ "{x ** 2 for x in numbers if (x ** 2) % 2 == 0}" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "... create a `set` object with all products obtained from the Cartesian product of `numbers` with itself as long as the products are greater than `80`." ] }, { "cell_type": "code", "execution_count": 235, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "text/plain": [ "{81, 84, 88, 90, 96, 99, 100, 108, 110, 120, 121, 132, 144}" ] }, "execution_count": 235, "metadata": {}, "output_type": "execute_result" } ], "source": [ "{x * y for x in numbers for y in numbers if x * y > 80}" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "### The `frozenset` Type" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "As `set` objects are mutable, they may *not* be used, for example, as keys in a `dict` object. Similar to how we replace `list` with `tuple` objects, we may often use a `frozenset` object instead of an ordinary one. The `frozenset` type is a built-in, and as `frozenset` objects are immutable, the only limitation is that we must specify *all* elements *upon* creation (cf., [documentation ](https://docs.python.org/3/library/stdtypes.html#frozenset)).\n", "\n", "`frozenset` objects are created by passing an iterable to the built-in [frozenset() ](https://docs.python.org/3/library/functions.html#func-frozenset) constructor. Even though `frozenset` objects are hashable, their elements are *not* ordered." ] }, { "cell_type": "code", "execution_count": 236, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "text/plain": [ "frozenset({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12})" ] }, "execution_count": 236, "metadata": {}, "output_type": "execute_result" } ], "source": [ "frozenset([7, 7, 7, 7, 7, 11, 8, 5, 3, 12, 2, 6, 9, 10, 1, 4, 4, 4, 4, 4])" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "## TL;DR" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "`dict` objects are **mutable** one-to-one **mappings** from a set of **key** objects to a set of **value** objects. The association between a key-value pair is also called **item**.\n", "\n", "The items contained in a `dict` object have **no order** that is *predictable*.\n", "\n", "The underlying **data structure** of the `dict` type are **hash tables**. They make key look-ups *extremely* fast by converting the items' keys into *deterministic* hash values specifiying *precisely* one of a fixed number of equally \"wide\" buckets in which an item's references are stored. A limitation is that objects used as keys must be *immutable* (for technical reasons) and *unique* (for practical reasons).\n", "\n", "A `set` object is a **mutable** and **unordered collection** of **immutable** objects. The `set` type mimics sets we know from math." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "## Further Resources" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "A lecture-style **video presentation** of this chapter is integrated below (cf., the [video ](https://www.youtube.com/watch?v=vbp0svA35TE&list=PL-2JV1G3J10lQ2xokyQowcRJI5jjNfW7f) or the entire [playlist ](https://www.youtube.com/playlist?list=PL-2JV1G3J10lQ2xokyQowcRJI5jjNfW7f))." ] }, { "cell_type": "code", "execution_count": 237, "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", " \n", " " ], "text/plain": [ "" ] }, "execution_count": 237, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from IPython.display import YouTubeVideo\n", "YouTubeVideo(\"vbp0svA35TE\", width=\"60%\")" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "Next, we list some conference talks that go into great detail regarding the workings of the `dict` type.\n", "\n", "[Brandon Rhodes](https://github.com/brandon-rhodes) explains in great detail in his PyCon 2010 & 2017 talks how dictionaries work \"under the hood.\"" ] }, { "cell_type": "code", "execution_count": 238, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "image/jpeg": "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\n", "text/html": [ "\n", " \n", " " ], "text/plain": [ "" ] }, "execution_count": 238, "metadata": {}, "output_type": "execute_result" } ], "source": [ "YouTubeVideo(\"C4Kc8xzcA68\", width=\"60%\")" ] }, { "cell_type": "code", "execution_count": 239, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "image/jpeg": "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jaZrfV2i5bO0tdRTiioZJH1MsUU5jutrbIAy4SyuY5/A854PQ1zc73RAQH+D/s3Q22wUDKGCpiFVCytqX18O4uNTVVLQ+aouDC8kVROBjJwGtAOAFPkRAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAUW2j2wZQ1b4JYy5oo2TxCLnVFTUy1BgipIIjwfI4gY4+EnABKlKh20+xEVxrzVVAZobQcmglYSKulqeUidtTTPLcRSNLWEPHHLejC3hm39o5Vc+3sbS9qNqo6OGH4SG5qnwunlgpIqmuEEbCNb3ughJbC3UGmVwAJBVnctu4I5blAxrgaC2R3HlMsVVyN7ZBVkZlihPxQbTB2tmdWp4bktcBjL5s7eqpkTZpaWZ3I5qWUNq62jgbOXuay4bqmZ/GHPi0Zp3kBpBDXEElU67YqudBWQMdSkVuzVNZ3PdLK0w1dIyvax4aITvKd5rzzuBG66Dnh0UYbzhKdXcunyJXWbV0MM4ppJXCXVDG8tgnfBFLUFoginqWR7mnkeXsAbIQTvGeEZt9r9oZKSahpom04krnysbNVyuhp2GFgfuwWtJkqX6ubHwyI5Tnm4OArdhpXV1TLojqKatqqaqk3lwuNPuHQxUsMjORU3xFZwpWPDnluC7ByAFJNrqCqmEYhjo6uAtkjqqCu5kNQH7sxyCYQvLHMLHDQWkHenoIC1tBNWNlKo4u+rXq5mKum1lVT07zPTw09Wyhu9TuHSSTNkdbTCGSwPYwNfSu3zHc8td8YwY6cZel2opTC6SWUNdFLR007QyXm1Va2mMETW6cvDjVwAFuRz+ngVEYtgasU7Y95TMxb7/SMha+Yw0vwrJSPpKeBzmanUsTadzckD8HDQOAzDtj5DcKGp3rBTxU8XK4edmaromSx0MjBjSWAVdS4k8c09L4OGzUDWMqvh4fp5mUh2vt75nwiZ2pm/GswVDYJHUurlMcFS6Pc1ErND8sjJI3cn5JxQt+2VJUvpzDK3k00FXUb2eKqpi+KmbSPM8BnhDH0wbVNJkJxxGM4diN2fYGeBwicyGSOnNa6lqn3G5SPzUxVMMX+jn/xanlEdU9hkaTka8NGrm3902HlqYKKmkljYyKxXC01D2anP3lbDQRCWEFuHsHJZTzsdLfPg407/AOf54GVOta9l/lvUkVh2mo65zmU75NYjbKGTU9RTOkheSGzwipjBmhJGNbMjiPCsyorZbTXSV0NbXtpYnUlFPRwspJZZhMaqWklnnkMsTdyP4lCBGNWNb8uPBSpcpJJ6jvTcmtYREWp0CIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIDkLt9bR+OpfRG+0nb62j8dS+iN9pauV9s60GsowQC11XSggjIIM8YIIPAjB6F6l4WklfNXI8BHHYhu2e+ZsHt+7RePpPRWe0nb92i8fSeis9pdUN2YtuP5PovRYPYT7mLb5PovRYPYVTpdD4aPQ924r4z6+Zyv2/dovH0norPaTt+7RePpPRWe0uqPuYtvk+i9Fg9hPuYtvk+i9Fg9hNMofDRnu3FfGfXzOV+37tF4+k9FZ7Sdv3aLx9J6Kz2l1R9zFt8n0XosHsJ9zFt8n0XosHsJplD4aHduK+M+vmcr9v3aLx9J6Kz2l72+to/HUnojfaXT9z2atwgmIoKIERSEHksHAhh/mLg2E81v5o/sUzC9jXv7CVitygsThM29Vu9+htTt9bR+OpfRG+0nb62j8dS+iN9pauRS9FpcK5Fb3hiON8zaPb62j8dS+iN9pO31tH46l9Eb7S1ciaLS4VyHeGI43zNo9vraPx1L6I32k7fW0fjqX0RvtLVyJotLhXId4YjjfM2j2+to/HUvojfaTt9bR+OpfRG+0tXImi0uFch3hiON8zaPb62j8dS+iN9pO31tH46l9Eb7S1ciaLS4VyHeGI43zNo9vraPx1L6I32k7fW0fjqX0RvtLVyJotLhXId4YjjfM2j2+to/HUvojfaTt9bR+OpfRG+0tXImi0uFch3hiON8zaPb62j8dS+iN9pO31tH46l9Eb7S1ciaLS4VyHeGI43zNo9vraPx1L6I32k7fW0fjqX0RvtLVyJotLhXId4YjjfM2j2+to/HUvojfaTt9bR+OpfRG+0tXImi0uFch3hiON8zaPb62j8dS+iN9pO31tH46l9Eb7S1ciaLS4VyHeGI43zNo9vraPx1L6I32k7fW0fjqX0RvtLVyJotLhXId4YjjfM2j2+to/HUvojfaTt9bR+OpfRG+0tXImi0uFch3hiON8zaPb62j8dS+iN9pO31tH46l9Eb7S1ciaLS4VyHeGI43zNo9vraPx1L6I32k7fW0fjqX0RvtLVyJotLhXId4YjjfM2j2+to/HUvojfaTt9bR+OpfRG+0tXImi0uFch3hiON8zaPb62j8dS+iN9pO31tH46l9Eb7S1ciaLS4VyHeGI43zNodvraPx1J6I32l72+to/HUvojfaWriiaLS4VyM6fiON8zaPb62j8dS+iN9pO31tH46l9Eb7S1ciaLS4VyMd4YjjfM2j2+to/HUvojfaTt9bR+OpfRG+0tXImi0uFch3hiON8zaPb62j8dS+iN9pO31tH46l9Eb7S1ciaLS4VyHeGI43zNo9vraPx1L6I32k7fW0fjqX0RvtLVyJotLhXId4YjjfM2j2+to/HUvojfaTt9bR+OpfRG+0tXImi0uFch3hiON8zaPb62j8dS+iN9pO31tH46l9Eb7S1ciaLS4VyHeGI43zNo9vraPx1L6I32k7fW0fjqX0RvtLVyJot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\n", "text/html": [ "\n", " \n", " " ], "text/plain": [ "" ] }, "execution_count": 239, "metadata": {}, "output_type": "execute_result" } ], "source": [ "YouTubeVideo(\"66P5FMkWoVU\", width=\"60%\")" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "The `dict` type's order has been worked on with many PEPs in recent years:\n", "- [PEP 412 ](https://www.python.org/dev/peps/pep-0412/): Key-Sharing Dictionary\n", "- [PEP 468 ](https://www.python.org/dev/peps/pep-0468/): Preserving the order of \\*\\*kwargs in a function\n", "- [PEP 520 ](https://www.python.org/dev/peps/pep-0520/): Preserving Class Attribute Definition Order\n", "\n", "[Raymond Hettinger](https://github.com/rhettinger), a Python core developer and one of the greatest Python teachers in the world, summaries the history of the `dict` type in his PyCon 2017 talk." ] }, { "cell_type": "code", "execution_count": 240, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "image/jpeg": "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dfw1e3XmZbwsbcXO668vktwY+pI1law3eI1b8bdYSRodaOyXQlmcXhrGPrZynJYlzlnKyixi45cZka9mKsx9K/Rc/Vrd+DfDonDTe0HjAGxuiXanI5SvYfkaFao+GcxwWaF+HI47IwaeDapzx6PazUOBZIAfi+MgTVax6BOjCTZxuUfNPVdLlrjbb6mNrOpYqjux8MRUqz5HFoOp1cSOTYxpy57OQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAUDj20uBndUlWr3EcvLitGWJBbBblX4qOZsbodyUmVrXlgI5F3MkaGeFa6p9HfBdHdijpDKRZi9fFjc+XqXbtpz688vD4m+KlkAEdT4mdY112U837xXrZ+jMJJ9+eP4roeNJvtNhgcK1owyS1WyPswQT8LhWJ2Nil1jjJPwUnLwTp9XNscbFGyV9kcOSCC0x7I5ZGugtTRV68msbDyfJNGAOv4x6gdIxDstkW36tqzJA+Knkbt11l924+SWtPWyEEMTaT4+56nBZajb4BOohJ5EnWw2V2TksULLonsLJMjQGOfIySENwuIyrLVWEN0LiNO69x/U4SRHkCs8yHeaulq6radP5eegljdv8WS5plsMdHIyKZslC/GarpN3hG4HwfwSN++0tfLoCCSDoCryztbQjsmq+ZwkEsUD3iCw6vHYmDDDXlttj4EU7xJHpG5wPwsf8oa4rP7Kz2I84xj4QcmyBsG8X6MMVdkLuLozkN5pPg6q2vbL3nd1U2Gr3Bcvx3n2HySi3C3iwzz12wCLclc58Lg2XfG6JR4J3fCjNgZudVbOXj8/AzVTbTHS2G1mTSGR1ieoHGtabB3VWdK2at3S6Lg8ccGQhmupDdRqNFYv22hlvUKlQmVti1agmlfXssic2tUtSudUsvYIbBbNAxhLC7rcqFPZKwyGCMvhLos9cyj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\n", "text/html": [ "\n", " \n", " " ], "text/plain": [ "" ] }, "execution_count": 240, "metadata": {}, "output_type": "execute_result" } ], "source": [ "YouTubeVideo(\"npw4s1QTmPg\", width=\"60%\")" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.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 }