diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e0d5f82 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +# direnv configuration file +.envrc + +# editor backup files +*~ +*.bak + +# checkpoint directory +.ipynb_checkpoints/ diff --git a/README.md b/README.md index 264b8f1..28b3505 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ -# Python-for-programmers +# Python for programmers Repository for participants of the "Python for programmers" training diff --git a/environment.yml b/environment.yml new file mode 100644 index 0000000..5936ac9 --- /dev/null +++ b/environment.yml @@ -0,0 +1,8 @@ +name: python_for_programmers +channels: + - defaults +dependencies: + - python + - jupyterlab + - matplotlib +prefix: /home/gjb/miniconda3/envs/python_for_programmers diff --git a/python_for_programmers_linux64_conda_specs.txt b/python_for_programmers_linux64_conda_specs.txt new file mode 100644 index 0000000..d0a9c08 --- /dev/null +++ b/python_for_programmers_linux64_conda_specs.txt @@ -0,0 +1,144 @@ +# This file may be used to create an environment using: +# $ conda create --name --file +# platform: linux-64 +@EXPLICIT +https://repo.anaconda.com/pkgs/main/linux-64/_libgcc_mutex-0.1-main.conda +https://repo.anaconda.com/pkgs/main/linux-64/blas-1.0-mkl.conda +https://repo.anaconda.com/pkgs/main/linux-64/ca-certificates-2022.4.26-h06a4308_0.conda +https://repo.anaconda.com/pkgs/main/linux-64/intel-openmp-2021.4.0-h06a4308_3561.conda +https://repo.anaconda.com/pkgs/main/linux-64/ld_impl_linux-64-2.38-h1181459_1.conda +https://repo.anaconda.com/pkgs/main/linux-64/libstdcxx-ng-11.2.0-h1234567_0.conda +https://repo.anaconda.com/pkgs/main/noarch/tzdata-2022a-hda174b7_0.conda +https://repo.anaconda.com/pkgs/main/linux-64/libgomp-11.2.0-h1234567_0.conda +https://repo.anaconda.com/pkgs/main/linux-64/mkl-2021.4.0-h06a4308_640.conda +https://repo.anaconda.com/pkgs/main/linux-64/_openmp_mutex-5.1-1_gnu.conda +https://repo.anaconda.com/pkgs/main/linux-64/libgcc-ng-11.2.0-h1234567_0.conda +https://repo.anaconda.com/pkgs/main/linux-64/brotli-1.0.9-he6710b0_2.conda +https://repo.anaconda.com/pkgs/main/linux-64/bzip2-1.0.8-h7b6447c_0.conda +https://repo.anaconda.com/pkgs/main/linux-64/expat-2.4.4-h295c915_0.conda +https://repo.anaconda.com/pkgs/main/linux-64/giflib-5.2.1-h7b6447c_0.conda +https://repo.anaconda.com/pkgs/main/linux-64/icu-58.2-he6710b0_3.conda +https://repo.anaconda.com/pkgs/main/linux-64/jpeg-9e-h7f8727e_0.conda +https://repo.anaconda.com/pkgs/main/linux-64/libffi-3.3-he6710b0_2.conda +https://repo.anaconda.com/pkgs/main/linux-64/libsodium-1.0.18-h7b6447c_0.conda +https://repo.anaconda.com/pkgs/main/linux-64/libuuid-1.0.3-h7f8727e_2.conda +https://repo.anaconda.com/pkgs/main/linux-64/libwebp-base-1.2.2-h7f8727e_0.conda +https://repo.anaconda.com/pkgs/main/linux-64/libxcb-1.15-h7f8727e_0.conda +https://repo.anaconda.com/pkgs/main/linux-64/lz4-c-1.9.3-h295c915_1.conda +https://repo.anaconda.com/pkgs/main/linux-64/ncurses-6.3-h7f8727e_2.conda +https://repo.anaconda.com/pkgs/main/linux-64/openssl-1.1.1o-h7f8727e_0.conda +https://repo.anaconda.com/pkgs/main/linux-64/pcre-8.45-h295c915_0.conda +https://repo.anaconda.com/pkgs/main/linux-64/xz-5.2.5-h7f8727e_1.conda +https://repo.anaconda.com/pkgs/main/linux-64/zlib-1.2.12-h7f8727e_2.conda +https://repo.anaconda.com/pkgs/main/linux-64/glib-2.69.1-h4ff587b_1.conda +https://repo.anaconda.com/pkgs/main/linux-64/libpng-1.6.37-hbc83047_0.conda +https://repo.anaconda.com/pkgs/main/linux-64/libxml2-2.9.12-h74e7548_2.conda +https://repo.anaconda.com/pkgs/main/linux-64/readline-8.1.2-h7f8727e_1.conda +https://repo.anaconda.com/pkgs/main/linux-64/tk-8.6.11-h1ccaba5_1.conda +https://repo.anaconda.com/pkgs/main/linux-64/zeromq-4.3.4-h2531618_0.conda +https://repo.anaconda.com/pkgs/main/linux-64/zstd-1.5.2-ha4553b6_0.conda +https://repo.anaconda.com/pkgs/main/linux-64/dbus-1.13.18-hb2f20db_0.conda +https://repo.anaconda.com/pkgs/main/linux-64/freetype-2.11.0-h70c0345_0.conda +https://repo.anaconda.com/pkgs/main/linux-64/gstreamer-1.14.0-h28cd5cc_2.conda +https://repo.anaconda.com/pkgs/main/linux-64/libtiff-4.2.0-h2818925_1.conda +https://repo.anaconda.com/pkgs/main/linux-64/sqlite-3.38.3-hc218d9a_0.conda +https://repo.anaconda.com/pkgs/main/linux-64/fontconfig-2.13.1-h6c09931_0.conda +https://repo.anaconda.com/pkgs/main/linux-64/gst-plugins-base-1.14.0-h8213a91_2.conda +https://repo.anaconda.com/pkgs/main/linux-64/lcms2-2.12-h3be6417_0.conda +https://repo.anaconda.com/pkgs/main/linux-64/libwebp-1.2.2-h55f646e_0.conda +https://repo.anaconda.com/pkgs/main/linux-64/python-3.10.4-h12debd9_0.tar.bz2 +https://repo.anaconda.com/pkgs/main/noarch/attrs-21.4.0-pyhd3eb1b0_0.conda +https://repo.anaconda.com/pkgs/main/noarch/backcall-0.2.0-pyhd3eb1b0_0.tar.bz2 +https://repo.anaconda.com/pkgs/main/linux-64/certifi-2022.5.18.1-py310h06a4308_0.conda +https://repo.anaconda.com/pkgs/main/noarch/charset-normalizer-2.0.4-pyhd3eb1b0_0.conda +https://repo.anaconda.com/pkgs/main/noarch/cycler-0.11.0-pyhd3eb1b0_0.conda +https://repo.anaconda.com/pkgs/main/linux-64/debugpy-1.5.1-py310h295c915_0.conda +https://repo.anaconda.com/pkgs/main/noarch/decorator-5.1.1-pyhd3eb1b0_0.conda +https://repo.anaconda.com/pkgs/main/noarch/defusedxml-0.7.1-pyhd3eb1b0_0.conda +https://repo.anaconda.com/pkgs/main/linux-64/entrypoints-0.4-py310h06a4308_0.conda +https://repo.anaconda.com/pkgs/main/noarch/executing-0.8.3-pyhd3eb1b0_0.conda +https://repo.anaconda.com/pkgs/main/noarch/idna-3.3-pyhd3eb1b0_0.conda +https://repo.anaconda.com/pkgs/main/noarch/ipython_genutils-0.2.0-pyhd3eb1b0_1.conda +https://repo.anaconda.com/pkgs/main/noarch/json5-0.9.6-pyhd3eb1b0_0.conda +https://repo.anaconda.com/pkgs/main/linux-64/kiwisolver-1.3.1-py310h295c915_0.conda +https://repo.anaconda.com/pkgs/main/linux-64/markupsafe-2.0.1-py310h7f8727e_0.conda +https://repo.anaconda.com/pkgs/main/linux-64/mistune-0.8.4-py310h7f8727e_1000.conda +https://repo.anaconda.com/pkgs/main/noarch/munkres-1.1.4-py_0.conda +https://repo.anaconda.com/pkgs/main/linux-64/nest-asyncio-1.5.5-py310h06a4308_0.conda +https://repo.anaconda.com/pkgs/main/noarch/pandocfilters-1.5.0-pyhd3eb1b0_0.conda +https://repo.anaconda.com/pkgs/main/noarch/parso-0.8.3-pyhd3eb1b0_0.conda +https://repo.anaconda.com/pkgs/main/noarch/pickleshare-0.7.5-pyhd3eb1b0_1003.conda +https://repo.anaconda.com/pkgs/main/linux-64/pillow-9.0.1-py310h22f2fdc_0.conda +https://repo.anaconda.com/pkgs/main/noarch/prometheus_client-0.13.1-pyhd3eb1b0_0.conda +https://repo.anaconda.com/pkgs/main/noarch/ptyprocess-0.7.0-pyhd3eb1b0_2.conda +https://repo.anaconda.com/pkgs/main/noarch/pure_eval-0.2.2-pyhd3eb1b0_0.conda +https://repo.anaconda.com/pkgs/main/noarch/pycparser-2.21-pyhd3eb1b0_0.conda +https://repo.anaconda.com/pkgs/main/noarch/pygments-2.11.2-pyhd3eb1b0_0.conda +https://repo.anaconda.com/pkgs/main/noarch/pyparsing-3.0.4-pyhd3eb1b0_0.conda +https://repo.anaconda.com/pkgs/main/linux-64/pyrsistent-0.18.0-py310h7f8727e_0.conda +https://repo.anaconda.com/pkgs/main/linux-64/pysocks-1.7.1-py310h06a4308_0.conda +https://repo.anaconda.com/pkgs/main/noarch/python-fastjsonschema-2.15.1-pyhd3eb1b0_0.conda +https://repo.anaconda.com/pkgs/main/noarch/pytz-2021.3-pyhd3eb1b0_0.conda +https://repo.anaconda.com/pkgs/main/linux-64/pyzmq-22.3.0-py310h295c915_2.conda +https://repo.anaconda.com/pkgs/main/linux-64/qt-5.9.7-h5867ecd_1.conda +https://repo.anaconda.com/pkgs/main/noarch/send2trash-1.8.0-pyhd3eb1b0_1.conda +https://repo.anaconda.com/pkgs/main/linux-64/sip-4.19.13-py310h295c915_0.conda +https://repo.anaconda.com/pkgs/main/noarch/six-1.16.0-pyhd3eb1b0_1.conda +https://repo.anaconda.com/pkgs/main/linux-64/sniffio-1.2.0-py310h06a4308_1.conda +https://repo.anaconda.com/pkgs/main/noarch/soupsieve-2.3.1-pyhd3eb1b0_0.conda +https://repo.anaconda.com/pkgs/main/noarch/testpath-0.5.0-pyhd3eb1b0_0.conda +https://repo.anaconda.com/pkgs/main/linux-64/tornado-6.1-py310h7f8727e_0.conda +https://repo.anaconda.com/pkgs/main/noarch/traitlets-5.1.1-pyhd3eb1b0_0.conda +https://repo.anaconda.com/pkgs/main/noarch/typing_extensions-4.1.1-pyh06a4308_0.conda +https://repo.anaconda.com/pkgs/main/noarch/wcwidth-0.2.5-pyhd3eb1b0_0.conda +https://repo.anaconda.com/pkgs/main/linux-64/webencodings-0.5.1-py310h06a4308_1.conda +https://repo.anaconda.com/pkgs/main/noarch/wheel-0.37.1-pyhd3eb1b0_0.conda +https://repo.anaconda.com/pkgs/main/linux-64/anyio-3.5.0-py310h06a4308_0.conda +https://repo.anaconda.com/pkgs/main/noarch/asttokens-2.0.5-pyhd3eb1b0_0.conda +https://repo.anaconda.com/pkgs/main/noarch/babel-2.9.1-pyhd3eb1b0_0.conda +https://repo.anaconda.com/pkgs/main/linux-64/beautifulsoup4-4.11.1-py310h06a4308_0.conda +https://repo.anaconda.com/pkgs/main/linux-64/cffi-1.15.0-py310hd667e15_1.conda +https://repo.anaconda.com/pkgs/main/noarch/fonttools-4.25.0-pyhd3eb1b0_0.conda +https://repo.anaconda.com/pkgs/main/linux-64/jedi-0.18.1-py310h06a4308_1.conda +https://repo.anaconda.com/pkgs/main/noarch/jinja2-3.0.3-pyhd3eb1b0_0.conda +https://repo.anaconda.com/pkgs/main/linux-64/jsonschema-4.4.0-py310h06a4308_0.conda +https://repo.anaconda.com/pkgs/main/linux-64/jupyter_core-4.10.0-py310h06a4308_0.conda +https://repo.anaconda.com/pkgs/main/noarch/jupyterlab_pygments-0.1.2-py_0.conda +https://repo.anaconda.com/pkgs/main/noarch/matplotlib-inline-0.1.2-pyhd3eb1b0_2.conda +https://repo.anaconda.com/pkgs/main/linux-64/mkl-service-2.4.0-py310h7f8727e_0.conda +https://repo.anaconda.com/pkgs/main/noarch/packaging-21.3-pyhd3eb1b0_0.conda +https://repo.anaconda.com/pkgs/main/noarch/pexpect-4.8.0-pyhd3eb1b0_3.conda +https://repo.anaconda.com/pkgs/main/noarch/prompt-toolkit-3.0.20-pyhd3eb1b0_0.conda +https://repo.anaconda.com/pkgs/main/linux-64/pyqt-5.9.2-py310h295c915_6.conda +https://repo.anaconda.com/pkgs/main/noarch/python-dateutil-2.8.2-pyhd3eb1b0_0.conda +https://repo.anaconda.com/pkgs/main/linux-64/setuptools-61.2.0-py310h06a4308_0.conda +https://repo.anaconda.com/pkgs/main/linux-64/terminado-0.13.1-py310h06a4308_0.conda +https://repo.anaconda.com/pkgs/main/noarch/typing-extensions-4.1.1-hd3eb1b0_0.conda +https://repo.anaconda.com/pkgs/main/linux-64/websocket-client-0.58.0-py310h06a4308_4.conda +https://repo.anaconda.com/pkgs/main/linux-64/argon2-cffi-bindings-21.2.0-py310h7f8727e_0.conda +https://repo.anaconda.com/pkgs/main/noarch/bleach-4.1.0-pyhd3eb1b0_0.conda +https://repo.anaconda.com/pkgs/main/linux-64/brotlipy-0.7.0-py310h7f8727e_1002.conda +https://repo.anaconda.com/pkgs/main/linux-64/cryptography-37.0.1-py310h9ce1e76_0.conda +https://repo.anaconda.com/pkgs/main/linux-64/jupyter_client-7.2.2-py310h06a4308_0.conda +https://repo.anaconda.com/pkgs/main/linux-64/nbformat-5.3.0-py310h06a4308_0.conda +https://repo.anaconda.com/pkgs/main/linux-64/numpy-base-1.22.3-py310h9585f30_0.conda +https://repo.anaconda.com/pkgs/main/linux-64/pip-21.2.4-py310h06a4308_0.conda +https://repo.anaconda.com/pkgs/main/noarch/stack_data-0.2.0-pyhd3eb1b0_0.conda +https://repo.anaconda.com/pkgs/main/noarch/argon2-cffi-21.3.0-pyhd3eb1b0_0.conda +https://repo.anaconda.com/pkgs/main/linux-64/ipython-8.3.0-py310h06a4308_0.conda +https://repo.anaconda.com/pkgs/main/linux-64/nbclient-0.5.13-py310h06a4308_0.conda +https://repo.anaconda.com/pkgs/main/noarch/pyopenssl-22.0.0-pyhd3eb1b0_0.conda +https://repo.anaconda.com/pkgs/main/linux-64/ipykernel-6.9.1-py310h06a4308_0.conda +https://repo.anaconda.com/pkgs/main/linux-64/nbconvert-6.4.4-py310h06a4308_0.conda +https://repo.anaconda.com/pkgs/main/linux-64/urllib3-1.26.9-py310h06a4308_0.conda +https://repo.anaconda.com/pkgs/main/noarch/jupyter_server-1.13.5-pyhd3eb1b0_0.conda +https://repo.anaconda.com/pkgs/main/linux-64/notebook-6.4.11-py310h06a4308_0.conda +https://repo.anaconda.com/pkgs/main/noarch/requests-2.27.1-pyhd3eb1b0_0.conda +https://repo.anaconda.com/pkgs/main/linux-64/jupyterlab_server-2.12.0-py310h06a4308_0.conda +https://repo.anaconda.com/pkgs/main/noarch/nbclassic-0.3.5-pyhd3eb1b0_0.conda +https://repo.anaconda.com/pkgs/main/noarch/jupyterlab-3.3.2-pyhd3eb1b0_0.conda +https://repo.anaconda.com/pkgs/main/linux-64/matplotlib-3.5.1-py310h06a4308_1.conda +https://repo.anaconda.com/pkgs/main/linux-64/matplotlib-base-3.5.1-py310ha18d171_1.conda +https://repo.anaconda.com/pkgs/main/linux-64/mkl_fft-1.3.1-py310hd6ae3a3_0.conda +https://repo.anaconda.com/pkgs/main/linux-64/mkl_random-1.2.2-py310h00e6091_0.conda +https://repo.anaconda.com/pkgs/main/linux-64/numpy-1.22.3-py310hfa59a62_0.conda diff --git a/source_code/.gitignore b/source_code/.gitignore new file mode 100644 index 0000000..87620ac --- /dev/null +++ b/source_code/.gitignore @@ -0,0 +1 @@ +.ipynb_checkpoints/ diff --git a/source_code/README.md b/source_code/README.md new file mode 100644 index 0000000..fa05ad4 --- /dev/null +++ b/source_code/README.md @@ -0,0 +1,38 @@ +# # Source code + +Code samples for in the presentation, or provided as extra material, illustrating +certain subtle points. + +## What is it? + +1. `coins.ipynb`: change a sum of money into coinage, comparing a brute force + approach to recursion, memoization and dynamic programming. +1. `condorcet_voting.ipynb`: implementation of Condorcet voting. +1. `counterintuiive.ipynb`: some example of counter-intuitive performance results. +1. `dHondt_method.ipynb`: assigning seats in gremia according to the number of votes. +1. `dice.ipynb`: modelling throwing two dice and visualizng the results. +1. `dynamic_programming.ipynb`: some illustrations of using memoization and + dynamic programming to efficiently solve mathematical problems. +1. generations.ipynb`: searching for the most recent common ancestor and the + identical ancestor generation. +1. `hundred_prisoners.ipynb`: implementation of the solution to the "hundred + prisoners` puzzle. +1. `islands.ipynb`: identify "islands" in a 2D sea. +1. `josephus.ipynb`: solving the Josephus problem; various implementations are + compared for performance. +1. `kaprekar.ipynb`: compute the Kaprekar number. +1. `neighborhoods.ipynb` and '`game.png`: computing the hairiness of neighborhoods. +1. `one_liners.ipynb`: implementatin of a variety of problems as Python one-liners; + this is for sport, not production. +1. `survival.ipynb`: study populatoin sizes in terms of procreation rates. +1. `structural_pattern_matching.ipynb`: example of structural pattern matching + (requires Python 3.10+). +1. `welford_algorithm.ipynb`: implementation of WellFord's algorithm to compute an + estimate for the standard deviation of a data stream. +1. `word_chains.ipynb`: create word chains out of a list of words so that the first + letter of a word is the same as the last letter of the previous. Search for the + longest chain. +1. `knights_tour.ipynb`: example of using backtracking to solve the knight's tour + problem. +1. `flatten.ipynb`: `itertools.chain` may not flatten a data structure as you might + expect or want. diff --git a/source_code/coins.ipynb b/source_code/coins.ipynb new file mode 100644 index 0000000..e63379d --- /dev/null +++ b/source_code/coins.ipynb @@ -0,0 +1,703 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "fc3d92f2-4bef-43fc-bb04-6982e2e97f8f", + "metadata": {}, + "source": [ + "# Requirements" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "49cbc5f1-4572-4707-855c-16441fde7f13", + "metadata": {}, + "outputs": [], + "source": [ + "import functools" + ] + }, + { + "cell_type": "markdown", + "id": "bbfb799c-3fd0-48a0-820b-a6d70c39fadf", + "metadata": {}, + "source": [ + "# Problem" + ] + }, + { + "cell_type": "markdown", + "id": "67a32c2f-e13f-4260-a255-783b3caabdda", + "metadata": {}, + "source": [ + "Given a set of coin values $C$, e.g., $\\{1, 2, 5\\}$ and an amount of money $m$, find weights such that\n", + "$$\n", + "m = \\sum_{i} w_i c_i\n", + "$$\n", + "and the sum of weights $\\sum_i w_i$ is minimal. Weights are positive integers." + ] + }, + { + "cell_type": "markdown", + "id": "64b0e69f-4e63-430c-bcee-c9dd1c690771", + "metadata": {}, + "source": [ + "# Implementations" + ] + }, + { + "cell_type": "markdown", + "id": "4bab30c4-1fe6-47eb-9319-7e566f9a4a55", + "metadata": {}, + "source": [ + "Various implementations are possible." + ] + }, + { + "cell_type": "markdown", + "id": "60af37c7-0865-4753-a97e-8ccefd50c898", + "metadata": {}, + "source": [ + "## Greedy algorithms" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "006ad899-b916-4a57-a9b4-05107007c8ca", + "metadata": {}, + "outputs": [], + "source": [ + "def change_greedy(amount, input_coins):\n", + " coins = sorted(input_coins, reverse=True)\n", + " change = []\n", + " for coin in coins:\n", + " change.append(amount//coin)\n", + " amount %= coin\n", + " return dict(zip(coins, change))" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "4ce39264-0acb-4649-abde-9c3339964595", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{5: 4, 2: 1, 1: 1}" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "change_greedy(23, [1, 2, 5])" + ] + }, + { + "cell_type": "markdown", + "id": "521df5c0-dfd3-43c7-8fc4-57e9cccf4499", + "metadata": {}, + "source": [ + "This works, however, not for all sets of coins. Consider, e.g., $\\{1, 3, 4\\}$ to give change for 6." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "fd2e20e1-2a00-4772-a590-035bb252516f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{4: 1, 3: 0, 1: 2}" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "change_greedy(6, [1, 3, 4])" + ] + }, + { + "cell_type": "markdown", + "id": "a9a19474-ae55-471b-b20d-7be03f15a90c", + "metadata": {}, + "source": [ + "Although the change is correct, it is not minimal since you could use two coins of 3." + ] + }, + { + "cell_type": "markdown", + "id": "ffbcadca-ac69-4c57-abef-9e3199dc8d60", + "metadata": {}, + "source": [ + "## Recursive algorithm" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "c99899e6-5216-4884-9c83-a398c7f187bc", + "metadata": {}, + "outputs": [], + "source": [ + "def change_recursive(amount, input_coins):\n", + " coins = sorted(input_coins, reverse=True)\n", + " def compute(amount, coins):\n", + " if len(coins) == 1:\n", + " return [amount//coins[0]]\n", + " best_change = [amount + 1]\n", + " for nr_coins in range(1 + amount//coins[0]):\n", + " change = [nr_coins]\n", + " remainder = amount - nr_coins*coins[0]\n", + " change.extend(compute(remainder, coins[1:]))\n", + " if sum(change) < sum(best_change):\n", + " best_change = change\n", + " return best_change\n", + " return dict(zip(coins, compute(amount, coins)))" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "140ac509-38ba-4f00-ab19-1b86bb260ea4", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{5: 4, 2: 1, 1: 1}" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "change_recursive(23, [1, 2, 5])" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "1800a10b-0146-4f90-a6bc-388da234ff81", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{4: 0, 3: 2, 1: 0}" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "change_recursive(6, [1, 3, 4])" + ] + }, + { + "cell_type": "markdown", + "id": "6235d105-bba7-467e-a122-af4735450e79", + "metadata": {}, + "source": [ + "Although it is correct, it is extremely slow. In fact, it is $O(2^m)$." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "7dc31147-2c2c-4c28-b243-b37116a64113", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "388 µs ± 4.22 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)\n" + ] + } + ], + "source": [ + "%timeit change_recursive(123, [1, 2, 5])" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "6370ae16-679b-4bfe-8224-7000a8eb8c50", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "39.3 ms ± 623 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)\n" + ] + } + ], + "source": [ + "%timeit change_recursive(1_234, [1, 2, 5])" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "679e7170-a5a1-4f12-ab97-7ae9073578c1", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "765 ns ± 6.53 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)\n" + ] + } + ], + "source": [ + "%timeit change_greedy(1_234, [1, 2, 5])" + ] + }, + { + "cell_type": "markdown", + "id": "448bdbfe-e926-41e3-9dc0-b62218e991a3", + "metadata": {}, + "source": [ + "## Memoization" + ] + }, + { + "cell_type": "markdown", + "id": "967da07d-b1b8-43e1-aebf-27b000d8f93d", + "metadata": {}, + "source": [ + "Typically, memoization can help solve this kind of performance problem. We can use the `lrucache` from `functools`." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "89e9dc96-fe36-423c-bea6-64d8e8e44be9", + "metadata": {}, + "outputs": [], + "source": [ + "def change_memoized(amount, coins):\n", + " coins = tuple(sorted(coins, reverse=True))\n", + " @functools.lru_cache\n", + " def compute(amount, coins):\n", + " if len(coins) == 1:\n", + " return [amount//coins[0]]\n", + " best_change = [amount + 1]\n", + " for nr_coins in range(1 + amount//coins[0]):\n", + " change = [nr_coins]\n", + " remainder = amount - nr_coins*coins[0]\n", + " change.extend(compute(remainder, coins[1:]))\n", + " if sum(change) < sum(best_change):\n", + " best_change = change\n", + " return best_change\n", + " return dict(zip(coins, compute(amount, coins)))" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "bc9dc9de-93f4-4e20-8839-1bd5c17dfa36", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{5: 4, 2: 1, 1: 1}" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "change_memoized(23, [1, 2, 5])" + ] + }, + { + "cell_type": "markdown", + "id": "bfdf99ab-249f-47fa-aa15-8b7233a5a726", + "metadata": {}, + "source": [ + "However, it doesn't in this case, so a hand-tuned implementation would be required." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "b18cdb53-4e4c-4eb6-8d41-378c3f3007f8", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "38.8 ms ± 501 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)\n" + ] + } + ], + "source": [ + "%timeit change_recursive(1_234, [1, 2, 5])" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "b1ee1282-5c42-47fa-b858-52bd3d5cb0d5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "47.5 ms ± 1.36 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)\n" + ] + } + ], + "source": [ + "%timeit change_memoized(1_234, [1, 2, 5])" + ] + }, + { + "cell_type": "markdown", + "id": "6cbc626e-ecb7-4494-9252-9632ca6d4ac2", + "metadata": {}, + "source": [ + "## Dynamic progamming" + ] + }, + { + "cell_type": "markdown", + "id": "7f146698-2a59-4c9f-b5a1-48a9308645f7", + "metadata": {}, + "source": [ + "For this type of problem, dynamic programming is a nice solution. The complexity of this algorithm is $O(m|C|)$, so linear in $m$ rather than exponential." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "2bf8f8f2-647d-4db7-a9ec-2d54d4ae20d8", + "metadata": {}, + "outputs": [], + "source": [ + "def change_dynamic_programming(amount, input_coins):\n", + " coins = sorted(input_coins)\n", + " table = [list(range(amount + 1))]\n", + " for coin in coins[1:]:\n", + " if coin > amount:\n", + " break\n", + " row = table[-1][:coin]\n", + " row.append(1)\n", + " for value in range(coin + 1, amount + 1):\n", + " row.append(min(table[-1][value], 1 + row[value - coin]))\n", + " table.append(row)\n", + " change = {coin: 0 for coin in coins}\n", + " i, j = len(table) - 1, len(table[-1]) - 1\n", + " while table[i][j] > 0:\n", + " if i > 0 and table[i][j] == table[i - 1][j]:\n", + " i -= 1\n", + " elif j >= coins[i]:\n", + " change[coins[i]] += 1\n", + " j -= coins[i]\n", + " return change" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "1b08ab97-d918-45bb-90f4-6f348f2cfc0f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{1: 1, 2: 1, 5: 4}" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "change_dynamic_programming(23, [1, 2, 5])" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "ae91eebb-83b5-4b30-bc55-b6852ca74e48", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{1: 0, 3: 2, 4: 0}" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "change_dynamic_programming(6, [1, 3, 4])" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "954962f8-f247-4732-8047-2d2dca59a786", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "38.3 ms ± 412 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)\n" + ] + } + ], + "source": [ + "%timeit change_recursive(1_234, [1, 2, 5])" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "c7d36a56-4b10-4b5d-a608-9a9f02717874", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "563 µs ± 4.12 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)\n" + ] + } + ], + "source": [ + "%timeit change_dynamic_programming(1_234, [1, 2, 5])" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "6c08e74b-753b-4639-a64c-cd8c33c853f0", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "761 ns ± 9.45 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)\n" + ] + } + ], + "source": [ + "%timeit change_greedy(1_234, [1, 2, 5])" + ] + }, + { + "cell_type": "markdown", + "id": "624fae83-27dd-42df-8c33-cad87b962040", + "metadata": {}, + "source": [ + "Dynamic programming is of course slower than the greedy algorithm, but it produces the correct result for weird coinage." + ] + }, + { + "cell_type": "markdown", + "id": "84c1f481-3aca-4771-9568-6216ded149eb", + "metadata": {}, + "source": [ + "# Verification" + ] + }, + { + "cell_type": "markdown", + "id": "f247d14e-cd54-4a74-9de0-ca935d0886de", + "metadata": {}, + "source": [ + "For the coinage $\\{1, 2, 5\\}$ all three algoritmns should produce the same result." + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "62b04e7f-d5c0-4025-953c-0ac299c54c8e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0 failures\n" + ] + } + ], + "source": [ + "coins = [1, 2, 5]\n", + "failures = 0\n", + "for amount in range(1, 25):\n", + " target = change_greedy(amount, coins)\n", + " for func in (change_recursive, change_memoized, change_dynamic_programming):\n", + " if func(amount, coins) != target:\n", + " print(f'failure for {amount} using {func.name}')\n", + " failures += 1\n", + "print(f'{failures} failures')" + ] + }, + { + "cell_type": "markdown", + "id": "85ee4a40-d717-48e1-9c1f-484c6eddde50", + "metadata": {}, + "source": [ + "Of course, dynamic programming and the recursive algorithm should yield the same result." + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "d76b3108-251c-4bc4-bb04-297f85380c51", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0 failures\n" + ] + } + ], + "source": [ + "coins = [1, 3, 4]\n", + "failures = 0\n", + "for amount in range(1, 101):\n", + " if change_recursive(amount, coins) != change_dynamic_programming(amount, coins):\n", + " print(amount)\n", + " failures += 1\n", + "print(f'{failures} failures')" + ] + }, + { + "cell_type": "markdown", + "id": "ae653861-3e40-4a70-9216-2e9e08c323be", + "metadata": {}, + "source": [ + "Out of curiosity, lets see how often dynamic programming deviates from the greedy algorithm." + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "1ff9e5a6-df1b-4cef-b03e-6df633d2ee10", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "6\n", + "9\n", + "10\n", + "13\n", + "14\n", + "17\n", + "18\n", + "21\n", + "22\n", + "25\n", + "26\n", + "29\n", + "30\n", + "33\n", + "34\n", + "37\n", + "38\n", + "41\n", + "42\n", + "45\n", + "46\n", + "49\n", + "50\n", + "53\n", + "54\n", + "57\n", + "58\n", + "61\n", + "62\n", + "65\n", + "66\n", + "69\n", + "70\n", + "73\n", + "74\n", + "77\n", + "78\n", + "81\n", + "82\n", + "85\n", + "86\n", + "89\n", + "90\n", + "93\n", + "94\n", + "97\n", + "98\n", + "47 failures\n" + ] + } + ], + "source": [ + "coins = [1, 3, 4]\n", + "failures = 0\n", + "for amount in range(1, 101):\n", + " if change_greedy(amount, coins) != change_dynamic_programming(amount, coins):\n", + " print(amount)\n", + " failures += 1\n", + "print(f'{failures} failures')" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/source_code/condorcet_voting.ipynb b/source_code/condorcet_voting.ipynb new file mode 100644 index 0000000..ad5fed3 --- /dev/null +++ b/source_code/condorcet_voting.ipynb @@ -0,0 +1,263 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "bfe81801-d2a3-4bfd-a13e-7fc733c501aa", + "metadata": {}, + "source": [ + "# Requirements" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "5db8246d-0509-4f1f-9db2-ec360c6b5e5e", + "metadata": {}, + "outputs": [], + "source": [ + "from collections import Counter\n", + "import random" + ] + }, + { + "cell_type": "markdown", + "id": "5114a8ee-5347-4c99-a3f7-3b115f23b5bb", + "metadata": {}, + "source": [ + "# Condorcet voting" + ] + }, + { + "cell_type": "markdown", + "id": "69815d25-697e-4aaa-b0a8-9ea9a87e4964", + "metadata": {}, + "source": [ + "Condorcet voting is a voting system that maximize the approval rating of the selected candidate. The list of $N$ candidates $(C_0, C_1, C_2, \\ldots, C_{N-1}$ is ranked by each particpant in the election from best candidate to poorest candidate.\n", + "\n", + "When the voting is done, each pair of candidates is pitted against one another, e.g., $C_i$ versus $C_j$. Now the number of voters is counted that ranked $C_i$ higher than $C_j$ and vice versa. The candidate who wins this round receives a score of 1, the other a score of 0. When all pair have been compared, the winner is the candiate who won the most rounds." + ] + }, + { + "cell_type": "markdown", + "id": "dba64895-47c3-4e22-a4c4-6c28f6fe2010", + "metadata": {}, + "source": [ + "# Voters" + ] + }, + { + "cell_type": "markdown", + "id": "9664a656-5395-4528-9f53-7dfc1c558710", + "metadata": {}, + "source": [ + "We have four candidates, `A`, `B`, `C`, `D` and represent them as a list. The voters who vote for candidate `A` hate candidate `B` and will rank `B` last. Similarly, the voters who prefer `B` hate `A` and rank that candidate last. Voters who like `C` rank `A` and `B` randomly." + ] + }, + { + "cell_type": "code", + "execution_count": 87, + "id": "3bd23f10-6acd-4ce8-8e6b-8ea106098381", + "metadata": {}, + "outputs": [], + "source": [ + "def A_voter():\n", + " return ['A', 'C', 'B']" + ] + }, + { + "cell_type": "code", + "execution_count": 88, + "id": "d5f6cde7-b019-423f-bb59-1fa17d8721d1", + "metadata": {}, + "outputs": [], + "source": [ + "def B_voter():\n", + " return ['B', 'C', 'A']" + ] + }, + { + "cell_type": "code", + "execution_count": 89, + "id": "e5dc0b95-d9f6-4bf5-921b-d73ba16f8578", + "metadata": {}, + "outputs": [], + "source": [ + "def C_voter():\n", + " if random.random() < 0.5:\n", + " return ['C', 'B','A']\n", + " else:\n", + " return ['C', 'A', 'B']" + ] + }, + { + "cell_type": "markdown", + "id": "a0c00617-9cd5-47ef-bdd4-889957552aec", + "metadata": {}, + "source": [ + "# Counting votes" + ] + }, + { + "cell_type": "markdown", + "id": "a085b89b-605e-4b13-9783-281029cd7618", + "metadata": {}, + "source": [ + "The following function implements Condorcet voting." + ] + }, + { + "cell_type": "code", + "execution_count": 90, + "id": "f7d5dd07-556e-4dfd-8354-109276e1f789", + "metadata": {}, + "outputs": [], + "source": [ + "def count_condorcet_votes(rankings):\n", + " votes = Counter()\n", + " candidates = rankings[0]\n", + " for candidate in candidates:\n", + " for ranking in rankings:\n", + " for opponent in candidates:\n", + " candidate_rank = ranking.index(candidate)\n", + " opponent_rank = ranking.index(opponent)\n", + " if candidate_rank < opponent_rank:\n", + " votes[candidate] += 1\n", + " return votes" + ] + }, + { + "cell_type": "markdown", + "id": "5e35d749-0838-4de4-9c54-decb72221ccf", + "metadata": {}, + "source": [ + "Classic voting is simply counting the number of times a candidate ends up first in the ranking." + ] + }, + { + "cell_type": "code", + "execution_count": 91, + "id": "27f24758-2a1d-45a8-bfa4-9536ddd92083", + "metadata": {}, + "outputs": [], + "source": [ + "def count_classic_votes(rankings):\n", + " votes = Counter()\n", + " candidates = rankings[0]\n", + " for ranking in rankings:\n", + " votes[ranking[0]] += 1\n", + " return votes" + ] + }, + { + "cell_type": "markdown", + "id": "29289c36-ae0b-4a78-b570-a71d365c20b2", + "metadata": {}, + "source": [ + "The votes are cast, `A` and `B` are most popular, and `C` only gets ranked first by very few voters." + ] + }, + { + "cell_type": "code", + "execution_count": 92, + "id": "9435ea0c-25cb-412b-8fac-226e4db55c51", + "metadata": {}, + "outputs": [], + "source": [ + "rankings = [A_voter() for _ in range(4800)] + [B_voter() for _ in range(4800)] + [C_voter() for _ in range(400)]" + ] + }, + { + "cell_type": "markdown", + "id": "d01ef767-d87b-404e-9660-791d14ea39d0", + "metadata": {}, + "source": [] + }, + { + "cell_type": "markdown", + "id": "d68684b9-fbfa-4d68-bff0-934dc9f9f48a", + "metadata": {}, + "source": [ + "It is clear that `A` or `B` are the clear winners when votes are counted classically." + ] + }, + { + "cell_type": "code", + "execution_count": 94, + "id": "1bd3ed3b-f812-408a-b6ae-0afba01d1c0e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Counter({'A': 4800, 'B': 4800, 'C': 400})" + ] + }, + "execution_count": 94, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "count_classic_votes(rankings)" + ] + }, + { + "cell_type": "markdown", + "id": "f6689361-a9a4-4b7c-a408-3925e2d91c68", + "metadata": {}, + "source": [ + "However, with Condorcet voting, `C` is the winner." + ] + }, + { + "cell_type": "code", + "execution_count": 95, + "id": "90dbd450-c8f5-4bca-9368-37cd2e23f831", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Counter({'A': 9794, 'C': 10400, 'B': 9806})" + ] + }, + "execution_count": 95, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "count_condorcet_votes(rankings)" + ] + }, + { + "cell_type": "markdown", + "id": "d001505b-bfcf-4c6a-bb0e-079c3e9cef95", + "metadata": {}, + "source": [ + "Although `C` didn't get ranked first by many, but is not hated by most and wins out." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/source_code/counterintuiive.ipynb b/source_code/counterintuiive.ipynb index 5036560..d4bdf75 100644 --- a/source_code/counterintuiive.ipynb +++ b/source_code/counterintuiive.ipynb @@ -703,11 +703,280 @@ "\n", "The reason in this case is that the `str.count` method is implemented as a C function that operates directly on the raw character representation of the Python string." ] + }, + { + "cell_type": "markdown", + "id": "d07c739e-f984-443c-8ff9-f02c30d2c708", + "metadata": {}, + "source": [ + "# Method resolution order" + ] + }, + { + "cell_type": "markdown", + "id": "1506ce19-720e-45ad-9158-c1a1270251ed", + "metadata": {}, + "source": [ + "There is an oddity in the order in which Python searches for methods in an object instance. This is illustrated in the example below. The `say_something` method and the `__repr__` are both defined twice, once at instance level in the `__init__` function, once as methods in the class." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "0e178d52-d9cd-4c74-8dd3-428efe716f0b", + "metadata": {}, + "outputs": [], + "source": [ + "class Gadget:\n", + " \n", + " def __init__(self):\n", + " self.say_something = lambda: print('hello')\n", + " self.__repr__ = lambda: 'it is I, Leclair'\n", + " \n", + " def say_something(self):\n", + " print('bye')\n", + " \n", + " def __repr__(self):\n", + " return 'listen very carefully, I will say this only once'" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "f1a163ef-b4b6-44aa-ba14-e8f9d349eb20", + "metadata": {}, + "outputs": [], + "source": [ + "gadget = Gadget()" + ] + }, + { + "cell_type": "markdown", + "id": "7010618f-c9f6-44fd-a9d0-01fb99a95cb0", + "metadata": {}, + "source": [ + "When the `__repr__` method is invoked, the class-level method gets priority." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "0d7b89ce-93fe-4465-b5ea-17873bac3ac8", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "listen to me very carefully, I will say this only once\n" + ] + } + ], + "source": [ + "print(gadget)" + ] + }, + { + "cell_type": "markdown", + "id": "c7302665-2f47-45b5-ab99-bdc2121e2732", + "metadata": {}, + "source": [ + "However, for the `say_something` method, it is the instance method that is invoked." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "55942d8c-b30a-467c-af2c-f796b04e468c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "hello\n" + ] + } + ], + "source": [ + "gadget.say_something()" + ] + }, + { + "cell_type": "markdown", + "id": "2e096a80-aeda-43d0-8432-c9cc03eb3b13", + "metadata": {}, + "source": [ + "Typically, this should not cause issues since mostly, you don't use instance methods, except... for dependency injection.\n", + "\n", + "The following example illustrates this. Animals are created with an optional function object that determines the sound the animal produces (the dependency). If there is an instance method, it will be invoked, if not, a default method is used." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "2347d234-50b9-47d8-8b59-7a676aab9653", + "metadata": {}, + "outputs": [], + "source": [ + "class Animal:\n", + " \n", + " def __init__(self, say=None):\n", + " if say is not None:\n", + " self.say = say\n", + " \n", + " def say(self):\n", + " print(\"I've nothing to say\")" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "65bf0068-6691-46ee-bd76-30a18694ac84", + "metadata": {}, + "outputs": [], + "source": [ + "dog = Animal(lambda: print('woof'))" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "5cd055cb-1ead-4986-8cba-719a81b4990f", + "metadata": {}, + "outputs": [], + "source": [ + "cat = Animal(lambda: print('meow'))" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "7b106022-5a7c-4ade-baf8-84a5da11c27f", + "metadata": {}, + "outputs": [], + "source": [ + "donkey = Animal()" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "3b7c37ae-70c6-47e6-8aec-ac238df601ae", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "woof\n" + ] + } + ], + "source": [ + "dog.say()" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "be55436e-26d2-4fc5-a8a5-12a3d9e95a7c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "meow\n" + ] + } + ], + "source": [ + "cat.say()" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "a1b79b1a-9d46-4d5f-9303-374624de7d84", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "I've nothing to say\n" + ] + } + ], + "source": [ + "donkey.say()" + ] + }, + { + "cell_type": "markdown", + "id": "88f5ac7c-fcda-44a6-a2ef-7ef93a2708f0", + "metadata": {}, + "source": [ + "This approach works well, but would of course fail for dunder-functions." + ] + }, + { + "cell_type": "markdown", + "id": "6ffd6c83-6d43-4fe0-a49d-c249a22cfa65", + "metadata": {}, + "source": [ + "# Rounding" + ] + }, + { + "cell_type": "markdown", + "id": "0c768a47-7031-4252-b513-6103344fb8c8", + "metadata": {}, + "source": [ + "Python has a built-in `round` function that may surprise you." + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "147a80c8-edd7-41a6-8dcc-e5809b2eac4e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1/2 -> 0.0\n", + "3/2 -> 2.0\n", + "5/2 -> 2.0\n", + "7/2 -> 4.0\n", + "9/2 -> 4.0\n" + ] + } + ], + "source": [ + "for numerator in range(1, 11, 2):\n", + " print(f'{numerator}/2 -> {round(numerator/2, 0)}')" + ] + }, + { + "cell_type": "markdown", + "id": "60cb7b3d-5449-4d27-b885-ae63809cdef5", + "metadata": {}, + "source": [ + "Python uses bankers' rounding, i.e., a floating point value is rounded to the nearest even integer. So\n", + "* 0.5 is rounded down to 0,\n", + "* 1.5 is rounded up to 2,\n", + "* 2.5 is rounded down to 2,\n", + "* 3.5 is rounded up to 4,\n", + "* 4.5 is rounded down to 4." + ] } ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -721,8 +990,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.2" - } + "version": "3.10.4" + }, + "toc-autonumbering": true }, "nbformat": 4, "nbformat_minor": 5 diff --git a/source_code/dHondt_method.ipynb b/source_code/dHondt_method.ipynb new file mode 100644 index 0000000..5366401 --- /dev/null +++ b/source_code/dHondt_method.ipynb @@ -0,0 +1,241 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "2f7d2b1c-39d4-42fa-9e47-8634b83a766d", + "metadata": {}, + "source": [ + "# Assigning seats" + ] + }, + { + "cell_type": "markdown", + "id": "239d0a24-ff59-4083-9f7e-fad8e942d91c", + "metadata": {}, + "source": [ + "How to a number of seats to parties according to the number of votes they received during elections?\n", + "\n", + "A well-known algorithm to accomplish this is [d'Hondt's method](https://en.wikipedia.org/wiki/D%27Hondt_method). The input is the number of votes each party received as a dictionary with the party name as key, and the number of votes as values. For each party, we create a sequence of the number of votes it received, divided by 1 upto the number of seats to assign as a list. The list for each party is merged and sorted in descending order. From the resulting list, we count the number of seats for each party from the first number of seats elements." + ] + }, + { + "cell_type": "markdown", + "id": "909bf144-6008-4faf-a261-10cb8bfa713c", + "metadata": {}, + "source": [ + "# Requirements" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "2c89248f-17cc-412f-986c-49a8d8877196", + "metadata": {}, + "outputs": [], + "source": [ + "import collections" + ] + }, + { + "cell_type": "markdown", + "id": "67b5c5a9-dbda-4a29-88d4-b01aabffbdc7", + "metadata": {}, + "source": [ + "# Implementation" + ] + }, + { + "cell_type": "markdown", + "id": "3bdb9eed-ec5a-460c-a6e3-11dbf19dde71", + "metadata": {}, + "source": [ + "The function `allocateseats` is a straightforward implementation of the the d'Hondt method." + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "ccda5156-642e-4a4a-a04f-9ecfef757500", + "metadata": {}, + "outputs": [], + "source": [ + "def allocate_seats(votes, nr_seats):\n", + " ranking = []\n", + " for party, nr_votes in votes.items():\n", + " for divisor in range(1, nr_seats + 1):\n", + " ranking.append((party, nr_votes/divisor))\n", + " assignements = sorted(ranking, key=lambda x: x[1], reverse=True)\n", + " seats = collections.Counter()\n", + " for _ in range(nr_seats):\n", + " party, _ = assignements.pop(0)\n", + " seats[party] += 1\n", + " return seats" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "4fd4744c-2d3f-4cf2-b774-b05b78987b5e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Counter({'A': 3, 'B': 3, 'C': 1})" + ] + }, + "execution_count": 31, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "allocate_seats({\n", + " 'A': 110,\n", + " 'B': 85,\n", + " 'C': 35,\n", + "}, 7)" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "f65b0615-3da1-49c0-b3ee-15f47fc29d13", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Counter({'A': 4, 'B': 3, 'C': 1})" + ] + }, + "execution_count": 33, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "allocate_seats({\n", + " 'A': 100_000,\n", + " 'B': 80_000,\n", + " 'C': 30_000,\n", + " 'D': 20_000,\n", + "}, 8)" + ] + }, + { + "cell_type": "markdown", + "id": "37bce844-fd23-474b-91bb-b316e95cf877", + "metadata": {}, + "source": [ + "# Alternative implementation" + ] + }, + { + "cell_type": "markdown", + "id": "f453e019-f66b-49bd-af24-44cfa2966c07", + "metadata": {}, + "source": [ + "The implementation can be considerably simplified using the itertools module." + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "62da51bc-4772-49c0-84b2-cd8a60b3fcc7", + "metadata": {}, + "outputs": [], + "source": [ + "import itertools" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "b50edf26-a5ef-4eeb-a6f9-a27cdf438daa", + "metadata": {}, + "outputs": [], + "source": [ + "def allocate_seats(votes, nr_seats):\n", + " assignements = sorted(((vote[0], vote[1]/divisor) for vote, divisor in itertools.product(votes.items(), range(1, nr_seats + 1))),\n", + " key=lambda x: x[1], reverse=True)\n", + " seats = collections.Counter()\n", + " for party, _ in assignements[:nr_seats]:\n", + " seats[party] += 1\n", + " return seats" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "ab2ba177-19ca-4186-a648-cdce7ac33164", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Counter({'A': 3, 'B': 3, 'C': 1})" + ] + }, + "execution_count": 35, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "allocate_seats({\n", + " 'A': 110,\n", + " 'B': 85,\n", + " 'C': 35,\n", + "}, 7)" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "8447e319-8917-4ad2-abc5-a1fbe6c95fc3", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Counter({'A': 4, 'B': 3, 'C': 1})" + ] + }, + "execution_count": 36, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "allocate_seats({\n", + " 'A': 100_000,\n", + " 'B': 80_000,\n", + " 'C': 30_000,\n", + " 'D': 20_000,\n", + "}, 8)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/source_code/dice.ipynb b/source_code/dice.ipynb new file mode 100644 index 0000000..b2be989 --- /dev/null +++ b/source_code/dice.ipynb @@ -0,0 +1,139 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "c7e3470f-f326-44ee-824a-e957688e3680", + "metadata": {}, + "source": [ + "We want to model throws with two dice and visualize the results." + ] + }, + { + "cell_type": "markdown", + "id": "476016b8-9272-45a2-ba32-5a361c9152b9", + "metadata": {}, + "source": [ + "# Requirements" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "2ac20b85-73c1-4d61-9af8-5f9314b5af62", + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "%matplotlib inline\n", + "import numpy as np" + ] + }, + { + "cell_type": "markdown", + "id": "9b79d0ff-fb6e-4444-ac82-a09999d651f8", + "metadata": {}, + "source": [ + "# Implementation" + ] + }, + { + "cell_type": "markdown", + "id": "c36b4eaf-eefd-42f2-84ca-ae8a1207bb43", + "metadata": {}, + "source": [ + "The following function executes `nr_throws` independent throws with two fair dice and returns the sum of the eyes as a numpy array." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "47128a54-7ca8-44ee-abb6-963e5383ca47", + "metadata": {}, + "outputs": [], + "source": [ + "def throw_dice(nr_throws):\n", + " return np.random.randint(1, 6 + 1, size=(nr_throws, )) + \\\n", + " np.random.randint(1, 6 + 1, size=(nr_throws, ))" + ] + }, + { + "cell_type": "markdown", + "id": "eda74362-376a-4008-a59c-4b671f0c493e", + "metadata": {}, + "source": [ + "We generate data for 1,000 and 100,000 throws." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "ec00b082-22a2-478b-9388-ead42d0b6f59", + "metadata": {}, + "outputs": [], + "source": [ + "throws_1_000 = throw_dice(1_000)\n", + "throws_100_000 = throw_dice(100_000)" + ] + }, + { + "cell_type": "markdown", + "id": "eba0c980-0975-424c-aa33-e5fd018cd1d0", + "metadata": {}, + "source": [ + "We can now visualize the resulting distribution using Matplotlib's `hist` function. We explicitely set the bins by specifying the boundaries. Note that there are 11 bins, since we can throw any number between 2 and 12 inclusive using two dice.\n", + "\n", + "The data is normalized by the `hist` function when we set the argument `density` to `True`. The subplots share the $y$-axis to make it easier to compare the results. The ticks on the $x$-axis are set explicitly." + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "960cc387-ba45-4c9e-960a-63013fbc34cf", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "figure, axes = plt.subplots(1, 2, figsize=(10, 5), sharey=True)\n", + "bins = np.arange(1.5, 13.0, 1.0)\n", + "ticks = np.arange(2.0, 13.0, 2.0)\n", + "for i, data in enumerate([throws_1_000, throws_100_000]):\n", + " axes[i].hist(data, bins=bins, density=True)\n", + " axes[i].set_xticks(ticks)\n", + "figure.tight_layout()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/source_code/dynamic_programming.ipynb b/source_code/dynamic_programming.ipynb new file mode 100644 index 0000000..d1a2b36 --- /dev/null +++ b/source_code/dynamic_programming.ipynb @@ -0,0 +1,261 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "1b4bd8fe-c7be-4d15-a58f-48f112d7faac", + "metadata": {}, + "source": [ + "# Requirements" + ] + }, + { + "cell_type": "markdown", + "id": "800cdb81-bc22-4c16-a2a4-5eb482d7c766", + "metadata": {}, + "source": [ + "# Memoization" + ] + }, + { + "cell_type": "markdown", + "id": "d20b5e96-7a9d-4fff-a0bf-be3c5290e58a", + "metadata": {}, + "source": [ + "## Fibonacci" + ] + }, + { + "cell_type": "markdown", + "id": "6244cdf3-68a2-4c5a-9dbb-9a75e780261d", + "metadata": {}, + "source": [ + "The Fibonacci function is recursively defined as:\n", + "\n", + "$$\n", + " f(n) = \\begin{cases}\n", + " 1 & \\mbox{if } n = 0 \\mbox{ or } n = 1 \\\\\n", + " f(n-1) + f(n-2) & \\mbox{if } n > 1\n", + " \\end{cases}\n", + "$$" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bba4e04e-b51d-410a-9760-4df5ac42f78a", + "metadata": {}, + "outputs": [], + "source": [ + "def fib(n):\n", + " if n == 0 or n == 1:\n", + " return 1\n", + " else:\n", + " return fib(n - 1) + fib(n - 2)" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "275ba72e-63fa-44c4-95e2-0ec2a13620a2", + "metadata": {}, + "outputs": [], + "source": [ + "for input, output in [(0, 1), (1, 1), (2, 2), (3, 3), (4, 5), (5, 8)]:\n", + " assert fib(input == output)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "a6ad9fa0-eb35-4f99-9e76-81b88189e563", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "179 ms ± 821 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)\n" + ] + } + ], + "source": [ + "%timeit fib(30)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "829c1e21-b2c6-49ae-bc39-4d350542b96f", + "metadata": {}, + "outputs": [], + "source": [ + "def fib_memoized(n, memo=None):\n", + " if memo is None:\n", + " memo = {}\n", + " if n == 0 or n == 1:\n", + " return 1\n", + " elif n in memo:\n", + " return memo[n]\n", + " else:\n", + " result = fib_memoized(n - 1, memo), fib_memoized(n - 2, memo)\n", + " memo[n] = result\n", + " return result " + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "7fd86ee3-fbbf-4ba7-a5e4-47e2cbc29424", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "8.44 µs ± 20.4 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)\n" + ] + } + ], + "source": [ + "%timeit fib_memoized(30)" + ] + }, + { + "cell_type": "markdown", + "id": "6e145ce9-50e4-46cd-bc9d-51213be2e080", + "metadata": {}, + "source": [ + "## Grid walk" + ] + }, + { + "cell_type": "markdown", + "id": "9a4d2fc9-5abb-4edb-ba95-1c82a0c4dd9e", + "metadata": {}, + "source": [ + "Consider walks on a grid that start in the upper left corner and end in the lower right corner. You can only move down or left. In how many distinct ways can you walk from start to finish?" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "4e846940-3e29-4f9d-b49b-ff19d958800c", + "metadata": {}, + "outputs": [], + "source": [ + "def number_of_walks(nr_rows, nr_cols):\n", + " if nr_rows == 0 or nr_cols == 0: return 0\n", + " if nr_rows == 1 and nr_cols == 1: return 1\n", + " return number_of_walks(nr_rows - 1, nr_cols) + number_of_walks(nr_rows, nr_cols - 1)" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "67bce850-848a-4e74-8949-fd2e3b3aa3b3", + "metadata": {}, + "outputs": [], + "source": [ + "for input, output in [((0, 5), 0), ((5, 0), 0), ((1, 1), 1), ((1, 2), 1), ((2, 1), 1), ((2, 2), 2), ((2, 3), 3), ((3, 2), 3), ((3, 3), 6)]:\n", + " assert number_of_walks(*input) == output, f'{input} -> {output}'" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "c402ef35-0d9a-48ea-bcd7-6e6c71e18628", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "358 ms ± 2.6 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" + ] + } + ], + "source": [ + "%timeit number_of_walks(12, 12)" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "e3c5b579-9bcc-4c9e-960c-32c10e1c82f0", + "metadata": {}, + "outputs": [], + "source": [ + "def number_of_walks_memoized(nr_rows, nr_cols, memo=None):\n", + " if memo is None:\n", + " memo = {}\n", + " if nr_rows == 0 or nr_cols == 0: return 0\n", + " if nr_rows == 1 and nr_cols == 1: return 1\n", + " if (nr_rows, nr_cols) in memo:\n", + " return memo[(nr_rows, nr_cols)]\n", + " if (nr_cols, nr_rows) in memo:\n", + " return memo[(nr_cols, nr_rows)]\n", + " result = number_of_walks_memoized(nr_rows - 1, nr_cols, memo) + number_of_walks_memoized(nr_rows, nr_cols - 1, memo)\n", + " memo[(nr_rows, nr_cols)] = result\n", + " return result" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "d4156c61-16f4-4328-9619-9f5f67f0a159", + "metadata": {}, + "outputs": [], + "source": [ + "for input, output in [((0, 5), 0), ((5, 0), 0), ((1, 1), 1), ((1, 2), 1), ((2, 1), 1), ((2, 2), 2), ((2, 3), 3), ((3, 2), 3), ((3, 3), 6)]:\n", + " assert number_of_walks_memoized(*input) == output, f'{input} -> {output}'" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "56be9108-f4d9-4dd0-bd32-c5ba14a586d7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "39.2 µs ± 540 ns per loop (mean ± std. dev. of 7 runs, 10,000 loops each)\n" + ] + } + ], + "source": [ + "%timeit number_of_walks_memoized(12, 12)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2548b228-d6da-436e-a651-09eb68190521", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.4" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/source_code/exercises.ipynb b/source_code/exercises.ipynb new file mode 100644 index 0000000..d0abf36 --- /dev/null +++ b/source_code/exercises.ipynb @@ -0,0 +1,159 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "7ea52ac8-def9-4fa6-ad62-437fb876c85b", + "metadata": {}, + "source": [ + "Some random programming exercises." + ] + }, + { + "cell_type": "markdown", + "id": "2eea84dc-48b1-4582-835f-ac084837f56d", + "metadata": {}, + "source": [ + "## Contains digit\n", + "\n", + "Write a function that returns `True` if a given number contains a given digit, `False` otherwise." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "b3a54ce5-354e-4525-aa1e-f6fcb85f7fc1", + "metadata": {}, + "outputs": [], + "source": [ + "def has_digit(n, digit):\n", + " while n > 0:\n", + " if n % 10 == digit:\n", + " return True\n", + " n //= 10\n", + " return False" + ] + }, + { + "cell_type": "markdown", + "id": "2823f917-cd56-4e0f-bbaf-69bc6c59b339", + "metadata": {}, + "source": [ + "Test the implmentation, taking edge cases into account." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "711c7090-69b5-42fe-b986-dc498f2ee6a3", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "34901 has digit 0\n", + "34901 has digit 1\n", + "no 2 in 34901\n", + "34901 has digit 3\n", + "34901 has digit 4\n", + "no 5 in 34901\n", + "no 6 in 34901\n", + "no 7 in 34901\n", + "no 8 in 34901\n", + "34901 has digit 9\n" + ] + } + ], + "source": [ + "n = 34901\n", + "for digit in range(10):\n", + " if has_digit(n, digit):\n", + " print(f'{n} has digit {digit}')\n", + " else:\n", + " print(f'no {digit} in {n}')" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "e6d1d4c6-480c-43ff-a422-87dd7090a205", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "no 0 in 3491\n", + "3491 has digit 1\n", + "no 2 in 3491\n", + "3491 has digit 3\n", + "3491 has digit 4\n", + "no 5 in 3491\n", + "no 6 in 3491\n", + "no 7 in 3491\n", + "no 8 in 3491\n", + "3491 has digit 9\n" + ] + } + ], + "source": [ + "n = 3491\n", + "for digit in range(10):\n", + " if has_digit(n, digit):\n", + " print(f'{n} has digit {digit}')\n", + " else:\n", + " print(f'no {digit} in {n}')" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "92538c32-190b-4e6f-bd96-6a4fef1ce54c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "has_digit(210, 0)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "259a311a-edfc-4c82-9239-5162c797df72", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.4" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/source_code/flatten.ipynb b/source_code/flatten.ipynb new file mode 100644 index 0000000..778b66d --- /dev/null +++ b/source_code/flatten.ipynb @@ -0,0 +1,162 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 3, + "id": "82480372-c0ad-440c-a076-5e8cc6666e48", + "metadata": {}, + "outputs": [], + "source": [ + "import itertools" + ] + }, + { + "cell_type": "markdown", + "id": "2e8c295a-92e9-4ee5-aefa-5ebe658ebc04", + "metadata": {}, + "source": [ + "Although `itertools.chain` is very useful, it may not do what you intend when elements are strings. It also doesn't recurse beyond the first level." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "5f6e1c01-98e4-4093-984b-2f8b470c804b", + "metadata": {}, + "outputs": [], + "source": [ + "data = [('a', 'b'), 'c012', ('d', 'e490'), 'f', 'g', (1, (3, 7))]" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "96e0ea2e-28cc-438b-9e5d-e98edc521883", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a\n", + "b\n", + "c\n", + "0\n", + "1\n", + "2\n", + "d\n", + "e490\n", + "f\n", + "g\n", + "1\n", + "(3, 7)\n" + ] + } + ], + "source": [ + "for item in itertools.chain(*data):\n", + " print(item)" + ] + }, + { + "cell_type": "markdown", + "id": "308fe429-20ae-41b6-832d-ee840c1a1bb6", + "metadata": {}, + "source": [ + "The following function may be more what you expect." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "b36d51df-47b6-48a6-b79e-778b5e88e17e", + "metadata": {}, + "outputs": [], + "source": [ + "def flatten(data, recurse=True):\n", + " for item in data:\n", + " if hasattr(item, '__iter__') and not isinstance(item, str):\n", + " for subitem in flatten(item) if recurse else item:\n", + " yield subitem\n", + " else:\n", + " yield item" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "64a64ff7-8b26-462a-b98b-c4c2c69b9014", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a\n", + "b\n", + "c012\n", + "d\n", + "e490\n", + "f\n", + "g\n", + "1\n", + "3\n", + "7\n" + ] + } + ], + "source": [ + "for item in flatten(data):\n", + " print(item)" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "bbb96b04-1e84-4c6d-bd8d-18638732f32e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a\n", + "b\n", + "c012\n", + "d\n", + "e490\n", + "f\n", + "g\n", + "1\n", + "(3, 7)\n" + ] + } + ], + "source": [ + "for item in flatten(data, recurse=False):\n", + " print(item)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/source_code/game.png b/source_code/game.png new file mode 100644 index 0000000..772b01f Binary files /dev/null and b/source_code/game.png differ diff --git a/source_code/generations.ipynb b/source_code/generations.ipynb new file mode 100644 index 0000000..0d37d14 --- /dev/null +++ b/source_code/generations.ipynb @@ -0,0 +1,189 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import random" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We search for the most recent common ancestor and the identical ancestor generation." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "def has_common_ancestor(current_generation):\n", + " return any(map(lambda descendants: len(descendants) == len(current_generation), current_generation))" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "def is_identical_ancestor_generation(current_generation):\n", + " return all(map(lambda descendants: len(descendants) == len(current_generation) or len(descendants) == 0, current_generation))" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "def track_previous_generation(current_generation):\n", + " previous_generation = [set() for _ in range(len(current_generation))]\n", + " for individual in range(len(current_generation)):\n", + " potential_parents = list(range(len(current_generation)))\n", + " mother = random.choice(potential_parents)\n", + " potential_parents.pop(mother)\n", + " father = random.choice(potential_parents)\n", + " previous_generation[mother].update(current_generation[individual])\n", + " previous_generation[father].update(current_generation[individual])\n", + " return previous_generation" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[{0}, {2, 4}, {0, 3}, {1, 2, 3}, {1, 4}]\n", + "[{1, 2, 3, 4}, {0, 3}, {0, 1, 2, 3, 4}, {1, 4}, {0, 2, 4}]\n", + "common ancestor in generation 1\n", + "[{1, 4}, {0, 1, 2, 3, 4}, set(), {0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}]\n", + "common ancestor in generation 2\n", + "[{0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}, {1, 4}, {0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}]\n", + "common ancestor in generation 3\n", + "[{0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}]\n", + "common ancestor in generation 4\n", + "[{0, 1, 2, 3, 4}, set(), {0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}]\n", + "common ancestor in generation 5\n", + "[{0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}]\n", + "common ancestor in generation 6\n", + "[{0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}]\n", + "common ancestor in generation 7\n", + "[{0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}]\n", + "common ancestor in generation 8\n", + "[{0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}]\n", + "common ancestor in generation 9\n", + "[{0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}]\n", + "common ancestor in generation 10\n", + "[set(), {0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}]\n", + "common ancestor in generation 11\n", + "[{0, 1, 2, 3, 4}, set(), {0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}]\n", + "common ancestor in generation 12\n", + "[set(), {0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}]\n", + "common ancestor in generation 13\n", + "[{0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}]\n", + "common ancestor in generation 14\n", + "[{0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}]\n", + "common ancestor in generation 15\n", + "[set(), {0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}]\n", + "common ancestor in generation 16\n", + "[{0, 1, 2, 3, 4}, set(), {0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}]\n", + "common ancestor in generation 17\n", + "[{0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}, set(), {0, 1, 2, 3, 4}]\n", + "common ancestor in generation 18\n", + "[{0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}, set()]\n", + "common ancestor in generation 19\n" + ] + } + ], + "source": [ + "population_size = 5\n", + "nr_generations = 20\n", + "current_generation = [{i} for i in range(population_size)]\n", + "for generation in range(nr_generations):\n", + " current_generation = track_previous_generation(current_generation)\n", + " print(current_generation)\n", + " if has_common_ancestor(current_generation):\n", + " print(f'common ancestor in generation {generation}')" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "def find_common_ancestors(current_population, is_verbose=False):\n", + " common_ancestor_generation = 0\n", + " generation = 0\n", + " while not is_identical_ancestor_generation(current_population):\n", + " current_population = track_previous_generation(current_population)\n", + " if is_verbose:\n", + " print(current_population)\n", + " generation += 1\n", + " if has_common_ancestor(current_population) and not common_ancestor_generation:\n", + " common_ancestor_generation = generation\n", + " return common_ancestor_generation, generation" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(9, 19)" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "population_size = 512\n", + "current_generation = [{i} for i in range(population_size)]\n", + "find_common_ancestors(current_generation)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.10" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/source_code/hundred_prisoners.ipynb b/source_code/hundred_prisoners.ipynb new file mode 100644 index 0000000..ef180af --- /dev/null +++ b/source_code/hundred_prisoners.ipynb @@ -0,0 +1,296 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "b6baca61-df50-4edd-8fcc-42ebb7ef2bc5", + "metadata": {}, + "source": [ + "# Requirements" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "id": "bd7aef96-d24e-4bf7-a6ac-3e837c06202a", + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "import random" + ] + }, + { + "cell_type": "markdown", + "id": "c36bca2f-da37-414a-987a-086acd4d7c0c", + "metadata": {}, + "source": [ + "# Problem" + ] + }, + { + "cell_type": "markdown", + "id": "d379d896-c726-4d1f-b01f-7b252e54051f", + "metadata": {}, + "source": [ + "100 prisonors numbered from 1 to 100 get access to a room that contains 100 boxes numbered from 1 to 100. Each box contains a slip of paper with a number between 1 and 100. Each of the 100 prisoners can open 50 of the boxes. If in one of these boxes he finds the slip with his number, he wins, if not, he loses.\n", + "\n", + "The prisoners are led into the room one by one, and they are not allowed to communicate with their peers. They also have to leave the room behind exactly as they found it. (So they can not swap slips between boxes, or leave boxes open, mark them, and so on.)\n", + "\n", + "If all prisonors win, they are all released. If at least one prisoner loses, all prisoners are executed.\n", + "\n", + "The prisoner can discuss strategies before they start." + ] + }, + { + "cell_type": "markdown", + "id": "b0f27f95-8244-456f-9f9f-0ab0db419fef", + "metadata": {}, + "source": [ + "For convenience, we number everything (prisoners, boxes and the value on the slips) starting from index 0. We can represent the 100 boxes as a random permutation of the the numbers 0 to 99." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "77baa8a0-e195-4e7a-bf7a-d7e2b1120fe3", + "metadata": {}, + "outputs": [], + "source": [ + "boxes = list(range(100))\n", + "random.shuffle(boxes)" + ] + }, + { + "cell_type": "markdown", + "id": "d8f70883-e64d-4af9-8335-6df6dffbb45a", + "metadata": {}, + "source": [ + "# Random strategy" + ] + }, + { + "cell_type": "markdown", + "id": "ce245209-bc01-4254-b9e7-7952fbd6aaa9", + "metadata": {}, + "source": [ + "The probability that a randomly opened box contains a slip with a given number is 0.01. It is clear that if each prisoner picks 50 boxes at random, the probability to open the one that contains his number is 0.5. However, since the prisoners have no way to communicate, each of them opens the boxes independent of all the others, and the probability that they all find the slip with their name on it is $2^{-100}$, so vanishingly small. " + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "a6646551-d8d5-4458-a242-43c83ae16ae9", + "metadata": {}, + "outputs": [], + "source": [ + "def pick_randomly(prisoner, boxes):\n", + " opened_boxes = random.sample(range(100), k=50)\n", + " return any(map(lambda x: boxes[x] == prisoner, opened_boxes))" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "068deee2-cfaa-4964-a1d2-220a9d8d1d4f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 35, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "pick_randomly(50, boxes)" + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "id": "c70c5849-5a57-40b2-b149-4607f880de1c", + "metadata": {}, + "outputs": [], + "source": [ + "def nr_succesful_prisoners(boxes, strategy):\n", + " nr_successes = 0\n", + " for prisoner in range(100):\n", + " if strategy(prisoner, boxes):\n", + " nr_successes += 1\n", + " return nr_successes" + ] + }, + { + "cell_type": "code", + "execution_count": 61, + "id": "5edd236f-1378-4908-ad03-69e75e57f628", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "runs = [nr_succesful_prisoners(boxes, pick_randomly) for _ in range(500)]\n", + "plt.hist(runs, bins=100);" + ] + }, + { + "cell_type": "markdown", + "id": "cd6931ec-8c11-4c6a-ad3f-f4cebce82ae7", + "metadata": {}, + "source": [ + "For 500 independent trials, the maximum number of prisoners that finds his own number is less than 70." + ] + }, + { + "cell_type": "markdown", + "id": "749b0147-a8ba-4bdb-a173-983b17ca76fd", + "metadata": {}, + "source": [ + "# Smart strategy" + ] + }, + { + "cell_type": "markdown", + "id": "13b32b7f-8ac5-41de-8da0-0e4d7a58669c", + "metadata": {}, + "source": [ + "Although the prisoners have no means to communicate once they start to open boxes, they can agree on a strategy upfront. One of them is pretty smart, and he proposes the following:\n", + "* open the box that corresponds to your number, so, e.g., prisoner 5 starts by opening the 5th box;\n", + "* next, read the slip in that box, and open the box with the number on the slip next,\n", + "* repeat until you found your number, or opened the 50th box." + ] + }, + { + "cell_type": "code", + "execution_count": 72, + "id": "682f52ae-8d44-422d-a150-01674e218866", + "metadata": {}, + "outputs": [], + "source": [ + "def pick_strategically(prisoner, boxes):\n", + " slip = boxes[prisoner]\n", + " for _ in range(50):\n", + " if slip == prisoner:\n", + " return True\n", + " slip = boxes[slip]\n", + " return False " + ] + }, + { + "cell_type": "code", + "execution_count": 75, + "id": "cf24b35e-6c73-4d39-bd44-3e08368d8a2c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 75, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "pick_strategically(85, boxes)" + ] + }, + { + "cell_type": "markdown", + "id": "b5725150-37b8-4374-b514-7ce513f4b79b", + "metadata": {}, + "source": [ + "The algorithm itself is now deterministic, so we have to vary the distribution of the slips in the boxes to estimate the probability of success for the 100 prisoners." + ] + }, + { + "cell_type": "code", + "execution_count": 78, + "id": "c74c389b-900c-4750-a6b6-c8c8948ffa34", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "runs = []\n", + "for _ in range(500):\n", + " random.shuffle(boxes)\n", + " runs.append(nr_succesful_prisoners(boxes, pick_strategically))\n", + "plt.hist(runs, bins=100, density=True);" + ] + }, + { + "cell_type": "markdown", + "id": "e9df80ec-d10a-48e5-a2cc-6c09ee1db157", + "metadata": {}, + "source": [ + "Using this strategy, the prisoners win their freedom with a probability of more than 0.3. Note that there is no middle ground. Either all prisoners find their slip, or more than half don't." + ] + }, + { + "cell_type": "markdown", + "id": "c49c12a2-1c87-4c61-828c-395d948e60f7", + "metadata": {}, + "source": [ + "# References" + ] + }, + { + "cell_type": "markdown", + "id": "d1490a01-5836-4c3e-80c2-e05732ab1d8c", + "metadata": {}, + "source": [ + "See [Wikipedia](https://en.wikipedia.org/wiki/100_prisoners_problem) for a detailed description." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.4" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/source_code/islands.ipynb b/source_code/islands.ipynb new file mode 100644 index 0000000..456f3ce --- /dev/null +++ b/source_code/islands.ipynb @@ -0,0 +1,354 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "05b5376a-578d-4ed1-8c90-25ac3d32ff35", + "metadata": {}, + "source": [ + "# Requirements" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "4363c329-f572-455d-9459-edf752ce79a8", + "metadata": {}, + "outputs": [], + "source": [ + "import itertools\n", + "import random\n", + "import warnings" + ] + }, + { + "cell_type": "markdown", + "id": "3ea27891-1a3c-45da-ac81-57c914837a53", + "metadata": {}, + "source": [ + "# Problem" + ] + }, + { + "cell_type": "markdown", + "id": "05b3ff8f-3b86-4d87-9929-8569f6a40a6e", + "metadata": {}, + "source": [ + "Consider a \"map\", i.e., a matrix of values of 0 and 1. 0 represents water, 1 represents land. Islands are formed by elements that have the value 1 and that touch horizontally or vertically, not diagonally." + ] + }, + { + "cell_type": "markdown", + "id": "eb34ef89-77e6-4976-82b3-ed05f836872e", + "metadata": {}, + "source": [ + "We start by writing a function to create a map." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "9c4e1c7e-4e33-47d4-8028-3510154e295e", + "metadata": {}, + "outputs": [], + "source": [ + "def create_map(nr_rows, nr_cols):\n", + " return [random.choices((0, 1), k=nr_cols)\n", + " for _ in range(nr_rows)]" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "febd5d8f-c7d8-4b9b-9f44-5c3d6a0b615d", + "metadata": {}, + "outputs": [], + "source": [ + "random.seed(1234)" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "e0d24aed-6b02-49b7-9753-cf4aadb374ee", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[[1, 0, 0, 1, 1, 1],\n", + " [1, 0, 1, 0, 0, 1],\n", + " [0, 1, 1, 0, 0, 0],\n", + " [0, 0, 1, 0, 1, 0],\n", + " [1, 0, 1, 0, 1, 1]]" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "world_map = create_map(5, 6)\n", + "world_map" + ] + }, + { + "cell_type": "markdown", + "id": "ca5a1019-6fc5-4cd8-8d2a-2836e7101e38", + "metadata": {}, + "source": [ + "The problem is to find the islands. In the map above, we have the following islands:\n", + "* {(0, 0), (1, 0)}\n", + "* {(0, 3), (0, 4), (0, 5), (1, 5)}\n", + "* {(1, 2), (2, 1), (2, 2), (3, 2), (4, 2)}\n", + "* {(3, 4), (4, 4), (4, 5)}\n", + "* {(4, 0)}" + ] + }, + { + "cell_type": "markdown", + "id": "e84623fb-3ec5-4240-b29d-b17c75b4f2aa", + "metadata": {}, + "source": [ + "# Implementation" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "3051f703-5baa-47d6-8364-f681d06683f5", + "metadata": {}, + "outputs": [], + "source": [ + "def find_islands(world_map):\n", + " islands = []\n", + " for row_nr, row in enumerate(world_map):\n", + " for col_nr in range(len(row)):\n", + " if world_map[row_nr][col_nr]:\n", + " next_islands = []\n", + " # A location can be added to 0, 1, or 2 islands. If it\n", + " # is added to 2 islands, it forms a bridge between them and\n", + " # they should be merged.\n", + " enlarged_islands = []\n", + " for island in islands:\n", + " if (row_nr, col_nr - 1) in island or (row_nr - 1, col_nr) in island:\n", + " island.add((row_nr, col_nr))\n", + " enlarged_islands.append(island)\n", + " else:\n", + " next_islands.append(island)\n", + " match len(enlarged_islands):\n", + " case 0:\n", + " # The location wasn't added ot any existing island, so it forms\n", + " # an island by itself.\n", + " next_islands.append({(row_nr, col_nr)})\n", + " case 1:\n", + " # The location was added to a single existing island.\n", + " next_islands.append(enlarged_islands[0])\n", + " case 2:\n", + " # The location was added to 2 islands, they have to be merged.\n", + " new_island = enlarged_islands[0].union(enlarged_islands[1])\n", + " next_islands.append(new_island)\n", + " case _:\n", + " warnings.warn(\"# error\")\n", + " islands = next_islands\n", + " return islands " + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "69a96fd9-1d1b-41f3-a93a-c096ef080bc2", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[[1, 0, 0, 1, 1, 1],\n", + " [1, 0, 1, 0, 0, 1],\n", + " [0, 1, 1, 0, 0, 0],\n", + " [0, 0, 1, 0, 1, 0],\n", + " [1, 0, 1, 0, 1, 1]]" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "world_map" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "e9127210-2bf6-4d75-95cd-4082110cbf1a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[{(0, 0), (1, 0)},\n", + " {(0, 3), (0, 4), (0, 5), (1, 5)},\n", + " {(4, 0)},\n", + " {(1, 2), (2, 1), (2, 2), (3, 2), (4, 2)},\n", + " {(3, 4), (4, 4), (4, 5)}]" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "find_islands(world_map)" + ] + }, + { + "cell_type": "markdown", + "id": "c54781f7-e8fb-450b-8d45-cb29eb5725dd", + "metadata": {}, + "source": [ + "# Verification" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "89919c3e-5823-4490-863f-3d362e441b14", + "metadata": {}, + "outputs": [], + "source": [ + "def is_on_map(location, world_map):\n", + " nr_rows, nr_cols = len(world_map), len(world_map[0])\n", + " return 0 <= location[0] < nr_rows and 0 <= location[1] < nr_cols" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "5249834f-1a01-4823-b981-1b0c3a20c5b4", + "metadata": {}, + "outputs": [], + "source": [ + "def get_neighbours(location, world_map):\n", + " neighbours = []\n", + " for delta in (-1, 1):\n", + " neighbour = (location[0] + delta, location[1])\n", + " if is_on_map(neighbour, world_map):\n", + " neighbours.append(neighbour)\n", + " neighbour = (location[0], location[1] + delta)\n", + " if is_on_map(neighbour, world_map):\n", + " neighbours.append(neighbour)\n", + " return neighbours" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "d440f138-1fb6-4b68-b7fb-0204ce4ba54c", + "metadata": {}, + "outputs": [], + "source": [ + "def is_island(island, world_map):\n", + " for location in island:\n", + " if world_map[location[0]][location[1]] != 1:\n", + " return False\n", + " for neighbour in get_neighbours(location, world_map):\n", + " if world_map[neighbour[0]][neighbour[1]] == 1 and neighbour not in island:\n", + " return False\n", + " if world_map[neighbour[0]][neighbour[1]] == 0 and neighbour in island:\n", + " return False\n", + " return True" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "31a9ea41-357b-47bc-a254-e5a7744da5c9", + "metadata": {}, + "outputs": [], + "source": [ + "def are_islands(islands, world_map):\n", + " for island in islands:\n", + " if not is_island(island, world_map):\n", + " return False\n", + " for i, island1 in enumerate(islands):\n", + " for island2 in islands[i + 1:]:\n", + " if island1.intersection(island2):\n", + " return False\n", + " nr_rows, nr_cols = len(world_map), len(world_map[0])\n", + " all_land = {(i, j) for i, j in itertools.product(range(nr_rows), range(nr_cols))\n", + " if world_map[i][j] == 1}\n", + " all_island_land = set()\n", + " for island in islands:\n", + " all_island_land.update(island)\n", + " return all_land == all_island_land" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "4a9ece8e-8687-40cc-aa8e-77319390efb0", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 31, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "are_islands(islands, world_map)" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "c40c3177-5f12-4124-9f21-017ce2260f74", + "metadata": {}, + "outputs": [], + "source": [ + "for _ in range(100):\n", + " world_map = create_map(20, 20)\n", + " islands = find_islands(world_map)\n", + " if not are_islands(islands, world_map):\n", + " print(world_map)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b20f8f13-6ed8-4bd5-bdf9-9539d38dd4c2", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.4" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/source_code/josephus.ipynb b/source_code/josephus.ipynb new file mode 100644 index 0000000..86549e1 --- /dev/null +++ b/source_code/josephus.ipynb @@ -0,0 +1,430 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Requirements" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import math" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Problem setting" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Jewish rebels were under siege in the Massada fortress by the Romans. Since they could not hold out any longer, would not surrender, and their religion prohibits suicide, they came up with a creative solution.\n", + "\n", + "The rebels stand in a circle. The first rebel kill the second, the third kills the fourth, and so on. In general, the rebel standing clockwise from the one who was just killed, kills the next one in the cricle (clockwise). The last man standing will commit suicide.\n", + "\n", + "Josephus would prefer to live, so he has to be the last man standing, so that he can surrender to the Romans. Which position in the circle should he take when there are 41 rebels?" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Implementations" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Several implementations are possible." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Formula" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In general, when there are $n$ rebels, this can be written as $2^a + \\ell$ where $\\ell < 2^a$. The position of the last man standing is $2\\ell + 1$." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "def last_position(n):\n", + " a = int(math.log2(n))\n", + " return 2*(n - 2**a) + 1" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can use this function to test the various implementations." + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [], + "source": [ + "def test_kill_method(kill_method, max_rebels=42):\n", + " for nr_rebels in range(1, max_rebels + 1):\n", + " if kill_method(nr_rebels) != last_position(nr_rebels):\n", + " print(f'Problem for {nr_rebels}: {kill_method(nr_rebels):3d} versus {last_position(nr_rebels):3d}')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Classic implementation" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The group of rebels is modeled as a list, from which the element that represents the victim is removed. An index keeps track of the rebel that will be the next killer. When there is only a single rebel left, the problem is solved." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "def kill_rebels(n, is_animated=False):\n", + " rebels = list(range(1, n + 1))\n", + " if is_animated:\n", + " print(rebels)\n", + " killer_pos = 0\n", + " while len(rebels) > 1:\n", + " victim_pos = (killer_pos + 1) % len(rebels)\n", + " rebels.pop(victim_pos)\n", + " if is_animated:\n", + " print(rebels)\n", + " killer_pos = victim_pos\n", + " return rebels[0]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Testing the implementation shows that it works well." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[1, 2, 3, 4, 5]\n", + "[1, 3, 4, 5]\n", + "[1, 3, 5]\n", + "[3, 5]\n", + "[3]\n" + ] + }, + { + "data": { + "text/plain": [ + "3" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "kill_rebels(5, is_animated=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [], + "source": [ + "test_kill_method(kill_rebels)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Recursive approach" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "There is a recursive approach that doesn't require modulo operations to keep track of the index of the killer. When the group of remaining rebels is viewed as a circle, in each step, the circle shrinks with one. Also, if the new circle always has the next killer on top, it is really easy to keep track." + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [], + "source": [ + "def kill_rebels_recursively(n):\n", + " rebels = list(range(1, n + 1))\n", + " def _kill_rebels(rebels):\n", + " if len(rebels) == 1:\n", + " return rebels[0]\n", + " else:\n", + " killer = rebels.pop(0)\n", + " rebels.pop(0)\n", + " rebels.append(killer)\n", + " return _kill_rebels(rebels)\n", + " return _kill_rebels(rebels)" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "19" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "kill_rebels_recursively(41)" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": {}, + "outputs": [], + "source": [ + "test_kill_method(kill_rebels_recursively)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Iterative implementation" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This same algorithm is easy to implement iteratively as well." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "def kill_rebels_iteratively(n):\n", + " rebels = list(range(1, n + 1))\n", + " while len(rebels) > 1:\n", + " killer = rebels.pop(0)\n", + " rebels.pop(0)\n", + " rebels.append(killer)\n", + " return rebels[0]" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": {}, + "outputs": [], + "source": [ + "test_kill_method(kill_rebels_iteratively)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Sieve implementation" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "An alternative implementation removes all dead rebels for a single traversal of the circle. When that is done, we remove the first element of the remaining list if the length of the original circle length was odd, since the first member will certainly be killed in the next round. Again, we are done when the list has a single element only." + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": {}, + "outputs": [], + "source": [ + "def kill_rebels_sieve(n):\n", + " rebels = list(range(1, n + 1))\n", + " while True:\n", + " is_odd = len(rebels) % 2 == 1\n", + " rebels = rebels[::2]\n", + " if len(rebels) == 1:\n", + " return rebels[0]\n", + " elif is_odd:\n", + " rebels.pop(0)" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": {}, + "outputs": [], + "source": [ + "test_kill_method(kill_rebels_sieve)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Performance" + ] + }, + { + "cell_type": "raw", + "metadata": {}, + "source": [ + "nr_rebels = 345984" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "12.7 µs ± 1.08 µs per loop (mean ± std. dev. of 7 runs, 100000 loops each)\n" + ] + } + ], + "source": [ + "%timeit kill_rebels(nr_rebels)" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "20.4 µs ± 3.06 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)\n" + ] + } + ], + "source": [ + "%timeit kill_rebels_recursively(nr_rebels)" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "15.4 µs ± 909 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)\n" + ] + } + ], + "source": [ + "%timeit kill_rebels_iteratively(nr_rebels)" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2.65 µs ± 245 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)\n" + ] + } + ], + "source": [ + "%timeit kill_rebels_sieve(nr_rebels)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Results are not unexpected. The classic implementation is faster than the recursive algorithm, as well as its iterative counterpart. However, the sieve turns out the have the best performance. This should not come as a surprise. Although each iteration is fairly expensive, i.e., constructing and copying a new list, the number of iterations is $O(\\log_2 n)$, rather than $O(n)$ as for the other implementations, and that outweighs the cost per iteration." + ] + } + ], + "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.9" + }, + "toc-autonumbering": true + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/source_code/kaprekar.ipynb b/source_code/kaprekar.ipynb new file mode 100644 index 0000000..dbe1c91 --- /dev/null +++ b/source_code/kaprekar.ipynb @@ -0,0 +1,641 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "0b4b34ee-fae3-4f12-80cd-f5b1c74ac552", + "metadata": {}, + "source": [ + "# Requirements" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "b654353f-1806-4921-9100-20c6ac604e39", + "metadata": {}, + "outputs": [], + "source": [ + "from collections import Counter\n", + "import matplotlib.pyplot as plt\n", + "%matplotlib inline\n", + "import random" + ] + }, + { + "cell_type": "markdown", + "id": "e4e0df10-d320-4c64-90c6-10e5aa95b4c7", + "metadata": {}, + "source": [ + "# Problem setting" + ] + }, + { + "cell_type": "markdown", + "id": "de9b171d-3e37-4d91-b68c-14419434e5bf", + "metadata": {}, + "source": [ + "The Kaprekar constant is computed using an iterative process that starts from an arbitrary four-digit number that consists of at least two distict digits. Two new numbers are constructed that consist of the same digits, but sorted in descending order for the first, and in descending order for the second. The smallest of these numbers is substracted from the largest to procuce a new number. This procedure is repeated until the number no longer changes." + ] + }, + { + "cell_type": "markdown", + "id": "3c922171-ffc9-4086-b2aa-606832919d66", + "metadata": {}, + "source": [ + "# Auxillary functions" + ] + }, + { + "cell_type": "markdown", + "id": "b3b06ea4-7e92-49cc-8f9c-5f4880492f33", + "metadata": {}, + "source": [ + "We need functions to convert a number to a list of digits, and vice versa." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "12d6e736-2636-4fe6-9059-eaa0ac2c5247", + "metadata": {}, + "outputs": [], + "source": [ + "def num2digits(n):\n", + " digits = []\n", + " for _ in range(4):\n", + " digits.append(n % 10)\n", + " n //= 10\n", + " return digits" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "ae3d9d91-0e36-43bf-9e10-99ff92db504c", + "metadata": {}, + "outputs": [], + "source": [ + "def digits2num(digits):\n", + " n = 0\n", + " for i, digit in enumerate(digits):\n", + " n += digit*10**i\n", + " return n" + ] + }, + { + "cell_type": "markdown", + "id": "744080e7-2778-47cd-b487-4e41ce8c6771", + "metadata": {}, + "source": [ + "We will also need a function to verify that a number has at least two distinct digits." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "64415c3e-468f-43d8-98df-2f9f709e9aa4", + "metadata": {}, + "outputs": [], + "source": [ + "def is_valid_number(n):\n", + " return len(set(f'{n:04d}')) > 1" + ] + }, + { + "cell_type": "markdown", + "id": "68d24859-1087-4281-8272-b2f2c4658730", + "metadata": {}, + "source": [ + "To test, we convert a number to its list of digits, and back again. If the result is the original number, the test succeeds." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "62f1d12e-d009-4b40-93d9-42b4dbb5f2bd", + "metadata": {}, + "outputs": [], + "source": [ + "for _ in range(1_000):\n", + " n = random.randint(1000, 9999)\n", + " assert(n == digits2num(num2digits(n)))" + ] + }, + { + "cell_type": "markdown", + "id": "4c7d44e0-58c3-4332-ad5d-4305b5ece556", + "metadata": {}, + "source": [ + "# Computing the constant" + ] + }, + { + "cell_type": "markdown", + "id": "3a6f3687-b5bb-41bd-a227-12929563268a", + "metadata": {}, + "source": [ + "We define a function to perform a single step in this computation." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "bbe88f26-ba05-4aab-a545-46a3cf834cd6", + "metadata": {}, + "outputs": [], + "source": [ + "def kaprekar_step(n):\n", + " digits = num2digits(n)\n", + " high_number = digits2num(sorted(digits, reverse=False))\n", + " low_number = digits2num(sorted(digits, reverse=True))\n", + " return high_number - low_number" + ] + }, + { + "cell_type": "markdown", + "id": "8df78b24-4519-4ae3-83db-b156043bbca7", + "metadata": {}, + "source": [ + "For example:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "f18ffbfd-ac28-4ed7-aa7b-7f2b4d763e2e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "9261" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "kaprekar_step(1049)" + ] + }, + { + "cell_type": "markdown", + "id": "6ec3c812-70e4-466d-8c44-73ef99b8658b", + "metadata": {}, + "source": [ + "Now we can define a function that will iterate until a fixed point is reached, or that will raise an exception when a maximum number of steps has been exceeded." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "9c64daf1-f19b-4bb5-a129-0c3ea45fb89b", + "metadata": {}, + "outputs": [], + "source": [ + "def kaprekar_sequence(n, max_steps=100):\n", + " sequence = []\n", + " previous_n = -1\n", + " while n != previous_n:\n", + " if len(sequence) > max_steps:\n", + " raise RuntimeError('too many iterations')\n", + " sequence.append(n)\n", + " previous_n = n\n", + " n = kaprekar_step(n)\n", + " return sequence" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "610a1cbe-a1ba-41db-ba0c-fe91a179b213", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[4934, 5994, 5355, 1998, 8082, 8532, 6174]" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "kaprekar_sequence(4934)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "37338e29-d9d4-4aca-bae4-8e43bfbf674f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[6174]" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "kaprekar_sequence(6174)" + ] + }, + { + "cell_type": "markdown", + "id": "76ef7d14-4a1b-431f-b486-c95199708667", + "metadata": {}, + "source": [ + "# Step distribution" + ] + }, + { + "cell_type": "markdown", + "id": "a7711b39-052c-4a4d-8728-8c689c1cd0e9", + "metadata": {}, + "source": [ + "We now define a function that will compute the distribution of the number of steps required for all integers in the domain of the function, so for $1000 \\le n \\le 9999$ such that $n$ has at least two distict digits." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "618af509-cb5f-444f-a4da-15fc266ace9f", + "metadata": {}, + "outputs": [], + "source": [ + "def compute_kaprekar_step_distribution():\n", + " steps = Counter()\n", + " for n in range(1_000, 10_000):\n", + " if is_valid_number(n):\n", + " nr_steps = 0\n", + " while n != 6174:\n", + " nr_steps += 1\n", + " n = kaprekar_step(n)\n", + " steps[nr_steps] += 1\n", + " return steps" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "bb47c6c6-4cd2-41c2-8b0f-ecd547b149d0", + "metadata": {}, + "outputs": [], + "source": [ + "step_distribution = compute_kaprekar_step_distribution()" + ] + }, + { + "cell_type": "markdown", + "id": "40050714-d518-41fa-8873-fceaf2228f5c", + "metadata": {}, + "source": [ + "We can visualize the distribution in a histogram." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "2e97cb84-28de-42b2-8051-ba9c5d976a17", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "_ = plt.bar(step_distribution.keys(), step_distribution.values())" + ] + }, + { + "cell_type": "markdown", + "id": "acc406aa-3e2a-49e0-8d5f-3c022d88f8f8", + "metadata": {}, + "source": [ + "When considering this distributoin, it is clear that if we have compute the sequence of steps for numbers that take, e.g., 7 steps, we have implicitly computed the number of steps required for other numbers as well. If we cache the result, we may be able to speed up the computation." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "8f9ff646-7a9f-4192-aa6d-3d6f7e64656c", + "metadata": {}, + "outputs": [], + "source": [ + "def compute_kaprekar_steps():\n", + " steps = {}\n", + " for n in range(1_000, 10_000):\n", + " if is_valid_number(n):\n", + " orig_n = n\n", + " nr_steps = 0\n", + " while n != 6_174:\n", + " n = kaprekar_step(n)\n", + " nr_steps += 1\n", + " steps[orig_n] = nr_steps\n", + " return steps" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "98cbcd8b-7497-49eb-a61f-81e43ba440cc", + "metadata": {}, + "outputs": [], + "source": [ + "def compute_kaprekar_steps_cached():\n", + " steps = {}\n", + " for n in range(1_000, 10_000):\n", + " if is_valid_number(n):\n", + " orig_n = n\n", + " nr_steps = 0\n", + " while n != 6_174:\n", + " if n in steps:\n", + " steps[orig_n] = nr_steps + steps[n]\n", + " break\n", + " else:\n", + " n = kaprekar_step(n)\n", + " nr_steps += 1\n", + " else:\n", + " steps[orig_n] = nr_steps\n", + " return steps" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "dfa13a6c-88c2-4236-9b72-48664a3f69e1", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "52 ms ± 173 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)\n" + ] + } + ], + "source": [ + "%timeit steps_cached = compute_kaprekar_steps_cached()" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "ee23d010-fd4a-4273-97fc-9e273bc31b79", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "95.5 ms ± 374 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)\n" + ] + } + ], + "source": [ + "%timeit steps = compute_kaprekar_steps()" + ] + }, + { + "cell_type": "markdown", + "id": "4cfc08c7-5822-4056-8cd9-dd548f33164f", + "metadata": {}, + "source": [ + "Checking the correctness of the implementation by comparing the results." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "6028df0b-0750-4263-b768-d02870fff81d", + "metadata": {}, + "outputs": [], + "source": [ + "steps_cached = compute_kaprekar_steps_cached()\n", + "steps = compute_kaprekar_steps()\n", + "\n", + "for n in steps:\n", + " if n >= 1_000:\n", + " assert(steps[n] == steps_cached[n])\n", + "for n in steps_cached:\n", + " if n >= 1_000:\n", + " assert(steps[n] == steps_cached[n])" + ] + }, + { + "cell_type": "markdown", + "id": "1ba117cd-9766-488f-939a-a6eacb749f59", + "metadata": {}, + "source": [ + "Indeed, caching reduces the compute time by almost a factor of two." + ] + }, + { + "cell_type": "markdown", + "id": "604593cc-7ca9-487b-85bf-54aeeb914a4c", + "metadata": {}, + "source": [ + "# Recursive implementation" + ] + }, + { + "cell_type": "markdown", + "id": "75a2a26a-f19f-471f-8bd7-9c69ef32b937", + "metadata": {}, + "source": [ + "If we create a recursive function to compute the number of steps, we can use the LRU-cache in Python's standard libray." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "5f8da672-60d6-42b6-8aa6-31dd1ab9e4bb", + "metadata": {}, + "outputs": [], + "source": [ + "def kaprekar_steps_recursive(n):\n", + " if n == 6_174:\n", + " return 0\n", + " elif not is_valid_number(n):\n", + " return 1\n", + " else:\n", + " n = kaprekar_step(n)\n", + " return 1 + kaprekar_steps_recursive(n)" + ] + }, + { + "cell_type": "markdown", + "id": "f87c2d96-1558-4b81-b66e-bd3f7bae928d", + "metadata": {}, + "source": [ + "First verify the function works correectly." + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "7a7d4506-d720-4bfa-9eea-2f92916afcd5", + "metadata": {}, + "outputs": [], + "source": [ + "for n, nr_steps in steps.items():\n", + " assert(nr_steps == kaprekar_steps_recursive(n))" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "99211c3d-653f-4c8e-a6e9-1e0499ef8af1", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "113 ms ± 421 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)\n" + ] + } + ], + "source": [ + "%timeit for n in range(1_000, 10_000): kaprekar_steps_recursive(n)" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "42be1cc2-bb41-4931-b524-e1929bf1d1ac", + "metadata": {}, + "outputs": [], + "source": [ + "from functools import lru_cache" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "4d567764-aee4-4cd4-8ebd-f417095ffd51", + "metadata": {}, + "outputs": [], + "source": [ + "@lru_cache\n", + "def kaprekar_steps_recursive_cached(n):\n", + " if n == 6_174:\n", + " return 0\n", + " elif not is_valid_number(n):\n", + " return 1\n", + " else:\n", + " n = kaprekar_step(n)\n", + " return 1 + kaprekar_steps_recursive_cached(n)" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "694a7c2b-ccf2-4683-ab41-6ccaf9c8084b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "27.7 ms ± 177 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)\n" + ] + } + ], + "source": [ + "%timeit for n in range(1_000, 10_000): kaprekar_steps_recursive_cached(n)" + ] + }, + { + "cell_type": "markdown", + "id": "9826f856-9622-4bcf-89d7-9bf8240267f0", + "metadata": {}, + "source": [ + "Using the LRU-cache significantly speeds up the code." + ] + }, + { + "cell_type": "markdown", + "id": "49180f03-4a90-4424-8f1a-71aed0672645", + "metadata": {}, + "source": [ + "For comparison the iterative implementation." + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "57b05866-c314-484d-8f68-7dca59aa24e6", + "metadata": {}, + "outputs": [], + "source": [ + "def kaprekar_steps_iterative(n):\n", + " if is_valid_number(n):\n", + " nr_steps = 0\n", + " while n != 6174:\n", + " nr_steps += 1\n", + " n = kaprekar_step(n)\n", + " return nr_steps\n", + " else:\n", + " return 1" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "feaa3603-b56b-44ed-8843-156c3dc6aeb9", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "96.4 ms ± 239 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)\n" + ] + } + ], + "source": [ + "%timeit for n in range(1_000, 10_000): kaprekar_steps_iterative(n)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/source_code/knights_tour.ipynb b/source_code/knights_tour.ipynb new file mode 100644 index 0000000..44204bf --- /dev/null +++ b/source_code/knights_tour.ipynb @@ -0,0 +1,1117 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "7c5e2975-6547-4ed6-9f6e-8f13aa65843d", + "metadata": {}, + "source": [ + "# Requirements" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "d51322fb-c8f6-4d9e-a378-1b3653b86ae4", + "metadata": {}, + "outputs": [], + "source": [ + "import math\n", + "import sys" + ] + }, + { + "cell_type": "markdown", + "id": "c9d355ab-4965-405f-ac5d-5d9c2843f799", + "metadata": {}, + "source": [ + "# Problem" + ] + }, + { + "cell_type": "markdown", + "id": "19e39cbf-13b3-4cd7-b497-252ac5d68ea0", + "metadata": {}, + "source": [ + "We want to complete a knight's tour on a square board. A knight's tour is a sequence of moves (like the knight in chess) on the board such that each position on the board is visited exactly once.\n", + "\n", + "The $1 \\times 1$ board is trivial, there is a single trivial tour. It is immediately clear that there are no tours for $2 \\times 2$ and $3 \\times 3$ boards. Maybe less trivial to see, there is also no tour for a $4 \\times 4$ board. An example of a knight's tour on a $8 \\times 8$ board is given below:\n", + "\n", + "$$\n", + " \\begin{array}{cccccccc}\n", + " 1 & 38 & 55 & 34 & 3 & 36 & 19 & 22 \\\\\n", + " 54 & 47 & 2 & 37 & 20 & 23 & 4 & 17 \\\\\n", + " 39 & 56 & 33 & 46 & 35 & 18 & 21 & 10 \\\\\n", + " 48 & 53 & 40 & 57 & 24 & 11 & 16 & 5 \\\\\n", + " 59 & 32 & 45 & 52 & 41 & 26 & 9 & 12 \\\\\n", + " 44 & 49 & 58 & 25 & 62 & 15 & 6 & 27 \\\\\n", + " 31 & 60 & 51 & 42 & 29 & 8 & 13 & 64 \\\\\n", + " 50 & 43 & 30 & 61 & 14 & 63 & 28 & 7 \\\\\n", + " \\end{array}\n", + "$$\n", + "\n", + "In theory, it would be possible to solve this problem by brute force, i.e., generate permutation of $\\{1, \\ldots, n^2\\}$ and verify whether they form a valid knight's tour, stopping when the first one is found. Unfortunately, the number of permutations grows very fast as $O(n^2!)$. This is prohibitive in practice even for small boards." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "fbe71852-8a4a-429b-af16-f25632dff99b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "4x4 board: 2.09e+13\n", + "5x5 board: 1.55e+25\n", + "6x6 board: 3.72e+41\n", + "7x7 board: 6.08e+62\n", + "8x8 board: 1.27e+89\n", + "9x9 board: 5.80e+120\n" + ] + } + ], + "source": [ + "for n in range(4, 10):\n", + " print(f'{n}x{n} board: {math.factorial(n**2):.2e}')" + ] + }, + { + "cell_type": "markdown", + "id": "4fbd2879-bc58-4d7a-b509-d3d037cd98bc", + "metadata": {}, + "source": [ + "We will have to find a more efficient approach." + ] + }, + { + "cell_type": "markdown", + "id": "597510a9-220c-4f26-ac26-8f8624e4f608", + "metadata": {}, + "source": [ + "# Backtracking" + ] + }, + { + "cell_type": "markdown", + "id": "a4697336-97da-4616-832d-2f3c547f40af", + "metadata": {}, + "source": [ + "A technique that is useful for a problem of this type is backtracking. Make legal moves until you get stuck or cover the board. In the former case, backtrack to the last position where you could choose, and take another option. This requires bookkeeping of which options where tried at some point in time." + ] + }, + { + "cell_type": "markdown", + "id": "f39d8b7d-ab28-45e2-8593-ab95314efe9f", + "metadata": {}, + "source": [ + "## Implementation" + ] + }, + { + "cell_type": "markdown", + "id": "2195bce2-960e-48f9-bb7e-9aa3d16622e9", + "metadata": {}, + "source": [ + "We will use backtracking to solve this problem. In order to keep track of the state, we define a class." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "b5f7086d-8c17-4fe4-aa43-bc314d3de69c", + "metadata": {}, + "outputs": [], + "source": [ + "class Options:\n", + " '''class to keep track of options to be checked in a backtracking\n", + " algorithm.\n", + " '''\n", + " \n", + " def __init__(self, options):\n", + " '''create a new object that represents the options at a choice point\n", + " \n", + " Parameters\n", + " ----------\n", + " options: iterable[value_type]\n", + " iterable that represents the potential options\n", + " '''\n", + " self._options = list(options)\n", + " self._current_idx = 0\n", + " \n", + " @property\n", + " def nr_options(self):\n", + " '''number of options\n", + " \n", + " Returns\n", + " -------\n", + " int\n", + " number of options that are available in total at this point\n", + " '''\n", + " return len(self._options)\n", + " \n", + " def has_option(self):\n", + " '''check whether a next option is still available\n", + " \n", + " Returns\n", + " -------\n", + " bool\n", + " True if more options are available, False otherwise\n", + " '''\n", + " return self._current_idx < self.nr_options\n", + " \n", + " @property\n", + " def current_option(self):\n", + " '''return the current option, and advance to the next option\n", + " \n", + " Returns\n", + " -------\n", + " value_type\n", + " current option value, and advance to next\n", + " Raises\n", + " ------\n", + " IndexError\n", + " raised if no more options are available\n", + " '''\n", + " if self.has_option():\n", + " option = self._options[self._current_idx]\n", + " self._current_idx += 1\n", + " return option\n", + " else:\n", + " raise IndexError(f'all options exhausted')\n", + " \n", + " def peek(self):\n", + " '''get the current option without advancing to the next\n", + " \n", + " Returns\n", + " -------\n", + " value_type\n", + " current option value\n", + " Raises\n", + " ------\n", + " IndexError\n", + " raised if either current_option hasn't been called yet, or no more options\n", + " are available\n", + " '''\n", + " if 0 <= self._current_idx - 1 < self.nr_options:\n", + " return self._options[self._current_idx - 1]\n", + " elif self._current_idx - 1 < 0:\n", + " raise IndexError('enumeration not initialized, first call current_option')\n", + " else:\n", + " raise IndexError('all options exhausted')\n", + "\n", + " def __repr__(self):\n", + " '''compute string representation of the object\n", + " \n", + " Returns\n", + " -------\n", + " str\n", + " string representation of the object's state\n", + " '''\n", + " return f'{self._current_idx} -> {self._options}'" + ] + }, + { + "cell_type": "markdown", + "id": "af0649a8-83e9-45af-80b7-fd7414c40f18", + "metadata": {}, + "source": [ + "The code below illustrates the use of such objects." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "2c92a116-623a-46c5-9e25-1dd3da7df04e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "3 3\n", + "5 5\n", + "7 7\n" + ] + }, + { + "data": { + "text/plain": [ + "7" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "options = Options([3, 5, 7])\n", + "while options.has_option():\n", + " print(options.current_option, end='')\n", + " print(f' {options.peek()}')\n", + "options.peek()" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "76746553-0996-4e71-b119-2f3417ecf4e9", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "3 -> [3, 5, 7]" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "options" + ] + }, + { + "cell_type": "markdown", + "id": "e37bf8f8-f0ef-4311-961e-12c3a2ea1dcd", + "metadata": {}, + "source": [ + "For better performance, precompute the moves that are legal on each position on the board, not taking into account occupancy.\n", + "\n", + "The positions of the board will be given by a single index ranging from $0$ to $n^2 - 1$, row-wise, i.e., for a $3 \\times 3$ board:\n", + "\n", + "$$\n", + " \\begin{array}{ccc}\n", + " 0 & 1 & 2 \\\\\n", + " 3 & 4 & 5 \\\\\n", + " 6 & 7 & 8 \\\\\n", + " \\end{array}\n", + "$$" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "31e73e9c-6f07-4033-a1c8-919db0086eaa", + "metadata": {}, + "outputs": [], + "source": [ + "def compute_all_moves(n):\n", + " '''compute the possible moves on each position of the board\n", + " \n", + " Parameters\n", + " ----------\n", + " n: int\n", + " dimension of the n-by-n board\n", + " \n", + " Returns\n", + " -------\n", + " list[list[int]]\n", + " a list that contains lists of board positions; board positions are number row-wise from\n", + " 0 to n**2 - 1.\n", + " '''\n", + " deltas = [(-2, 1), (-1, 2), (1, 2), (2, 1), (2, -1), (1, -2), (-1, -2), (-2, -1)]\n", + " all_moves = []\n", + " for k in range(n**2):\n", + " all_moves.append([])\n", + " i, j = k//n, k % n\n", + " for delta_i, delta_j in deltas:\n", + " new_i, new_j = i + delta_i, j + delta_j\n", + " if 0 <= new_i < n and 0 <= new_j < n:\n", + " all_moves[-1].append(new_i*n + new_j)\n", + " return all_moves" + ] + }, + { + "cell_type": "markdown", + "id": "9f5a570d-874f-46af-8d19-c9185bf5c29d", + "metadata": {}, + "source": [ + "For instance, for a $4 \\times 4$ board:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "054cf04d-68fd-4313-9f26-adab9638774e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[[6, 9],\n", + " [7, 10, 8],\n", + " [11, 9, 4],\n", + " [10, 5],\n", + " [2, 10, 13],\n", + " [3, 11, 14, 12],\n", + " [15, 13, 8, 0],\n", + " [14, 9, 1],\n", + " [1, 6, 14],\n", + " [2, 7, 15, 0],\n", + " [3, 12, 4, 1],\n", + " [13, 5, 2],\n", + " [5, 10],\n", + " [6, 11, 4],\n", + " [7, 8, 5],\n", + " [9, 6]]" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "compute_all_moves(4)" + ] + }, + { + "cell_type": "markdown", + "id": "cfe6d75b-bc9b-4076-ac29-3773f9eba1e1", + "metadata": {}, + "source": [ + "To check this, we can write a visualization function." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "b3b71b27-b151-4c86-abc9-4a384acfd69e", + "metadata": {}, + "outputs": [], + "source": [ + "def visualize_option(all_moves, pos):\n", + " '''visualize the possible moves at a given position\n", + " \n", + " Parameters\n", + " ----------\n", + " all_moves: list[list[int]]\n", + " moves for each position\n", + " pos: int\n", + " position given as a number from 0 to n**2 - 1\n", + " '''\n", + " n = math.isqrt(len(all_moves))\n", + " for i in range(n):\n", + " for j in range(n):\n", + " k = i*n + j\n", + " if k == pos:\n", + " print('x', end='')\n", + " elif pos in all_moves[k]:\n", + " print('o', end='')\n", + " else:\n", + " print('-', end='')\n", + " print()" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "17aa6b55-6ee8-4bc3-b0b5-53f75d7c3d62", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "------\n", + "--o-o-\n", + "-o---o\n", + "---x--\n", + "-o---o\n", + "--o-o-\n" + ] + } + ], + "source": [ + "visualize_option(compute_all_moves(6), 21)" + ] + }, + { + "cell_type": "markdown", + "id": "a17b3be1-c7ed-4770-8ac6-2702ca28e620", + "metadata": {}, + "source": [ + "Given the symmetries of a $n \\times n$ chess board, only a quarter of the possible first moves needs to be checked. These are the potential starting points for the tour(s)." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "a77275b8-4a6a-40a1-a881-6ae6db666892", + "metadata": {}, + "outputs": [], + "source": [ + "def compute_initial_moves(n):\n", + " '''compute the relevant initial moves, taking into account\n", + " the symmetry of the board\n", + " \n", + " Parameters\n", + " ----------\n", + " n: int\n", + " size of the n-by-n board\n", + " \n", + " Returns\n", + " -------\n", + " list[int]\n", + " positions to consider for the first move, i.e., the starting point of the\n", + " tour\n", + " '''\n", + " return [i*n + j for i in range(n//2 + n % 2)\n", + " for j in range(i, n//2 + n % 2)]" + ] + }, + { + "cell_type": "markdown", + "id": "1dd01416-6a18-41a3-be57-562295d76e3e", + "metadata": {}, + "source": [ + "The initial positions for a $5 \\times 5$ board are shown below." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "7a3fb455-4f17-48b4-a975-339b7d3074a3", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[0, 1, 2, 6, 7, 12]" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "compute_initial_moves(5)" + ] + }, + { + "cell_type": "markdown", + "id": "d1fb4dee-5ce3-40f2-a930-fec99a7d7b8f", + "metadata": {}, + "source": [ + "A tour will be a list of positions on the board. In order to visualize that, we can use the function below." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0ccb1546-3cd9-4ef5-9ad7-f4b488508462", + "metadata": {}, + "outputs": [], + "source": [ + "def print_tour(tour):\n", + " '''visualize a tour on the board\n", + " \n", + " Parameters\n", + " ----------\n", + " tour: list[int]\n", + " the sequence of positions on the board\n", + " '''\n", + " n = math.isqrt(len(tour))\n", + " board = [[0]*n for _ in range(n)]\n", + " for i, pos in enumerate(tour):\n", + " board[pos//n][pos % n] = i + 1\n", + " for i in range(n):\n", + " print(' '.join(map(lambda x: f'{x:4d}', board[i])))" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "bccb2473-d18a-4824-b2da-f9acbcb46a87", + "metadata": {}, + "outputs": [], + "source": [ + "def find_tour(n):\n", + " '''find a knight's tour on an n-by-n board using backtracking\n", + " \n", + " Parameters\n", + " ----------\n", + " n: int\n", + " size of the n-by-n board\n", + " \n", + " Returns\n", + " -------\n", + " list[int]\n", + " the sequence of positions on the board that form a valid knight's tour,\n", + " or an empty list if no tour can be found\n", + " '''\n", + " all_moves = compute_all_moves(n)\n", + " stack = [Options(compute_initial_moves(n))]\n", + " tour = []\n", + " while stack:\n", + " is_updated = False\n", + " while stack[-1].has_option():\n", + " new_pos = stack[-1].current_option\n", + " if new_pos not in tour:\n", + " tour.append(new_pos)\n", + " stack.append(Options(all_moves[new_pos]))\n", + " is_updated = True\n", + " break\n", + " if len(tour) == n**2:\n", + " return tour\n", + " if not is_updated:\n", + " stack.pop()\n", + " if tour:\n", + " tour.pop()\n", + " return tour" + ] + }, + { + "cell_type": "markdown", + "id": "dca5acf8-910a-4ecc-a4ab-172408e76b9d", + "metadata": {}, + "source": [ + "Print a board with a knight's tour (if any) for board sizes $n \\in \\{1, \\ldots, 8\\}$:" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "7b9169b6-8d91-4dec-ad5d-a70284c41fc0", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "------ 1 ----------\n", + " 1\n", + "------ 2 ----------\n", + "------ 3 ----------\n", + "------ 4 ----------\n", + "------ 5 ----------\n", + " 1 20 17 12 3\n", + " 16 11 2 7 18\n", + " 21 24 19 4 13\n", + " 10 15 6 23 8\n", + " 25 22 9 14 5\n", + "------ 6 ----------\n", + " 1 28 33 20 3 26\n", + " 34 19 2 27 8 13\n", + " 29 32 21 12 25 4\n", + " 18 35 30 7 14 9\n", + " 31 22 11 16 5 24\n", + " 36 17 6 23 10 15\n", + "------ 7 ----------\n", + " 1 28 37 24 3 26 17\n", + " 36 39 2 27 18 11 4\n", + " 29 42 23 38 25 16 9\n", + " 40 35 30 19 10 5 12\n", + " 43 22 41 32 15 8 47\n", + " 34 31 20 45 48 13 6\n", + " 21 44 33 14 7 46 49\n", + "------ 8 ----------\n", + " 1 38 55 34 3 36 19 22\n", + " 54 47 2 37 20 23 4 17\n", + " 39 56 33 46 35 18 21 10\n", + " 48 53 40 57 24 11 16 5\n", + " 59 32 45 52 41 26 9 12\n", + " 44 49 58 25 62 15 6 27\n", + " 31 60 51 42 29 8 13 64\n", + " 50 43 30 61 14 63 28 7\n" + ] + } + ], + "source": [ + "for n in range(1, 9):\n", + " print(f'------ {n} ----------')\n", + " print_tour(find_tour(n))" + ] + }, + { + "cell_type": "markdown", + "id": "d402640b-6463-43cd-b7ef-cde15a0876fc", + "metadata": {}, + "source": [ + "We can easily write a function to verify whether a given tour is a valid knight's tour." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "d3fb6016-9c7a-44be-baf2-bd77099f3c80", + "metadata": {}, + "outputs": [], + "source": [ + "def is_valid_knights_tour(tour, is_verbose=False):\n", + " '''check whether a given sequence of positions represents a valid\n", + " knight's tour\n", + " \n", + " Parameters\n", + " ----------\n", + " tour: list[int]\n", + " sequence of board positions\n", + " is_verbose: bool\n", + " if True, print the reason why the tour is not valid, default is False\n", + " \n", + " Returns\n", + " -------\n", + " bool\n", + " True if the sequence is a knight's tour, False otherwise\n", + " '''\n", + " if len(tour) == 0:\n", + " if is_verbose:\n", + " print(f'length 0 tour')\n", + " return False\n", + " n = math.isqrt(len(tour))\n", + " if len(tour) != n**2:\n", + " if is_verbose:\n", + " print(f'length of the tour does not fit on a squaure board {n}x{n}', file=sys.stderr)\n", + " return False\n", + " if len(set(tour)) != len(tour):\n", + " if is_verbose:\n", + " print(f'duplicate positions in tour')\n", + " return False\n", + " if min(tour) != 0 or max(tour) != n**2 - 1:\n", + " if is_verbose:\n", + " print(f'invalid positions in tour')\n", + " deltas = [(-2, 1), (-1, 2), (1, 2), (2, 1), (2, -1), (1, -2), (-1, -2), (-2, -1)]\n", + " for i, pos in enumerate(tour[1:]):\n", + " if all(tour[i] != ((pos//n + delta_i)*n + pos % n + delta_j) for delta_i, delta_j in deltas):\n", + " print(f'illegal move from {tour[i]} to {pos}')\n", + " return False\n", + " return True" + ] + }, + { + "cell_type": "markdown", + "id": "de7c86c1-43b7-4b41-a2e9-d94dadaef0d0", + "metadata": {}, + "source": [ + "Check that an invalid sequence is not valid." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "464fba47-d52f-4d22-a02b-4acac9cf515b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "illegal move from 0 to 1\n" + ] + }, + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "is_valid_knights_tour(list(range(16)), True)" + ] + }, + { + "cell_type": "markdown", + "id": "bbe17029-dbaf-45fc-bea4-9cfa490e15c7", + "metadata": {}, + "source": [ + "Check tours for $n \\in \\{1, \\ldots, 8\\}$." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "545d75f6-17d4-402f-a55c-869cde8b214a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "------ 1 ----------\n", + "True\n", + "------ 2 ----------\n", + "False\n", + "------ 3 ----------\n", + "False\n", + "------ 4 ----------\n", + "False\n", + "------ 5 ----------\n", + "True\n", + "------ 6 ----------\n", + "True\n", + "------ 7 ----------\n", + "True\n", + "------ 8 ----------\n", + "True\n" + ] + } + ], + "source": [ + "for n in range(1, 9):\n", + " print(f'------ {n} ----------')\n", + " tour = find_tour(n)\n", + " print(is_valid_knights_tour(tour))" + ] + }, + { + "cell_type": "markdown", + "id": "ef5767e0-5114-4b30-8878-80f0150cd583", + "metadata": {}, + "source": [ + "## Performance" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "40a8b572-00bd-4e2d-b5f4-419820d8d650", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "68.9 µs ± 1.13 µs per loop (mean ± std. dev. of 7 runs, 10,000 loops each)\n" + ] + } + ], + "source": [ + "%timeit find_tour(5)" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "c36f323f-7f21-4a00-b526-72e5c7ccf8a8", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "10.3 ms ± 61.5 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n" + ] + } + ], + "source": [ + "%timeit find_tour(6)" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "6cecf705-03ce-45fe-815e-15928a32c4dc", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "26.7 s ± 497 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" + ] + } + ], + "source": [ + "%timeit find_tour(7)" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "f216d2ec-29f3-4f6c-8f43-e7e57b509245", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "8.79 s ± 49.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" + ] + } + ], + "source": [ + "%timeit find_tour(8)" + ] + }, + { + "cell_type": "markdown", + "id": "c3693282-403e-4fa6-ba41-479774d8dbc3", + "metadata": {}, + "source": [ + "The runtime increases rapidly, and patience runs out for a $9 \\times 9$ board. Can we do better?" + ] + }, + { + "cell_type": "markdown", + "id": "fb6418f5-7fa3-4807-90dc-5d4b9f0f7727", + "metadata": {}, + "source": [ + "# Wansdorff's algorithm" + ] + }, + { + "cell_type": "markdown", + "id": "835bb1f2-2f60-4943-9fc2-9ada4b88acf6", + "metadata": {}, + "source": [ + "It turns out we can, or rather, Wansdorff could. He proposed a heuristic that works excellently in practice.\n", + "\n", + "\n", + "1. Set P to be a random initial position on the board\n", + "1. Mark the board at P with the move number “1”\n", + "1. Do following for each move number from 2 to the number of squares on\n", + " the board:\n", + " * let S be the set of positions accessible from P.\n", + " * Set P to be the position in S with minimum accessibility\n", + " * Mark the board at P with the current move number\n", + "1. Return the marked board — each square will be marked with the move\n", + " number on which it is visited." + ] + }, + { + "cell_type": "markdown", + "id": "ea9f45dc-12f9-4c65-b5d8-164ea5b45150", + "metadata": {}, + "source": [ + "## Implementation" + ] + }, + { + "cell_type": "code", + "execution_count": 88, + "id": "c7e766e4-ab0d-4bf0-81f9-4bdc5ec1b15e", + "metadata": {}, + "outputs": [], + "source": [ + "def find_wansdorff_tour(n):\n", + " '''find a knight's tour on an n-by-n board using Wansdorff's algorithm\n", + " \n", + " Parameters\n", + " ----------\n", + " n: int\n", + " size of the n-by-n board\n", + " \n", + " Returns\n", + " -------\n", + " list[int]\n", + " the sequence of positions on the board that form a valid knight's tour,\n", + " or an empty list if no tour can be found\n", + " '''\n", + " def update_moves(all_moves, pos):\n", + " for move in all_moves[pos]:\n", + " try:\n", + " all_moves[move].remove(pos)\n", + " except ValueError:\n", + " pass\n", + " all_moves = compute_all_moves(n)\n", + " tour = [0]\n", + " update_moves(all_moves, tour[-1])\n", + " for move_nr in range(2, n**2 + 1):\n", + " min_pos, min_accessibility = None, n**2\n", + " for move in all_moves[tour[-1]]:\n", + " accessibility = len(all_moves[move])\n", + " if move not in tour and 0 <= accessibility < min_accessibility:\n", + " min_accessibility = accessibility\n", + " min_pos = move\n", + " if min_pos is None:\n", + " return []\n", + " tour.append(min_pos)\n", + " update_moves(all_moves, tour[-1])\n", + " return tour " + ] + }, + { + "cell_type": "code", + "execution_count": 90, + "id": "1cd3254d-c09f-4c57-909f-08fd3f4e9bf5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " 1 14 9 20 3\n", + " 24 19 2 15 10\n", + " 13 8 25 4 21\n", + " 18 23 6 11 16\n", + " 7 12 17 22 5\n" + ] + } + ], + "source": [ + "print_tour(find_wansdorff_tour(5))" + ] + }, + { + "cell_type": "markdown", + "id": "44cdf539-c4c7-4f3c-937b-37a5f4b69ff7", + "metadata": {}, + "source": [ + "## Performance" + ] + }, + { + "cell_type": "code", + "execution_count": 91, + "id": "56eaf07d-91e9-4459-b7a9-8c003402e894", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "27.6 µs ± 378 ns per loop (mean ± std. dev. of 7 runs, 10,000 loops each)\n" + ] + } + ], + "source": [ + "%timeit find_wansdorff_tour(5)" + ] + }, + { + "cell_type": "code", + "execution_count": 92, + "id": "629fd883-54c4-4268-93be-088917c5979f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "43.8 µs ± 270 ns per loop (mean ± std. dev. of 7 runs, 10,000 loops each)\n" + ] + } + ], + "source": [ + "%timeit find_wansdorff_tour(6)" + ] + }, + { + "cell_type": "code", + "execution_count": 93, + "id": "ab4f6205-23b7-4962-b277-4efb2a47818f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "66.2 µs ± 1.58 µs per loop (mean ± std. dev. of 7 runs, 10,000 loops each)\n" + ] + } + ], + "source": [ + "%timeit find_wansdorff_tour(7)" + ] + }, + { + "cell_type": "code", + "execution_count": 94, + "id": "b29816be-4099-4a67-b8da-8503878ddd77", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "92.4 µs ± 881 ns per loop (mean ± std. dev. of 7 runs, 10,000 loops each)\n" + ] + } + ], + "source": [ + "%timeit find_wansdorff_tour(8)" + ] + }, + { + "cell_type": "code", + "execution_count": 96, + "id": "51b9047e-6986-47e7-9530-b6555c921a4c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "63.7 ms ± 338 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)\n" + ] + } + ], + "source": [ + "%timeit find_wansdorff_tour(50)" + ] + }, + { + "cell_type": "code", + "execution_count": 95, + "id": "e84c70dc-36aa-4103-ae35-f643bf67d4c2", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1.29 s ± 27.8 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" + ] + } + ], + "source": [ + "%timeit find_wansdorff_tour(100)" + ] + }, + { + "cell_type": "markdown", + "id": "f84d6c6e-5cd4-49ce-b8be-903b114366fc", + "metadata": {}, + "source": [ + "The runtime is very low, and increases slowly with the size of the board, $O(n)$." + ] + }, + { + "cell_type": "markdown", + "id": "b4b1938f-8e9e-43bb-a404-b7d2aa03084a", + "metadata": {}, + "source": [ + "# Conclusions" + ] + }, + { + "cell_type": "markdown", + "id": "d3c41755-7754-4eb7-a0c9-9ad38b2aa24f", + "metadata": {}, + "source": [ + "This implementation is reasonably fast. In fact, it is much faster than the naive approach that woulld enumerate all permutations of the sequence $0, \\ldots, n^2 - 1$, and checking for each whether it represents a valid knight's tour." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/source_code/magic_squares.ipynb b/source_code/magic_squares.ipynb new file mode 100644 index 0000000..b889bac --- /dev/null +++ b/source_code/magic_squares.ipynb @@ -0,0 +1,655 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "426b74ad-0e4d-46e4-b7e0-fed030ce2fe8", + "metadata": {}, + "source": [ + "# Requirements" + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "id": "99dd3bd6-9c07-446e-adea-9101b0e1d033", + "metadata": {}, + "outputs": [], + "source": [ + "import itertools\n", + "import math\n", + "import operator\n", + "import time" + ] + }, + { + "cell_type": "markdown", + "id": "20b50f72-fa7c-4863-bbe1-27863b08cfad", + "metadata": {}, + "source": [ + "# Problem statement" + ] + }, + { + "cell_type": "markdown", + "id": "0450fee1-cd71-48fd-8621-5e82056a864a", + "metadata": {}, + "source": [ + "A magic square is an $n \\times n$ square matrix with elements $\\{1, \\ldots, n^2\\}$ such that the sum of each row, each column, the main diagonal and the skew diagonal are all equal.\n", + "\n", + "For example, the following $3 \\times 3$ square is magic:\n", + "$$\n", + "\\begin{array}{ccc}\n", + " 8 & 1 & 6 \\\\\n", + " 3 & 5 & 7 \\\\\n", + " 4 & 9 & 2 \\\\\n", + "\\end{array}\n", + "$$\n", + "\n", + "The magic constant for a $3 \\times 3$ square is 15. In general for an $n \\times n$ square, the sum of all the elements is $\\sum_{i=1}^{n^2} i = \\frac{1}{2}n^2(n^2 + 1)$. Since each of the row sums is equal to the magic constant $M$, and there are $n$ rows, $M = \\frac{1}{2}n(n^2 + 1)$.\n", + "\n", + "The magic $1 \\times 1$ square is of course trivial, and there is no $2 \\times 2$ squire. For larger values of $n$, we might naively construct magic squares by considering the permutations of the fundamental $n \\times n$ square (i.e., a square filled row by row with the number $1$ to $n^2$ is an option that soon turns out to be impossible since the number of permutation is $n^2!$." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "16dce5c9-b3d7-4764-bee1-fe026becf5dd", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0: 1 (1.00e+00)\n", + "1: 1 (1.00e+00)\n", + "2: 24 (2.40e+01)\n", + "3: 362880 (3.63e+05)\n", + "4: 20922789888000 (2.09e+13)\n", + "5: 15511210043330985984000000 (1.55e+25)\n" + ] + } + ], + "source": [ + "for n in range(6):\n", + " print(f'{n}: {math.factorial(n**2)} ({math.factorial(n**2):.2e})')" + ] + }, + { + "cell_type": "markdown", + "id": "fdb7f6b3-243c-4af6-998f-5695e6c2dabb", + "metadata": {}, + "source": [ + "It is clear that even for $4 \\times 4$ squares, the number of squares to test is prohibitively large. However, there are algorithms to construct magic squares. We will implement one for odd values of $n$ attributed to Simon de la Loubère." + ] + }, + { + "cell_type": "markdown", + "id": "05286744-6d59-4a6f-ad3e-ac6d10f81bf9", + "metadata": {}, + "source": [ + "# Helper functions" + ] + }, + { + "cell_type": "markdown", + "id": "9c431a94-49f2-4d60-831b-d2346b7e2894", + "metadata": {}, + "source": [ + "For performance reasons, we will represent an $n \\times n$ square as a list of integers, so its length is $n^2$. The square's elements are stored rowwise, so the element of the square at row $i$ and column $j$ is the list element at index $i \\cdot n + j$ where $i, j \\in \\{0, \\ldots, n - 1\\}$." + ] + }, + { + "cell_type": "markdown", + "id": "498773e3-23cd-4639-a9bf-6ead1cac225c", + "metadata": {}, + "source": [ + "We implement a simple function that converts a list representation to a list of lists where each list represents a row of the square." + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "id": "28ce8741-bcb9-490d-86b0-c0a08da29743", + "metadata": {}, + "outputs": [], + "source": [ + "def convert_to_square(elements: list[int]) -> list[list[int]]:\n", + " n = math.isqrt(len(elements))\n", + " if n**2 != len(elements):\n", + " raise ValueError('list does not represent a square')\n", + " # The groupby key will be the row index, computed as the list index, integer\n", + " # divided by n, this yields a sequence of tuples, the first element of which is\n", + " # the key, the second a group. The group contains tuples, the first element the\n", + " # list index, the second the list tuple. We select the second by mapping the\n", + " # itemgetter(1), and than convert the group to a list.\n", + " return list(map(lambda t: list(map(operator.itemgetter(1), t[1])),\n", + " itertools.groupby(enumerate(elements), lambda t: t[0]//n)))" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "492cba61-0564-4ba2-ac31-d9d36ea1d8df", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[[1, 2, 3], [4, 5, 6], [7, 8, 9]]" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "convert_to_square(list(range(1, 10)))" + ] + }, + { + "cell_type": "markdown", + "id": "b5bfcc7d-6732-45e7-99a0-085b337621ec", + "metadata": {}, + "source": [ + "For convenience, we also write a function to display a square." + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "d56a4ac6-0a6b-403a-8569-7471f9caa9d8", + "metadata": {}, + "outputs": [], + "source": [ + "def print_square(elements: list[int]) -> None:\n", + " n = math.isqrt(len(elements))\n", + " if n**2 != len(elements):\n", + " raise ValueError('list does not represent a square')\n", + " for i in range(n):\n", + " print(''.join(f'{value:5d}' for value in elements[i*n:i*n+n]))" + ] + }, + { + "cell_type": "markdown", + "id": "18eed79e-968f-4a37-9d88-1bb5b005adee", + "metadata": {}, + "source": [ + "We also define a function to check whether a square is magic." + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "9c6de9a2-20dd-42b9-b9e6-dc29581d7bdf", + "metadata": {}, + "outputs": [], + "source": [ + "def is_magic_squuare(elements: list[int]) -> bool:\n", + " n = math.isqrt(len(elements))\n", + " if n**2 != len(elements):\n", + " raise ValueError('list does not represent a square')\n", + " diag_sum = sum(elements[i + i*n] for i in range(n))\n", + " for i in range(n):\n", + " row_sum = sum(elements[j + i*n] for j in range(n))\n", + " if row_sum != diag_sum:\n", + " return False\n", + " for j in range(n):\n", + " col_sum = sum(elements[j + i*n] for i in range(n))\n", + " if col_sum != diag_sum:\n", + " return False\n", + " return sum(elements[i*n + (n - i - 1)] for i in range(n)) == diag_sum" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "4875036e-a4c4-4972-aed0-5752efccf6e6", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 34, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "is_magic_squuare(list(range(1, 10)))" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "9149cec0-7678-4001-afcb-b11190368737", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 35, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "is_magic_squuare([2, 9, 4, 7, 5, 3, 6, 1, 8])" + ] + }, + { + "cell_type": "markdown", + "id": "db7b904f-6314-45fb-9d63-26cf38f4cec2", + "metadata": {}, + "source": [ + "We implement a function that creates the $n \\times n$ fundamental square." + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "724a793b-741e-4210-8278-4a2f5fb2f8a4", + "metadata": {}, + "outputs": [], + "source": [ + "def create_fundamental_square(n: int) -> list[int]:\n", + " return list(range(1, n**2 + 1))" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "9c7ab336-a0ed-48cb-93d2-415bcc0914e1", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[1, 2, 3, 4, 5, 6, 7, 8, 9]" + ] + }, + "execution_count": 36, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "create_fundamental_square(3)" + ] + }, + { + "cell_type": "markdown", + "id": "ee30166a-8b56-4e12-bd97-547070b4fb99", + "metadata": {}, + "source": [ + "# Finding magic squares" + ] + }, + { + "cell_type": "markdown", + "id": "5b352cbd-0415-4cf8-90cd-699af3f168a8", + "metadata": {}, + "source": [ + "## Enumeration" + ] + }, + { + "cell_type": "markdown", + "id": "6ccfad87-2ceb-4726-844d-6aa9cbaa3688", + "metadata": {}, + "source": [ + "The naive algorithm to find a magic squarre is straightforward by simply enumerating all permutations of the fundamental square, and returning the first one that is magic. We build in a maximum number of iterations to avoid unrealistic runtimes of the function." + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "id": "ed9f69e1-3988-4d85-94c6-355a0c6de1e4", + "metadata": {}, + "outputs": [], + "source": [ + "def find_magic_square(n: int, max_iterations: int=10_000_000) -> list[list[int]] | None:\n", + " for iteration, elements in enumerate(itertools.permutations(create_fundamental_square(n))):\n", + " if is_magic_squuare(elements):\n", + " return convert_to_square(elements)\n", + " if iteration >= max_iterations:\n", + " break\n", + " return None" + ] + }, + { + "cell_type": "code", + "execution_count": 59, + "id": "61864a59-91b8-4d57-9c29-e922a3e170db", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[[2, 7, 6], [9, 5, 1], [4, 3, 8]]" + ] + }, + "execution_count": 59, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "find_magic_square(3)" + ] + }, + { + "cell_type": "markdown", + "id": "c9750572-a9bd-4389-89da-384306095d15", + "metadata": {}, + "source": [ + "This worked well, and fast for $n = 3$. However, lets time this for $n = 4$." + ] + }, + { + "cell_type": "code", + "execution_count": 60, + "id": "14fc9428-5a75-49a5-80de-822615f69852", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "12.033219652000298" + ] + }, + "execution_count": 60, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "start = time.perf_counter()\n", + "find_magic_square(4)\n", + "time.perf_counter() - start" + ] + }, + { + "cell_type": "markdown", + "id": "12f39e32-2f43-48ca-9d34-40d72a5729cc", + "metadata": {}, + "source": [ + "It takes more than 10 seconds to iterate over $10^7$ squares. We know there are $10^{13}$ possible $4 \\times 4$ squares, so to enumerate them all would take $10^7 \\approx 116$ days. Of course, we might get lucky and hit upon a magic square \"early\"." + ] + }, + { + "cell_type": "markdown", + "id": "71ae749b-91b1-439a-a8b0-f97b9aa67a46", + "metadata": {}, + "source": [ + "For $n = 3$, we can enumerate all magic squares>" + ] + }, + { + "cell_type": "code", + "execution_count": 66, + "id": "b07909cd-7af9-4750-a996-55d954b482db", + "metadata": {}, + "outputs": [], + "source": [ + "def create_all_magic_square(n: int, is_verbose: bool=False) -> list[list[int]]:\n", + " nr_squares = 0\n", + " for elements in itertools.permutations(create_fundamental_square(n)):\n", + " if is_magic_squuare(elements):\n", + " yield convert_to_square(elements)\n", + " nr_squares += 1\n", + " if is_verbose and nr_squares % 10_000_000 == 0:\n", + " print(f'{nr_squares} tested')" + ] + }, + { + "cell_type": "code", + "execution_count": 67, + "id": "114e04e2-761a-4ee8-9b8d-8190cf57c98a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[[2, 7, 6], [9, 5, 1], [4, 3, 8]]\n", + "[[2, 9, 4], [7, 5, 3], [6, 1, 8]]\n", + "[[4, 3, 8], [9, 5, 1], [2, 7, 6]]\n", + "[[4, 9, 2], [3, 5, 7], [8, 1, 6]]\n", + "[[6, 1, 8], [7, 5, 3], [2, 9, 4]]\n", + "[[6, 7, 2], [1, 5, 9], [8, 3, 4]]\n", + "[[8, 1, 6], [3, 5, 7], [4, 9, 2]]\n", + "[[8, 3, 4], [1, 5, 9], [6, 7, 2]]\n", + "CPU times: user 478 ms, sys: 0 ns, total: 478 ms\n", + "Wall time: 476 ms\n" + ] + } + ], + "source": [ + "%%time\n", + "for square in create_all_magic_square(3):\n", + " print(square)" + ] + }, + { + "cell_type": "markdown", + "id": "a3d5e7d0-3380-43d2-aefe-150962e32749", + "metadata": {}, + "source": [ + "It takes only half a second to enumerate all magic $3 \\times 3$ squares, but there are only 362,880 candiates." + ] + }, + { + "cell_type": "markdown", + "id": "8ca3e8eb-fdfe-4c8f-90d8-8c2804dc0a42", + "metadata": {}, + "source": [ + "## Simon de la Loubère's method" + ] + }, + { + "cell_type": "markdown", + "id": "ecfbca4f-b945-46b6-8d30-a43832bf496e", + "metadata": {}, + "source": [ + "An algorithm attributed to Simon de la Loubère works for odd values of $n$ only. It works as follows:\n", + "* start in the middle of the first row;\n", + "* fill the corresponding broken diagonal by starting with the values 1 to $n$, going up\n", + "* at the position of the last value entered, move one row down, and repeat with the next $n$ values\n", + "* repeat until all $n^2$ values are entered into the square.\n", + "\n", + "For $n = 3$, this would look like for the first broken diagonal:\n", + "$$\n", + "\\begin{array}{ccc}\n", + " \\cdot & 1 & \\cdot \\\\\n", + " 3 & \\cdot & \\cdot \\\\\n", + " \\cdot & \\cdot & 2 \\\\\n", + "\\end{array}\n", + "$$\n", + "For the second (not) broken diagonal:\n", + "$$\n", + "\\begin{array}{ccc}\n", + " \\cdot & 1 & 6 \\\\\n", + " 3 & 5 & \\cdot \\\\\n", + " 4 & \\cdot & 2 \\\\\n", + "\\end{array}\n", + "$$\n", + "And finally for the last broken diagonal:\n", + "$$\n", + "\\begin{array}{ccc}\n", + " 8 & 1 & 6 \\\\\n", + " 3 & 5 & 7 \\\\\n", + " 4 & 9 & 2 \\\\\n", + "\\end{array}\n", + "$$\n" + ] + }, + { + "cell_type": "markdown", + "id": "991b687e-5906-46b9-9c5d-6fa54dbbc187", + "metadata": {}, + "source": [ + "The implementation is straightforward." + ] + }, + { + "cell_type": "code", + "execution_count": 72, + "id": "e540f141-57b7-4e12-815b-424d12ee0d29", + "metadata": {}, + "outputs": [], + "source": [ + "def create_magic_square(n: int) -> list[int]:\n", + " if n < 1:\n", + " raise ValueError(f'{n} is not a valid dimension')\n", + " if n % 2 == 0:\n", + " raise NotImplementedError('algorithm not implement for even sizes')\n", + " elements = [-1]*n**2\n", + " i, j = 0, n//2\n", + " for value in create_fundamental_square(n):\n", + " elements[i*n + j] = value\n", + " if value % n == 0:\n", + " i = (i + 1) % n\n", + " else:\n", + " i = (i - 1 + n) % n\n", + " j = (j + 1) % n\n", + " return elements" + ] + }, + { + "cell_type": "markdown", + "id": "a40b858f-c12b-4268-80cb-ba1fb5db70d3", + "metadata": {}, + "source": [ + "For $n = 3$, and also check whether the square is indeed magic:" + ] + }, + { + "cell_type": "code", + "execution_count": 74, + "id": "e3643f8d-baf9-4f99-8df5-bf1c46111db3", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " 8 1 6\n", + " 3 5 7\n", + " 4 9 2\n" + ] + } + ], + "source": [ + "print_square(create_magic_square(3))" + ] + }, + { + "cell_type": "code", + "execution_count": 76, + "id": "b21f2c3e-b5dd-44ee-995d-34a67c54ce87", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 76, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "is_magic_squuare(create_magic_square(3))" + ] + }, + { + "cell_type": "markdown", + "id": "44c8021d-158f-474c-8503-e517a65c8f9d", + "metadata": {}, + "source": [ + "For $n = 5$, and check:" + ] + }, + { + "cell_type": "code", + "execution_count": 75, + "id": "d7c246de-54cb-48a7-9fe0-806ace14f5ac", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " 17 24 1 8 15\n", + " 23 5 7 14 16\n", + " 4 6 13 20 22\n", + " 10 12 19 21 3\n", + " 11 18 25 2 9\n" + ] + } + ], + "source": [ + "print_square(create_magic_square(5))" + ] + }, + { + "cell_type": "code", + "execution_count": 78, + "id": "f3e07c26-dc8b-441d-9fd9-3359a0d8d303", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 78, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "is_magic_squuare(create_magic_square(5))" + ] + }, + { + "cell_type": "markdown", + "id": "0cc68dcc-68cc-4845-b31d-1d499e8617f8", + "metadata": {}, + "source": [ + "This method is of course very fast compared to enumerating squares, its complexity is quadratic in $n$." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/source_code/neighborhoods.ipynb b/source_code/neighborhoods.ipynb new file mode 100644 index 0000000..f8404a8 --- /dev/null +++ b/source_code/neighborhoods.ipynb @@ -0,0 +1,406 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Problem setting" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Game rules" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This \"game\" is played on a grid of squares. The starting configuation is a single occupied square. The probability for new squares depends on the number of neighboring occupied squares. The probability for occupying a new square is 0 for squares without any occupied neighbors, for each neighbor an empty square has, the probability goes up by 1.\n", + "\n", + "That means that when there is a single square, each of its four neighbors has a 0.25 probability of being occupied.\n", + "\n", + "![probabilities](game.png)\n", + "\n", + "For the configuration above, the squares labeled with 1 to 7 are candiates to be a new occupied square. Square 7 has two neighbors, so the probability to be picked is twice that of the squares 1 to 6 since those have a single neighbor each." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Measure" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The measure of interest is the \"hairiness\", which is defined as the number of free edges devide by the number occupied squares. In the configuration above, the three occupied squares have 8 edges, so the hairiness $H$ would be $8/3 \\approx 2.66667$." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Representation" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We need to keep track of the squares that are currently occupied, and the squares are candidates to occupy in the next move. The currently occupied squares can be represented by a set of tuples ($x$ and $y$ cooridnates). The candidate squares can be stored in a list, where each square occurs as many times as it has an occupied neightbor. The advantege of this representation of candidate squares as a list is that it is tirival to sample with the correct probabilities.\n", + "\n", + "For instance, in the example above, the occupied squares would be ${(0, 0), (1, 0), (1, 1)}$, while the list of candidate squares would be $[(1, 2), (2, 1), (2, 0), (1, -1), (0, -1), (-1, 0), (0, 1), (0, 1)]$. Note that square 6 at $(0, 1)$ occurs twice, insce it has two neighbors. Picking a random element from this list sample with the correct probability distribution.\n", + "\n", + "When a square is picked to be occupied, all of the copies in the list should be reovmed, and the square is added to the set of occupied squares. All the neighbors of the new square should be added to the list of candidate squares, unless they are occupied already.\n", + "\n", + "If a square has coordinates $(x, y)$, its neighbors have coordinates $(x - 1, y)$, $(x + 1, y)$, $(x, y - 1)$ and $(x, y + 1)$." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Board" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "from operator import itemgetter\n", + "import random\n", + "%matplotlib inline" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "First, we can write a function that returns the neighbors of a given square." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "def get_neighbors(square):\n", + " return [(square[0] + direction[0], square[1] + direction[1])\n", + " for direction in [(-1, 0), (1, 0), (0, -1), (0, 1)]]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We represent the board as a class. The constructor creates a board with a snigle square occupied at $(0, 0)$. It has a public move method that occupies a new square according to the rules. There is a getter to retrieve the occupied squares as a frozen set." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "class Board:\n", + " \n", + " def __init__(self):\n", + " self._occupied = set()\n", + " self._candidates = dict()\n", + " self._add_occupied((0, 0))\n", + " \n", + " def _add_occupied(self, new_square):\n", + " self._occupied.add(new_square)\n", + " self._add_neighbors(new_square)\n", + " \n", + " def _add_neighbors(self, square):\n", + " for neighbor in get_neighbors(square):\n", + " if neighbor not in self._occupied:\n", + " if neighbor not in self._candidates:\n", + " self._candidates[neighbor] = 0\n", + " self._candidates[neighbor] += 1\n", + " \n", + " def move(self):\n", + " new_square = random.choices(list(self._candidates.keys()),\n", + " weights=list(self._candidates.values()))[0]\n", + " self._candidates.pop(new_square)\n", + " self._add_occupied(new_square)\n", + " \n", + " @property\n", + " def occupied(self):\n", + " return frozenset(self._occupied)\n", + " \n", + " @property\n", + " def candidates(self):\n", + " return self._candidates.copy()\n", + " \n", + " @property\n", + " def nr_squares(self):\n", + " return len(self._occupied)\n", + "\n", + " @property\n", + " def nr_edges(self):\n", + " return sum(self._candidates.values())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Visualization" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can define a function that converts a set of square coordinates to a 2D array for visualization." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "def board_to_array(squares):\n", + " min_x = min(map(itemgetter(0), squares))\n", + " max_x = max(map(itemgetter(0), squares))\n", + " x_dim = max_x - min_x + 1\n", + " min_y = min(map(itemgetter(1), squares))\n", + " max_y = max(map(itemgetter(1), squares))\n", + " y_dim = max_y - min_y + 1\n", + " board = np.zeros((x_dim, y_dim), dtype=np.uint8)\n", + " for x, y in squares:\n", + " board[x - min_x, y - min_y] = 1\n", + " return board" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The function to visualize a board is then trivial to write using `imshow`." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "def show_board(board):\n", + " _ = plt.imshow(board_to_array(board.occupied))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Results" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can now visualize a board after 5,000 steps." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "board = Board()\n", + "for _ in range(50_000):\n", + " board.move()\n", + "show_board(board)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Free edge distirubtion" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "def plot_free_edge_distribution(board):\n", + " edges = board.candidates.values()\n", + " _ = plt.hist(edges, bins=[0.5, 1.5, 2.5, 3.5, 4.5], density=True)\n", + " _ = plt.xticks([1, 2, 3, 4])" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "plot_free_edge_distribution(board)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Hairiness" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The hairiness function can now be defined with a set of squares as an argument." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "def hairiness(board):\n", + " return board.nr_edges/board.nr_squares" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can now write a function that plays the game for a number of time steps, and returns the hairiness as a function of time." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "def simulate(t_max):\n", + " board = Board()\n", + " data = np.empty((t_max + 1, 2))\n", + " data[0, :] = 0, hairiness(board)\n", + " for t in range(1, t_max + 1):\n", + " board.move()\n", + " data[t, :] = t, hairiness(board)\n", + " return data" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Simulating over 5,000 time steps and plotting $H(t)$." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 15.3 s, sys: 41.8 ms, total: 15.3 s\n", + "Wall time: 15.7 s\n" + ] + } + ], + "source": [ + "%%time\n", + "data = simulate(200_000)" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "_ = plt.semilogy(data[:, 0], data[:, 1])\n", + "_ = plt.xlabel('$t$')\n", + "_ = plt.ylabel('$H(t)$')" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.4" + }, + "toc-autonumbering": true + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/source_code/one_liners.ipynb b/source_code/one_liners.ipynb new file mode 100644 index 0000000..b1c8540 --- /dev/null +++ b/source_code/one_liners.ipynb @@ -0,0 +1,465 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "bec5b0b9-8dcf-4dc9-9138-7f3fe484977a", + "metadata": {}, + "source": [ + "# Note of caution" + ] + }, + { + "cell_type": "markdown", + "id": "ed88a84c-75c7-4aa8-a1c6-19b8f7eb1378", + "metadata": {}, + "source": [ + "Although it is fun to write one-liners, it is very often bad practice. One-liners are hard to read, and hence very hard to maintain. If you feel the need to format your one-liner over multiple lines, you are definitely on the wrong track for production code.\n", + "\n", + "The code samples below are mostly for recreational purposes (\"because we can\") and not intended in any way as examples of good coding practice." + ] + }, + { + "cell_type": "markdown", + "id": "fb69dff7-51e2-450f-9f93-ba3ae9dbbec6", + "metadata": {}, + "source": [ + "# Product" + ] + }, + { + "cell_type": "markdown", + "id": "d606684c-76e8-4e41-b8e6-f10fd8faead1", + "metadata": {}, + "source": [ + "Write a one-line function that computes the product of the elements of a list. Don't use `math.prod` or `functools.reduce`." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "533742e1-01b4-4a40-b338-d1dd0b884e62", + "metadata": {}, + "outputs": [], + "source": [ + "def prod(data):\n", + " return 1 if not data else data[0]*prod(data[1:])" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "2a7bf872-023d-41dc-9c15-7fdebc12b35a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "84" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "prod([3, 4, 7])" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "624a1677-08ea-495f-af33-332e6b4391ab", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "3" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "prod([3])" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "fbdf3a63-aab3-4e4a-b7b1-1edde34b1756", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "prod([])" + ] + }, + { + "cell_type": "markdown", + "id": "35d7dc68-d99e-4c8b-ba1e-10aeebdab597", + "metadata": {}, + "source": [ + "# Happy numbers" + ] + }, + { + "cell_type": "markdown", + "id": "b256ef84-8d8b-4803-b446-89cc71af8ff6", + "metadata": {}, + "source": [ + "A number is a happy number when you end up with 1 by repeatedly applying the following rule: sum thesquares of the digists. If you end up with a one-digit number distinct from 1, the number is not a happy one." + ] + }, + { + "cell_type": "markdown", + "id": "f0f5d31d-2db0-491b-a702-e46645735bf2", + "metadata": {}, + "source": [ + "Reference implementation that is less tedious than a one-liner." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "e09d900e-4d36-4d08-87d4-a47d67f5e395", + "metadata": {}, + "outputs": [], + "source": [ + "def is_happy_ref(n, done=[]):\n", + " if n != 1:\n", + " n_new = sum(map(lambda d: int(d)**2, str(n)))\n", + " if n_new in done:\n", + " return False\n", + " else:\n", + " return is_happy_ref(n_new, done + [n_new])\n", + " else:\n", + " return True" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "69b21621-06b9-4d06-84da-9c00565703ca", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1\n", + "7\n", + "10\n", + "13\n", + "19\n", + "23\n", + "28\n", + "31\n", + "32\n" + ] + } + ], + "source": [ + "for n in range(34):\n", + " if is_happy_ref(n):\n", + " print(n)" + ] + }, + { + "cell_type": "markdown", + "id": "043c064f-4253-488d-8fe8-2278a2e4f5f9", + "metadata": {}, + "source": [ + "Actual implementation as one-liner." + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "b30cbd46-8c6c-47be-be55-d8f2ea2d239d", + "metadata": {}, + "outputs": [], + "source": [ + "def is_happy(n, done=[]):\n", + " return n == 1 or \\\n", + " (is_happy(new_n, done + [new_n]) \\\n", + " if (new_n := sum(map(lambda d: int(d)**2, str(n)))) not in done else\n", + " False)" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "2165c18e-3f0d-4523-848d-0d8f087683d4", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1\n", + "7\n", + "10\n", + "13\n", + "19\n", + "23\n", + "28\n", + "31\n", + "32\n" + ] + } + ], + "source": [ + "for n in range(34):\n", + " if is_happy(n):\n", + " print(n)" + ] + }, + { + "cell_type": "markdown", + "id": "de9696ef-e32b-419d-ad1d-6147fd7b6e8a", + "metadata": { + "tags": [] + }, + "source": [ + "# Greatest common divisor" + ] + }, + { + "cell_type": "markdown", + "id": "a5a885f3-273a-4b65-9ebb-81c24ce33911", + "metadata": {}, + "source": [ + "Compute the greatest common divisor of two positive integers. Don't use `math.gcd`." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "0d507836-2bd5-44fe-9b83-839310e7457a", + "metadata": {}, + "outputs": [], + "source": [ + "def gcd(a, b):\n", + " return a if a == b else gcd(a - b, b) if a > b else gcd(a, b - a)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "13c64d5e-d381-421c-86c6-8ff39c2d66f4", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "3" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "gcd(12, 15)" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "df6d1ea7-6b0d-450e-a36b-d50d461d92e8", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "gcd(7, 13)" + ] + }, + { + "cell_type": "markdown", + "id": "4dd6f93a-af9b-42e3-830a-4e3f7e11891f", + "metadata": { + "tags": [] + }, + "source": [ + "# Camel-case split" + ] + }, + { + "cell_type": "markdown", + "id": "27dad074-7df4-4936-8d79-61295600f8c0", + "metadata": {}, + "source": [ + "Split a camel-case string, e.g., `'thisIsCamelCase'` should be split into `['this', 'Is', 'Camel', 'Case']`." + ] + }, + { + "cell_type": "code", + "execution_count": 56, + "id": "f1851b01-d9c0-425c-b8a1-0e1c37d96fdf", + "metadata": {}, + "outputs": [], + "source": [ + "def split_camel_case(text):\n", + " return [\n", + " text[lower:upper] for \\\n", + " lower, upper in zip(*map(lambda f: f(list(map(lambda t: t[0],\n", + " filter(lambda t: t[1],\n", + " ((i, c.isupper()) \\\n", + " for i, c in enumerate(text)))))),\n", + " (lambda l: [0] + l, lambda l: l + [len(text)])))\n", + " ]" + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "id": "772eb01e-17be-4fc2-81f8-9f5ef83483a3", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['this', 'Is', 'A', 'Sentence', 'With', 'Words']" + ] + }, + "execution_count": 57, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "split_camel_case('thisIsASentenceWithWords')" + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "id": "2b33641e-d215-41fa-bb64-57ae165f5feb", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['this']" + ] + }, + "execution_count": 58, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "split_camel_case('this')" + ] + }, + { + "cell_type": "code", + "execution_count": 59, + "id": "eb99b535-8b69-426e-83b8-94d251346761", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['', 'This', 'That']" + ] + }, + "execution_count": 59, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "split_camel_case('ThisThat')" + ] + }, + { + "cell_type": "code", + "execution_count": 60, + "id": "9e3c5a30-6e61-475c-a30a-824a047958bc", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['']" + ] + }, + "execution_count": 60, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "split_camel_case('')" + ] + }, + { + "cell_type": "code", + "execution_count": 64, + "id": "8d88dbc5-252d-412d-9834-11584c966874", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['', 'This', 'Is', 'A', 'String']" + ] + }, + "execution_count": 64, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "split_camel_case('ThisIsAString')" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.4" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/source_code/structural_pattern_matching.ipynb b/source_code/structural_pattern_matching.ipynb new file mode 100644 index 0000000..c57dffb --- /dev/null +++ b/source_code/structural_pattern_matching.ipynb @@ -0,0 +1,686 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "c35a72d7-06f8-4dc0-97d8-b48a670cdefa", + "metadata": {}, + "source": [ + "# Requirements" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "21660f53-c34b-49ce-ae30-89bc1ed276b1", + "metadata": {}, + "outputs": [], + "source": [ + "from dataclasses import dataclass\n", + "import math\n", + "import operator\n", + "import re\n", + "import sys\n", + "from types import SimpleNamespace" + ] + }, + { + "cell_type": "markdown", + "id": "f79158c1-2c0e-4aed-b8a5-667bf804a6b4", + "metadata": {}, + "source": [ + "# Problem setting" + ] + }, + { + "cell_type": "markdown", + "id": "e3224001-1f4c-4b78-a84a-d87a6a531bb5", + "metadata": {}, + "source": [ + "Structural pattern matching is a very powerful feature that has the potential to make intricate code much easier to write, read and maintain." + ] + }, + { + "cell_type": "markdown", + "id": "5dff0786-2990-41f8-8676-ae8b9c78c6c2", + "metadata": {}, + "source": [ + "# Simple example: RPN calculator" + ] + }, + { + "cell_type": "markdown", + "id": "ebb80a7e-30ae-4863-8146-9b63178802c7", + "metadata": {}, + "source": [ + "Consider the example of an evaluator for arithmetic expressions in reverse Polish notation. It evalues expressions such as `('+', ('*', 3, 7), 3)` (equivalent to `3*7 + 5`), and raises an exception if an expression can't be matched." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "9190635a-9e51-4931-ba26-76bca14d8fe8", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "class RpnEvaluator:\n", + " \n", + " def __init__(self):\n", + " self._ops = {\n", + " '+': operator.add,\n", + " '-': operator.sub,\n", + " '*': operator.mul,\n", + " '/': operator.truediv,\n", + " '^': operator.pow,\n", + " }\n", + " self._funcs = {\n", + " 'sqrt': math.sqrt,\n", + " }\n", + " \n", + " def is_operator(self, op):\n", + " return op in self._ops\n", + " \n", + " def is_function(self, func):\n", + " return func in self._funcs\n", + " \n", + " def eval(self, expr):\n", + " match expr:\n", + " case (func, arg) if self.is_function(func):\n", + " return self._funcs[func](self.eval(arg))\n", + " case (op, lhs, rhs) if self.is_operator(op):\n", + " return self._ops[op](self.eval(lhs), self.eval(rhs))\n", + " case value if isinstance(value, int) or isinstance(value, float):\n", + " return float(value)\n", + " case _:\n", + " raise ValueError(f'{expr} can not be matched')" + ] + }, + { + "cell_type": "markdown", + "id": "6a6b1506-3dd6-42a4-a0fb-04f890e9255f", + "metadata": {}, + "source": [ + "For convenience, we use the functions defined in the `operator` module, it would of course be possible to implement this using lambda functions as well." + ] + }, + { + "cell_type": "markdown", + "id": "9ac36414-082b-4bfe-93d9-402557cb9e90", + "metadata": {}, + "source": [ + "Now you can instantiate an evaluator." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "5b54a1f1-36a0-4a29-a72f-ad074f865ad0", + "metadata": {}, + "outputs": [], + "source": [ + "evaluator = RpnEvaluator()" + ] + }, + { + "cell_type": "markdown", + "id": "6b2fe41a-6d56-40b2-a4fe-baa8a640f732", + "metadata": {}, + "source": [ + "## Examples" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "2d5db383-8c7f-4a2c-abf8-da88fcfff012", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "24.0" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "evaluator.eval(('+', ('*', 3, 7), 3))" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "ef443bba-35bc-419c-bc54-d3f42f60753f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "5.000000000000001" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "evaluator.eval(('*', ('sqrt', 5), ('sqrt', 5)))" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "fff43b46-c127-4f28-81f3-da391b435775", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "8.0" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "evaluator.eval(('^', 2, 3))" + ] + }, + { + "cell_type": "markdown", + "id": "2003789b-d281-4a66-ad88-be0af5edbe1f", + "metadata": {}, + "source": [ + "## Error handling" + ] + }, + { + "cell_type": "markdown", + "id": "ce51545e-37a3-42ee-b0a2-4a44adf5a325", + "metadata": {}, + "source": [ + "When you try to evaluate an expression that contains errors, the matching fails." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "92abbd18-76fe-4b89-890e-b4da90cf5d27", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a can not be matched\n" + ] + } + ], + "source": [ + "try:\n", + " evaluator.eval(('+', 'a', 5))\n", + "except ValueError as e:\n", + " print(e)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "1dbf56eb-5253-46b3-9781-3604857a0ca4", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "('**', 3, 5) can not be matched\n" + ] + } + ], + "source": [ + "try:\n", + " evaluator.eval(('**', 3, 5))\n", + "except ValueError as e:\n", + " print(e)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "c5eb5e3a-344e-42fd-83b3-63cd7fdc034b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "('+', 3) can not be matched\n" + ] + } + ], + "source": [ + "try:\n", + " evaluator.eval(('+', 3))\n", + "except ValueError as e:\n", + " print(e)" + ] + }, + { + "cell_type": "markdown", + "id": "733aa703-2081-4a0d-9817-5ef7ac1e480e", + "metadata": {}, + "source": [ + "# Matching regular expressions" + ] + }, + { + "cell_type": "markdown", + "id": "a9997bba-bec0-4924-9898-56158041e995", + "metadata": {}, + "source": [ + "There is no direct support for matching regular expressions using Python's `match` statement. However, `match` relies on `__eq__`, so defining a class that implements that will do the trick." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "48b27187-3a69-4f18-8a18-565c68b9e06c", + "metadata": {}, + "outputs": [], + "source": [ + "@dataclass\n", + "class ReEqual(str):\n", + " \n", + " text: str\n", + " match: re.Match = None\n", + " \n", + " def __eq__(self, pattern):\n", + " self.match = re.search(pattern, self.text)\n", + " return self.match is not None" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "2b686267-c014-4687-b257-51b55fc28755", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ReEqual('E5') == '^[A-H][1-8]$'" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "18ba1d8d-71dc-4408-95f7-0dc76e0e3894", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ReEqual('E9') == '^[A-H][1-8]$'" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "a828f7b9-86e0-45ac-b8b8-177ad7a647a2", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "A3: mid field\n", + "B1: white home row\n", + "C8: black home row\n", + "D7: black home row\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "A9: invalid position\n", + "B11: invalid position\n", + "AA5: invalid position\n" + ] + } + ], + "source": [ + "for pos in ('A3', 'B1', 'C8', 'D7', 'A9', 'B11', 'AA5'):\n", + " match ReEqual(pos):\n", + " case r'^[A-H][1-2]$':\n", + " print(f'{pos}: white home row')\n", + " case r'^[A-H][7-8]$':\n", + " print(f'{pos}: black home row')\n", + " case r'^[A-H][1-8]$':\n", + " print(f'{pos}: mid field')\n", + " case _:\n", + " print(f'{pos}: invalid position', file=sys.stderr)" + ] + }, + { + "cell_type": "markdown", + "id": "030ed56d-4586-4466-88a2-8a4b469a84e1", + "metadata": {}, + "source": [ + "## Alternative" + ] + }, + { + "cell_type": "markdown", + "id": "6fd3f7d1-a96f-4e1a-9040-a29bfa5c599e", + "metadata": {}, + "source": [ + "An alternative approach would be to create a class that represents the pattern to match, rather then the string to match." + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "c8ffc0d5-0d9c-493a-b6da-5f7439a1ac18", + "metadata": {}, + "outputs": [], + "source": [ + "@dataclass\n", + "class ReMatcher:\n", + " pattern: str\n", + " \n", + " def __eq__(self, string):\n", + " return re.match(self.pattern, string) is not None" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "08207361-0851-40e6-8df7-cfd3155a5d90", + "metadata": {}, + "outputs": [], + "source": [ + "categories = SimpleNamespace(\n", + " white_home=ReMatcher('^[A-H][1-2]$'),\n", + " black_home=ReMatcher('^[A-H][7-8]$'),\n", + " midfield=ReMatcher('^[A-H][3-6]$'),\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "8e2abaf3-981c-4153-b55a-e46c831c7d13", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "A3: mid field\n", + "B1: white home row\n", + "C8: black home row\n", + "D7: black home row\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "A9: invalid position\n", + "B11: invalid position\n", + "AA5: invalid position\n" + ] + } + ], + "source": [ + "for pos in ('A3', 'B1', 'C8', 'D7', 'A9', 'B11', 'AA5'):\n", + " match pos:\n", + " case categories.white_home:\n", + " print(f'{pos}: white home row')\n", + " case categories.black_home:\n", + " print(f'{pos}: black home row')\n", + " case categories.midfield:\n", + " print(f'{pos}: mid field')\n", + " case _:\n", + " print(f'{pos}: invalid position', file=sys.stderr)" + ] + }, + { + "cell_type": "markdown", + "id": "9f0855bc-2c4a-4141-a824-403caa0147af", + "metadata": {}, + "source": [ + "The `SimpleNamespace` is required because just using `fruit` or `vegetable` variables would capture the match." + ] + }, + { + "cell_type": "markdown", + "id": "dd4ca4e7-a1a3-4348-b436-83401f6785a9", + "metadata": {}, + "source": [ + "# Set membership" + ] + }, + { + "cell_type": "markdown", + "id": "0163c55a-205e-422a-a902-0d0d1c518082", + "metadata": {}, + "source": [ + "Just like for regular expressions, set membership is also not supported by Python's `match` statement, but a similar trick can be applied." + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "8523937a-72c4-48f2-a2f8-470ec85882ee", + "metadata": {}, + "outputs": [], + "source": [ + "class SetEqual(set):\n", + " \n", + " def __eq__(self, text):\n", + " return text in self" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "6aab1082-8171-48d5-a8f4-e2525d8e50b6", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 30, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "SetEqual({'apple', 'pear', 'cherry'}) == 'apple'" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "7820ea33-cc1b-4784-a70d-382cce67a1ad", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 31, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "SetEqual({'apple', 'pear', 'cherry'}) == 'nut'" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "abac8b58-3597-4940-98ff-23aaa28f1764", + "metadata": {}, + "outputs": [], + "source": [ + "food = SimpleNamespace(\n", + " fruit=SetEqual({'apple', 'pear'}),\n", + " vegatable=SetEqual({'sprout', 'salad'}),\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "f4d37a64-45c7-4122-9062-f65752e64188", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "apple is fruit\n", + "sprout is vegetable\n", + "salad is vegetable\n", + "pear is fruit\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "nut is unknown\n", + "parsnip is unknown\n" + ] + } + ], + "source": [ + "for stuff in ['apple', 'nut', 'sprout', 'salad', 'pear', 'parsnip']:\n", + " match stuff:\n", + " case food.fruit:\n", + " print(f'{stuff} is fruit')\n", + " case food.vegatable:\n", + " print(f'{stuff} is vegetable')\n", + " case _:\n", + " print(f'{stuff} is unknown', file=sys.stderr)" + ] + }, + { + "cell_type": "markdown", + "id": "f32d0a8e-e92f-44f0-85d2-02b2d610e9d1", + "metadata": {}, + "source": [ + "The `SimpleNamespace` is required because just using `fruit` or `vegetable` variables would capture the match." + ] + }, + { + "cell_type": "markdown", + "id": "c263726d-89d8-4261-929e-a5e6f41253d3", + "metadata": {}, + "source": [ + "# Matching types" + ] + }, + { + "cell_type": "markdown", + "id": "c0a0c684-8666-465b-a436-cbc5dedba77f", + "metadata": {}, + "source": [ + "Again, matching against built-in types is not entirely straightforward, but it is supported by the `match` statements." + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "id": "10df7ce0-7743-4536-9181-7210d194d6ff", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\"abc\" is a string\n", + "3 is an integer\n", + "\"\" is a string\n", + "5.2 is a float\n", + "-8 is an integer\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "no idea what {} is\n" + ] + } + ], + "source": [ + "for item in ('abc', 3, '', 5.2, -8, {}):\n", + " match item:\n", + " case str():\n", + " print(f'\"{item}\" is a string')\n", + " case int():\n", + " print(f'{item} is an integer')\n", + " case float():\n", + " print(f'{item} is a float')\n", + " case _:\n", + " print(f'no idea what {item} is', file=sys.stderr)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.4" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/source_code/survival.ipynb b/source_code/survival.ipynb new file mode 100644 index 0000000..df3bb41 --- /dev/null +++ b/source_code/survival.ipynb @@ -0,0 +1,323 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Requirements" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "%matplotlib inline\n", + "import random" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Problem setting" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If each individual in a population has a probability of producing offspring, will that population survive or not?\n", + "\n", + "The distribution of offspring for each individual is specified as a list, e.g., `[0, 0, 1, 1, 2]`. So an individual will produce no offspring with probability $2/5$, have one child with probability $2/5$ and two children with probability $1/5$." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Implementation" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The size of the next generation is determined by the current population size, and the distribution." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "def compute_next_generation(population_size, children_distr):\n", + " new_population_size = 0\n", + " for _ in range(population_size):\n", + " new_population_size += random.choice(children_distr)\n", + " return new_population_size" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For example, when we start with a single individual." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " 1: 2\n", + " 2: 2\n", + " 3: 4\n", + " 4: 7\n", + " 5: 8\n", + " 6: 12\n", + " 7: 12\n", + " 8: 10\n", + " 9: 11\n", + " 10: 9\n", + " 11: 12\n", + " 12: 13\n", + " 13: 16\n", + " 14: 17\n", + " 15: 31\n", + " 16: 28\n", + " 17: 31\n", + " 18: 49\n", + " 19: 48\n", + " 20: 64\n" + ] + } + ], + "source": [ + "children_distr = [0, 0, 1, 1, 2, 2, 3]\n", + "population_size = 1\n", + "for generation in range(1, 21):\n", + " population_size = compute_next_generation(population_size, children_distr)\n", + " print(f'{generation:3d}: {population_size}')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Clearly, the population grows.\n", + "\n", + "To experiment a bit further, we write a function that returns two lists, one with the number of the generation, the other with the corresponding size of that generation. This way, we can create a plot." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "def compute_generations(children_distr, nr_generations):\n", + " generations = list(range( nr_generations + 1))\n", + " population_sizes = [1]\n", + " for _ in generations[1:]:\n", + " population_sizes.append(compute_next_generation(population_sizes[-1], children_distr))\n", + " return generations, population_sizes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's try it with the following distribution." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "die_out_distr = [0, 0, 1, 2]" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[]" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "generations, population_sizes = compute_generations(die_out_distr, 20)\n", + "plt.plot(generations, population_sizes)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "They seem to die out.\n", + "\n", + "Combining this into a function for convenience." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "def plot_generations(distr, nr_generations):\n", + " generations, population_sizes = compute_generations(distr, nr_generations)\n", + " plt.plot(generations, population_sizes)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "plot_generations(die_out_distr, 10)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's try a different distribution that is a bit more favorable to survival." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAX0AAAD4CAYAAAAAczaOAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8vihELAAAACXBIWXMAAAsTAAALEwEAmpwYAAAjRklEQVR4nO3deZhcdZ3v8fe39yydvdMk6ewLIYkgoQlhGWUn4hLGGZwgA1GZyajgMuoojDM6j8/lXnSUUbyAZgQNCsS4Er1GEwIDJiSEJCQhezp7Z+vu7FtvVd/7R50OZVOddHd11enq+ryep5469Tu/U+ebU8Wnf5xz6hxzd0REJDvkhF2AiIikj0JfRCSLKPRFRLKIQl9EJIso9EVEskhe2AVcyIABA3zEiBFhlyEiklFWrVpV4+4lzds7feiPGDGClStXhl2GiEhGMbPdidq1e0dEJIso9EVEsohCX0Qkiyj0RUSyiEJfRCSLKPRFRLKIQl9EJIso9EVEOplVu4/y+MsVnKxt6PD3VuiLiHQyv1+3n++/tI383I6PaIW+iEgns7SihitH9KMoP7fD31uhLyLSiVSdqGXroVNcN2ZASt5foS8i0oks3V4DwLUKfRGRrm9pxWH6dM9nwqBeKXn/C4a+mT1tZlVmtj7BvC+ZmZvZgLi2h8yswsy2mNltce1XmNlbwbzHzMw67p8hIpL53J2lFTVcO3oAOTmpicjWjPR/Akxr3mhmQ4FbgD1xbROAGcDEYJknzKzpSMSTwCxgbPB4x3uKiGSzHTWnOXC8NmW7dqAVoe/urwJHEsz6L+DLgMe1TQfmunudu+8EKoApZjYI6OXuy9zdgWeAO5ItXkSkK1laEdufn6qDuNDOffpm9iFgn7uvbTZrCLA37nVl0DYkmG7e3tL7zzKzlWa2srq6uj0liohknCXbahjarxvD+ndP2TraHPpm1h34KvC1RLMTtPl52hNy99nuXu7u5SUl77jbl4hIl9MYibJsx+GUjvKhfbdLHA2MBNYGx2LLgNVmNoXYCH5oXN8yYH/QXpagXUREgLf2HedkbSPXjE5t6Ld5pO/ub7n7QHcf4e4jiAX6ZHc/CMwHZphZoZmNJHbAdoW7HwBOmtnU4Kyde4EXOu6fISKS2V7bfhiAa0b3T+l6WnPK5vPAMuBiM6s0s/ta6uvuG4B5wEbgj8D97h4JZn8K+BGxg7vbgQVJ1i4i0mUs2VbDhEG96N+zMKXrueDuHXe/6wLzRzR7/TDwcIJ+K4FJbaxPRKTLO1sfYdXuo3zs2hEpX5d+kSsiErI3dh2hPhJN6fn5TRT6IiIhW1pRQ0FuDleO6JvydSn0RURCtqSihsuH9aF7QXtOqGwbhb6ISIiOnK5n44ETKT8/v4lCX0QkRMu2H8Ydrh2r0BcR6fKWVNRQXJjHpUN6p2V9Cn0RkRAtrahh6uj+5KXgfriJKPRFREKy5/AZ9hw5k7b9+aDQFxEJzdu3RkztpRfiKfRFREKypKKG0l6FjC7pmbZ1KvRFREIQjTrLth/m2jEDSOfdYxX6IiIh2HTwBEdO16d1fz4o9EVEQtF0a8R0XG8nnkJfRCQESyoOM3ZgT0p7FaV1vQp9EZE0q2uMsGLn4bSP8kGhLyKSdqt3H6O2IT2XUm5OoS8ikmavba8hN8e4alS/tK9boS8ikmZLKmq4rKw3vYry077u1twj92kzqzKz9XFt/2lmm81snZn9xsz6xM17yMwqzGyLmd0W136Fmb0VzHvM0nliqohIJ3GitoG1e4+l/VTNJq0Z6f8EmNasbREwyd0vBbYCDwGY2QRgBjAxWOYJM8sNlnkSmAWMDR7N31NEpMtbvv0wUU//qZpNLhj67v4qcKRZ20J3bwxeLgfKgunpwFx3r3P3nUAFMMXMBgG93H2ZuzvwDHBHB/0bREQyxtKKGrrl53L5sNTfGjGRjtin/wlgQTA9BNgbN68yaBsSTDdvT8jMZpnZSjNbWV1d3QElioh0Dksqapgysh8FeeEcUk1qrWb2VaAReLapKUE3P097Qu4+293L3b28pKQkmRJFRDqNg8dr2V59OrT9+QDtvguvmc0EPgDcFOyygdgIfmhctzJgf9BelqBdRCRrhHXphXjtGumb2TTgK8CH3P1M3Kz5wAwzKzSzkcQO2K5w9wPASTObGpy1cy/wQpK1i4hklKUVNfTvUcD4i4pDq+GCI30zex64HhhgZpXA14mdrVMILArOvFzu7p909w1mNg/YSGy3z/3uHgne6lPEzgTqRuwYwAJERLKEu7OkooZrxgwgJye8M9YvGPrufleC5qfO0/9h4OEE7SuBSW2qTkSki6ioOkXVyTquS+NdshLRL3JFRNJgSbA//5rR4e3PB4W+iEhaLK2oYXj/7gzt1z3UOhT6IiIp1hiJsnzHkVDP2mmi0BcRSbG1lcc5VdcY6vn5TRT6IiIptrSiBjO4elS4B3FBoS8iknJLKmqYNLg3fXsUhF2KQl9EJJVO1zXy5p6jXBPyqZpNFPoiIim0YtcRGiLeKfbng0JfRCSllm6roSAvhytHpP/WiIko9EVEUmjp9sOUD+9LUX7uhTungUJfRCRFak7VsenAiU5xfn4Thb6ISIq8tv0wQKfZnw8KfRGRlFm6rYZeRXlMGtI77FLOUeiLiKRA06WUrx7dn9wQL6XcnEJfRCQFdh8+w75jZzvVrh1Q6IuIpMSSTnBrxEQU+iIiKfA/W6oY1LuIkQN6hF3KX1Doi4h0sH3HzvLS5iruuHwIwS1lO40Lhr6ZPW1mVWa2Pq6tn5ktMrNtwXPfuHkPmVmFmW0xs9vi2q8ws7eCeY9ZZ9sSIiId5LnXdwNw91XDQq7knVoz0v8JMK1Z24PAYncfCywOXmNmE4AZwMRgmSfMrOlnaE8Cs4CxwaP5e4qIZLy6xghzV+zlxvGllPUN9y5ZiVww9N39VeBIs+bpwJxgeg5wR1z7XHevc/edQAUwxcwGAb3cfZm7O/BM3DIiIl3GgrcOcvh0PfdePTzsUhJq7z79Unc/ABA8DwzahwB74/pVBm1Dgunm7QmZ2SwzW2lmK6urq9tZoohI+j2zbBcjB/TodKdqNunoA7mJ9tP7edoTcvfZ7l7u7uUlJSUdVpyISCqt33ec1XuO8fdTh5PTiX6QFa+9oX8o2GVD8FwVtFcCQ+P6lQH7g/ayBO0iIl3GT5ftplt+Ln97RdmFO4ekvaE/H5gZTM8EXohrn2FmhWY2ktgB2xXBLqCTZjY1OGvn3rhlREQy3vEzDbywdh93XD6Y3t3ywy6nRXkX6mBmzwPXAwPMrBL4OvAIMM/M7gP2AHcCuPsGM5sHbAQagfvdPRK81aeInQnUDVgQPEREuoRfrNpLbUOUe6aOCLuU87pg6Lv7XS3MuqmF/g8DDydoXwlMalN1IiIZIBp1frp8N+XD+zJhcK+wyzkv/SJXRCRJr26rZvfhM9zTSU/TjKfQFxFJ0k+X7WZAz0LeN2lQ2KVckEJfRCQJe4+c4aUtVdw1ZSgFeZ0/Ujt/hSIindjPlu8mx4yPdsLr7CSi0BcRaafahgg/X7mXWy4pZVDvbmGX0yoKfRGRdvrd2v0cO9PQaa+zk4hCX0SknX66fDdjBvbk6tH9wy6l1RT6IiLtsGbvMdZVHufeq4d3uhulnI9CX0SkHZ5ZtoseBbn89eUtXjC4U1Loi4i00ZHT9fx+3QE+PLmM4qLOe52dRBT6IiJt9PM39lLfGM2IX+A2p9AXEWmDSNT52fLdTB3Vj3GlxWGX02YKfRGRNnh5cxX7jp3l3qtHhF1Kuyj0RUTa4JnluyntVcgtE0rDLqVdFPoiIq20o/oUr26t5qNThpOfm5nxmZlVi4iE4GfL95CXY9w1ZeiFO3dSCn0RkVY4U9/IL1btZdqkixjYqyjsctpNoS8i0govrNnPydrGjD2A2ySp0DezfzazDWa23syeN7MiM+tnZovMbFvw3Deu/0NmVmFmW8zstuTLFxFJPXfnmWW7GX9RMVeO6HvhBTqxdoe+mQ0BPguUu/skIBeYATwILHb3scDi4DVmNiGYPxGYBjxhZrnJlS8iknqrdh9l04ET3JNh19lJJNndO3lANzPLA7oD+4HpwJxg/hzgjmB6OjDX3evcfSdQAUxJcv0iIin3zLLdFBfmcce7M+s6O4m0O/TdfR/wbWAPcAA47u4LgVJ3PxD0OQAMDBYZAuyNe4vKoO0dzGyWma00s5XV1dXtLVFEJGnVJ+tYsP4Af3NFGT0K88IuJ2nJ7N7pS2z0PhIYDPQws78/3yIJ2jxRR3ef7e7l7l5eUlLS3hJFRJI2d8UeGiKekdfZSSSZ3Ts3AzvdvdrdG4BfA9cAh8xsEEDwXBX0rwTiT24tI7Y7SESkU2qMRHluxR6uGzOA0SU9wy6nQyQT+nuAqWbW3WJHNm4CNgHzgZlBn5nAC8H0fGCGmRWa2UhgLLAiifWLiKTUi5sOceB4bZcZ5UPsQGy7uPvrZvZLYDXQCLwJzAZ6AvPM7D5ifxjuDPpvMLN5wMag//3uHkmyfhGRlJnz2m4G9y7ipvEDL9w5QyR1VMLdvw58vVlzHbFRf6L+DwMPJ7NOEZF0eGPXEZbtOMxD7xtPXoZeZyeRrvMvERHpIO7Of/5pCwN6Fmb8L3CbU+iLiDTz5201rNh5hAduGE23gq71G1KFvohIHHfn2wu3MKRPN+66aljY5XQ4hb6ISJxFGw+xrvI4n71pDIV5XWuUDwp9EZFzolHn0UVbGTmgB38zuSzsclJCoS8iEvjduv1sPniSz988tkudsROva/6rRETaqDES5bsvbmP8RcV88NLBYZeTMgp9ERHgV6sr2Vlzmi/cMo6cnMy+fPL5KPRFJOvVNUZ4bHEFl5X15pYJpWGXk1IKfRHJenNX7GXfsbN88daLM/4mKRei0BeRrHa2PsL/fbmCKSP78VdjB4RdTsop9EUkq81Ztovqk3X8y21df5QPCn0RyWInahv4wSvbee+4Eq4c0S/sctJCoS8iWeupP+/k2JkGvnTrxWGXkjYKfRHJSkdP1/PUkp1Mm3gR7yrrHXY5aaPQF5Gs9INXt3O6vpEv3Dou7FLSSqEvIlmn6kQtc17bxfTLBjOutDjsctJKoS8iWefxlytoiDifvzm7RvmQZOibWR8z+6WZbTazTWZ2tZn1M7NFZrYteO4b1/8hM6swsy1mdlvy5YuItE3l0TM8t2IPHykvY8SAHmGXk3bJjvS/B/zR3ccDlwGbgAeBxe4+FlgcvMbMJgAzgInANOAJM+t6F6sWkU7tscXbMIzP3Dg27FJC0e7QN7NewHuApwDcvd7djwHTgTlBtznAHcH0dGCuu9e5+06gApjS3vWLiLTVjupT/Gr1Pu6eOozBfbqFXU4okhnpjwKqgR+b2Ztm9iMz6wGUuvsBgOB5YNB/CLA3bvnKoE1EJC2+++I2CnJz+PT1Y8IuJTTJhH4eMBl40t0vB04T7MppQaLfN3vCjmazzGylma2srq5OokQRkZjNB0/wu3X7+fi1IygpLgy7nNAkE/qVQKW7vx68/iWxPwKHzGwQQPBcFdd/aNzyZcD+RG/s7rPdvdzdy0tKSpIoUUQk5jsLt9KzMI9/es/osEsJVbtD390PAnvNrOn3yzcBG4H5wMygbSbwQjA9H5hhZoVmNhIYC6xo7/pFRFprzd5jLNp4iH/8q1H07p4fdjmhykty+c8Az5pZAbAD+DixPyTzzOw+YA9wJ4C7bzCzecT+MDQC97t7JMn1i4hc0HcWbqFfjwI+cd3IsEsJXVKh7+5rgPIEs25qof/DwMPJrFNEpC1e33GYP2+r4au3X0LPwmTHuZlPv8gVkS7L3fn2wi2U9irknquHh11Op6DQF5Eu69nX9/DGrqN85saxFOXrt6Cg0BeRLmpd5TG+8buN3HBxCR+dMizscjoNhb6IdDnHzzTw6WdXU1JcyKMfeTc5OV3/NoitpaMaItKluDtf/MVaDp2oZd4/XU3fHgVhl9SpaKQvIl3K7Fd38OKmQ/zr7Zdw+bC+F14gyyj0RaTLWLHzCN/60xbe/65BfOyaEWGX0ykp9EWkS6g+WccDz61mWL/uPPI378JM+/ETUeiLSMaLRJ3PzX2T42cbeOLuyRQXZfelFs5HB3JFJON978WtvLb9MN/620u5ZFCvsMvp1DTSF5GM9srWar7/cgV3XlHGR8qHXniBLKfQF5GMtf/YWT4/900uLi3mG9MnhV1ORlDoi0hGqm+Mcv9zq2mIOE/cPZluBbrMQmton76IZKRHFmzmzT3HePyjkxlV0jPscjKGRvoiknEWvHWAp5fu5GPXjOD9lw4Ku5yMotAXkYyyq+Y0X/7lOt49tA//evslYZeTcRT6IpIxahsifOrZ1eTmGo/fPZmCPEVYW2mfvohkjK+/sIFNB07w449fyZA+3cIuJyPpz6SIZIRfrqrk5yv38sANY7jh4oFhl5Oxkg59M8s1szfN7PfB635mtsjMtgXPfeP6PmRmFWa2xcxuS3bdIpIdNh88wb/99i2uHtWff75lXNjlZLSOGOl/DtgU9/pBYLG7jwUWB68xswnADGAiMA14wsx0Yq2InNepukY+/exqiovy+d5d7yZXN0RJSlKhb2ZlwPuBH8U1TwfmBNNzgDvi2ue6e5277wQqgCnJrF9EurbGSJT7n13N7sNn+P5dlzOwuCjskjJesiP97wJfBqJxbaXufgAgeG7a+TYE2BvXrzJoewczm2VmK81sZXV1dZIlikgmcnf+/YUNvLK1mv91xySmjuofdkldQrtD38w+AFS5+6rWLpKgzRN1dPfZ7l7u7uUlJSXtLVFEMtgPXtnB8yv28OnrR3OXbmzeYZI5ZfNa4ENmdjtQBPQys58Bh8xskLsfMLNBQFXQvxKIvwReGbA/ifWLSBf1u7X7+eYfN/PBywbzpVsvDrucLqXdI313f8jdy9x9BLEDtC+5+98D84GZQbeZwAvB9HxghpkVmtlIYCywot2Vi0iX9MauI3zxF2uZMqIf377zUnJ04LZDpeLHWY8A88zsPmAPcCeAu28ws3nARqARuN/dIylYv4hkqB3Vp/jHZ1ZS1qcbP7znCgrzdIJfRzP3hLvVO43y8nJfuXJl2GWISIodPlXHh598jVO1jfzm09cyrH/3sEvKaGa2yt3Lm7frMgwiErrahgj/8MxKDh6v5flZUxX4KaTQF5FQRaPOP/98DWv2HuPJuyczeVjfCy8k7aZr74hIqP7Pgk0sWH+Qr95+CdMm6dr4qabQF5HQPLNsF//9553MvHo49103MuxysoJCX0RC8eLGQ/zH/A3cfMlAvvbBiZjp1Mx0UOiLSNq9VXmczzz/JpOG9Oaxuy7XRdTSSKEvImlVefQMn5jzBv16FPCjmeV0L9D5JOmkrS0iaXP8bAMf//Eb1DZEeO4frtJVM0Ogkb6IpEV9Y5RP/nQVuw6f5of3XMHY0uKwS8pKGumLSMq5Ow/+eh3Ldhzm0Y9cxjWjB4RdUtZS6ItISp2tj/CN32/g16v38YVbxvHhyWVhl5TVFPoikjLr9x3nc3PfZHv1aT753tF85sYxYZeU9RT6ItLhIlHnh69u59GFWxnQs5Bn/+Eqrh2jXTqdgUJfRDpU5dEzfGHeWlbsPML73zWIh/96En26F4RdlgQU+iLSYV5Ys49/++163OE7d17GhycP0S9tOxmFvogk7fjZBv79t+uZv3Y/Vwzvy3f/7t0M7afLI3dGCn0RScqy7Yf54rw1HDpZxxdvGcenrh9NXq5+AtRZKfRFpF3qG6N8Z9EWZr+6gxH9e/CrT13Du4f2CbssuYB2/zk2s6Fm9rKZbTKzDWb2uaC9n5ktMrNtwXPfuGUeMrMKM9tiZrd1xD9ARNJv26GT3PH4Un74yg5mXDmM//fZ6xT4GSKZkX4j8EV3X21mxcAqM1sEfAxY7O6PmNmDwIPAV8xsAjADmAgMBl40s3G6ObpI5nB3nlm2m//9h030KMzjv+8t55YJpWGXJW3Q7tB39wPAgWD6pJltAoYA04Hrg25zgP8BvhK0z3X3OmCnmVUAU4Bl7a1BRNKn6mQt//KLdbyytZrrLy7hW397qS6YloE6ZJ++mY0ALgdeB0qDPwi4+wEzGxh0GwIsj1usMmhL9H6zgFkAw4YN64gSRaSdTtc18tSSncx+dQcNkSjfmD6Re6YO16mYGSrp0DeznsCvgM+7+4nzfBESzfBEHd19NjAboLy8PGEfEUmt+sYoz6/Yw/df2kbNqXpum1jKl6eNZ3RJz7BLkyQkFfpmlk8s8J91918HzYfMbFAwyh8EVAXtlcDQuMXLgP3JrF9EOl406vxu3X6+s3Are46c4aqR/Zh973gmD+t74YWl02t36FtsSP8UsMndH42bNR+YCTwSPL8Q1/6cmT1K7EDuWGBFe9cvIh3L3Xl1Ww3f+uNmNuw/wfiLivnxx6/k+nEl2pXThSQz0r8WuAd4y8zWBG3/Sizs55nZfcAe4E4Ad99gZvOAjcTO/LlfZ+6IdA5r9h7jmws2s2zHYcr6duO//u4ypl82hBzdu7bLSebsnSUk3k8PcFMLyzwMPNzedYpIx9pefYpv/2kLC9YfpH+PAr7+wQl89KphFOblhl2apIh+kSuShQ4er+V7i7cyb2UlRXk5fO6msfzje0bRs1CR0NXpExbJIsfPNPDkK9v58dKdRN25Z+pwHrhxDAN6FoZdmqSJQl+kizt2pp6XNlexaOMhXtlazdmGCNMvG8wXbrmYYf11Jcxso9AX6YL2HTvLog0HWbjxEK/vPEIk6gwsLuSOy4dw91XDmDi4d9glSkgU+iJdgLuz5dBJFm44xMKNB1m/7wQAYwb25J/eM4pbJ17EpUN662wcUeiLZKpI1Fm1+ygLgxH9niNnAJg8rA8Pvm88t0wo1a9n5R0U+iIZ5Gx9hKUVNSzceJDFm6o4fLqegtwcrhnTn0++dzQ3XzKQgb10ETRpmUJfpJOrPHqGlzdXsXhzFcu2H6auMUpxYR43jB/IrRNLee+4EoqL8sMuUzKEQl+kk2mMRFm95xgvba7ipc2H2HroFADD+3fno1cN48bxA7lqZH8K8nRLQmk7hb5IJ3D0dD2vbK3mpc1VvLK1muNnG8jLMa4c0Y9/e/9Qbhg/kFEDeugaOJI0hb5ICNydzQdPBqP5Kt7cc5SoQ/8eBdx8SSk3XTKQ68YOoJd220gHU+iLpFhDJMruw6fZcvAUWw+dZOuhk6zde4z9x2sBmDSkFw/cMIYbLynVaZWScgp9kQ4SiTp7jpyJBfvBk2ytOsXWgyfZUXOKhkjsXkBmMLxfdy4b2ofP3lTCDeMHUqqzbSSNFPoibVTbEGH/sbPsrDnN1kNvj94rqk5R1xg916+sbzfGlRZzw/iBjCvtybjSYsYM7ElRvq5gKeFR6Is00xTqlUebHmf+4rnqZN1f9B/Uu4ixpcVcPao/4y4qZlxpMWMH9qSHrlgpnZC+ldJlNUSi1DZEqG2IPdc1RjhbH6W2MXKuvfpk3QVDPS/HGNynG2V9u3H9xSWU9e1OWd9uDO/fnTEDi+ndTQdbJXMo9KVTaIhEOV3XyMnaRk7VNcam6xo5VRubPhXMOzcd9DlV28jZhrdDPBbsEWobo0Si3qp1txTqTc+lvYrI1cFV6SIU+vIOkaifC97mYXsqCOW/eNQ2cqY+QmM0FrQNkSiNEacx6jRG46Yj0eD5L+fVR6LUx+0LP58eBbn0LMqjR2EexYWx597d8inKz6UwP4du+bkU5edSlJ9DUd7b04VN7Xk5QVusfUDPQoW6ZJW0h76ZTQO+B+QCP3L3R9JdQyaLRJ2zDRHO1MWCNvZoPPd8Nm53xrnnxgh1DdFgBBxpNi9KXUMk9p71EU4H79saRfk59CzMo2dhHt0K8sjPNfJyjLycHArycuiWY+Tn5sTacmPt56ab2nNyyM81egTv07Po7TBvmm4K+R4FeQpnkSSlNfTNLBd4HLgFqATeMLP57r4xnXW0VjQaG4XWNcZGok0j0obguaX2+nMj3befGyJOJH60G4yIYyNjJxL0bYg6Z4MQP10fOTfdFOq1Da0bEccryMt5xwi3W34uhfm5sVFycSFF+bl0L8g9F7w940I40XSPwjzyc3UZAJFMk+6R/hSgwt13AJjZXGA60OGhf99P3mDn4dO4Q9Q99ojGTXss1M9NuwevY9ONUW/1PuG2ys81cnOM/JwccnPfHu3mBiPjbkEA9yrKY1CvIroX5NK9MJfuBXmx6YJcuhXk0SOYbmrvVpB7bvdG03NhXo5+7CMi56Q79IcAe+NeVwJXNe9kZrOAWQDDhg1r14pGDOhBUUEuuWbkGOSYYWbk5rw9nWOQm2PB61h7bk5sOteMgrzYboqC3BwKg+n83LfbmuYXNmvPDx55ufHBHnvE3l8hLCLhSHfoJ0q7dwyn3X02MBugvLy8XcPtf//AhPYsJiLSpaV7p2wlMDTudRmwP801iIhkrXSH/hvAWDMbaWYFwAxgfpprEBHJWmndvePujWb2APAnYqdsPu3uG9JZg4hINkv7efru/gfgD+ler4iIpH/3joiIhEihLyKSRRT6IiJZRKEvIpJFzD01lxroKGZWDexu5+IDgJoOLKejqK62UV1to7rapqvWNdzdS5o3dvrQT4aZrXT38rDraE51tY3qahvV1TbZVpd274iIZBGFvohIFunqoT877AJaoLraRnW1jepqm6yqq0vv0xcRkb/U1Uf6IiISR6EvIpJFukTom9k0M9tiZhVm9mCC+WZmjwXz15nZ5DTUNNTMXjazTWa2wcw+l6DP9WZ23MzWBI+vpbquYL27zOytYJ0rE8wPY3tdHLcd1pjZCTP7fLM+adleZva0mVWZ2fq4tn5mtsjMtgXPfVtY9rzfxRTU9Z9mtjn4nH5jZn1aWPa8n3kK6voPM9sX91nd3sKy6d5eP4+raZeZrWlh2VRur4TZkLbvmLtn9IPYJZq3A6OAAmAtMKFZn9uBBcTu3DUVeD0NdQ0CJgfTxcDWBHVdD/w+hG22Cxhwnvlp314JPtODxH5ckvbtBbwHmAysj2v7FvBgMP0g8M32fBdTUNetQF4w/c1EdbXmM09BXf8BfKkVn3Nat1ez+d8BvhbC9kqYDen6jnWFkf65m627ez3QdLP1eNOBZzxmOdDHzAalsih3P+Duq4Ppk8AmYvcIzgRp317N3ARsd/f2/hI7Ke7+KnCkWfN0YE4wPQe4I8Girfkudmhd7r7Q3RuDl8uJ3Y0urVrYXq2R9u3VxGI3qv4I8HxHra+1zpMNafmOdYXQT3Sz9ebh2po+KWNmI4DLgdcTzL7azNaa2QIzm5imkhxYaGarLHYT+uZC3V7E7qjW0n+MYWwvgFJ3PwCx/2iBgQn6hL3dPkHs/9ASudBnngoPBLudnm5hV0WY2+uvgEPuvq2F+WnZXs2yIS3fsa4Q+q252XqrbsieCmbWE/gV8Hl3P9Fs9mpiuzAuA74P/DYdNQHXuvtk4H3A/Wb2nmbzw9xeBcCHgF8kmB3W9mqtMLfbV4FG4NkWulzoM+9oTwKjgXcDB4jtSmkutO0F3MX5R/kp314XyIYWF0vQ1qZt1hVCvzU3Ww/lhuxmlk/sQ33W3X/dfL67n3D3U8H0H4B8MxuQ6rrcfX/wXAX8htj/MsYL8wb27wNWu/uh5jPC2l6BQ027uILnqgR9wvqezQQ+ANztwY7f5lrxmXcodz/k7hF3jwL/3cL6wtpeecCHgZ+31CfV26uFbEjLd6wrhH5rbrY+H7g3OCtlKnC86X+jUiXYZ/gUsMndH22hz0VBP8xsCrHP43CK6+phZsVN08QOBK5v1i3t2ytOiyOwMLZXnPnAzGB6JvBCgj6t+S52KDObBnwF+JC7n2mhT2s+846uK/4Y0F+3sL60b6/AzcBmd69MNDPV2+s82ZCe71gqjk6n+0HsbJOtxI5qfzVo+yTwyWDagMeD+W8B5Wmo6Tpi/9u1DlgTPG5vVtcDwAZiR+CXA9ekoa5RwfrWBuvuFNsrWG93YiHeO64t7duL2B+dA0ADsZHVfUB/YDGwLXjuF/QdDPzhfN/FFNdVQWwfb9N37AfN62rpM09xXT8NvjvriIXSoM6wvYL2nzR9p+L6pnN7tZQNafmO6TIMIiJZpCvs3hERkVZS6IuIZBGFvohIFlHoi4hkEYW+iEgWUeiLiGQRhb6ISBb5/4QzKUlTWCEEAAAAAElFTkSuQmCC\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "viable_distr = [0, 0, 1, 1, 2, 2, 2, 3]\n", + "plot_generations(viable_distr, 20)" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAXQAAAD4CAYAAAD8Zh1EAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8vihELAAAACXBIWXMAAAsTAAALEwEAmpwYAAATQUlEQVR4nO3db4xcV3nH8e+T9S7NbCC7ixcaHAeHKtC6FaHpEqAtNBUt2GmFS1W1CaihKciKlFTwolIsoVJUXlFKVVEClkutQIUIqgjgItNQVbS8QEHZoPwzIbCEPzEOyQbH/EkojpOnL2ZmM57MeGft2T9zzvcjrXbm3jM7D3cmP47PzH1uZCaSpNF31noXIEkaDgNdkgphoEtSIQx0SSqEgS5Jhdi0Xk+8efPm3LZt23o9vSSNpNtvv/2RzJzttW/dAn3btm3Mz8+v19NL0kiKiO/22+eSiyQVwkCXpEIY6JJUCANdkgphoEtSIZYN9IjYHxEPR8Q9ffZHRHwgIhYi4q6IuGT4ZUqSljPIDP1GYMcp9u8ELmr97AY+fOZlSZJWatlAz8wvAUdPMWQX8LFsuhWYiojzhlVgt/t+8BP+4Zb7ePSx46v1FJI0koaxhr4FeKDj/uHWtmeIiN0RMR8R84uLi6f1ZN9+5DE++MUFvn/sZ6f1eEkq1TACPXps63nVjMzcl5lzmTk3O9vzzNVlzUxOAHDs8SdO6/GSVKphBPphYGvH/fOBI0P4uz1NN8YBOPq4Sy6S1GkYgX4AuKr1bZdXAj/KzAeH8Hd7mm7N0F1Dl6STLducKyI+AVwGbI6Iw8DfAuMAmbkXOAhcDiwAjwNXr1axAFNnN2fojzpDl6STLBvomXnlMvsTuHZoFS1j09hZnHv2uDN0SeoykmeKTjfGOeqHopJ0ktEM9MkJZ+iS1GUkA32mMeEauiR1GclAn2o4Q5ekbiMZ6DOT434PXZK6jGSgT09O8H9PPMXPjj+53qVI0oYxmoHeaJ1c5CxdkpaMdKAfdR1dkpaMZKC3G3Q5Q5ekp41ooLdP//fkIklqG8lAn2rYoEuSuo1moLcadLmGLklPG8lAbzfoOuYauiQtGclABxt0SVK30Q10G3RJ0klGNtBt0CVJJxvZQHeGLkknG91Ab9igS5I6jW6g26BLkk4ysoE+Y4MuSTrJyAb6lA26JOkkIxvoNuiSpJONcKDboEuSOo1soNugS5JONrqBboMuSTrJyAZ6u0GXa+iS1DSygQ7ND0ZdQ5ekppEO9KnGuGvoktQy0oE+05hwDV2SWkY60KcnJ7zIhSS1jHag26BLkpYMFOgRsSMi7ouIhYjY02P/uRHxHxFxZ0Qcioirh1/qM9mgS5KetmygR8QYcAOwE9gOXBkR27uGXQt8LTMvBi4D3h8RE0Ou9RnaDbqcpUvSYDP0S4GFzLw/M48DNwG7usYk8OyICOAc4ChwYqiV9jA96dmiktQ2SKBvAR7ouH+4ta3TB4FfAY4AdwNvz8ynuv9QROyOiPmImF9cXDzNkp82bQtdSVoySKBHj23Zdf/1wB3AC4CXAR+MiOc840GZ+zJzLjPnZmdnV1jqM7UbdPnVRUkaLNAPA1s77p9Pcybe6Wrg5mxaAL4N/PJwSuyvPUM/5tmikjRQoN8GXBQRF7Y+6LwCONA15nvAawEi4vnAS4D7h1loL+faoEuSlmxabkBmnoiI64BbgDFgf2YeiohrWvv3Au8BboyIu2ku0VyfmY+sYt2ADbokqdOygQ6QmQeBg13b9nbcPgK8brilDcYGXZLUNNJnioINuiSpbeQD3QZdktQ08oE+PTnhGrokUUCgzxjokgQUEOhTjXEbdEkSBQS6DbokqWnkA90GXZLUNPqBboMuSQIKCHQbdElS08gH+tIM3UCXVLmRD/Rzzx4nAk//l1S9kQ/0TWNn8ZxfsEGXJI18oEPz5CLX0CXVrohAn26Me5ELSdUrJNCdoUtSGYFuPxdJKiPQXUOXpEICfaoxzs9P2KBLUt2KCHQbdElSIYFugy5JKiTQZyZt0CVJRQT6dMMGXZJUSKC75CJJRQS6DbokqZBAt0GXJBUS6ODJRZJUTKBPN5yhS6pbMYE+MznBo4+5hi6pXsUE+lTDBl2S6lZMoLuGLql2AwV6ROyIiPsiYiEi9vQZc1lE3BERhyLif4db5vKmGxM26JJUtU3LDYiIMeAG4PeBw8BtEXEgM7/WMWYK+BCwIzO/FxHPW6V6+1o6W/Tx42yZOHutn16S1t0gM/RLgYXMvD8zjwM3Abu6xrwJuDkzvweQmQ8Pt8zl2aBLUu0GCfQtwAMd9w+3tnV6MTAdEf8TEbdHxFW9/lBE7I6I+YiYX1xcPL2K+2g36HIdXVKtBgn06LEtu+5vAn4D+APg9cDfRMSLn/GgzH2ZOZeZc7Ozsysu9lTaSy5+00VSrZZdQ6c5I9/acf984EiPMY9k5mPAYxHxJeBi4BtDqXIANuiSVLtBZui3ARdFxIURMQFcARzoGvNZ4NURsSkiGsArgHuHW+qptRt0HbVBl6RKLTtDz8wTEXEdcAswBuzPzEMRcU1r/97MvDci/hO4C3gK+Ehm3rOahXfbNHYW5549zjGXXCRVapAlFzLzIHCwa9vervvvA943vNJWbrrhyUWS6lXMmaJggy5JdSsq0Jun/7uGLqlORQX6VGPCNXRJ1Soq0NsNujK7vyYvSeUrKtCXGnQ9YYMuSfUpKtBnJttni7qOLqk+RQX6lGeLSqpYUYFugy5JNSsq0Jf6ufhNF0kVKizQW2voztAlVaioQLdBl6SaFRXo7QZdztAl1aioQIfmOrpr6JJqVGCg26BLUp2KC3QbdEmqVXGBPm2DLkmVKi/QbdAlqVLlBboNuiRVqrhAbzfo8vR/SbUpLtDbDbqOeXKRpMoUF+g26JJUq+IC3QZdkmpVXKC3Z+ie/i+pNsUFug26JNWquEAfOyts0CWpSsUFOsBMY4KjrqFLqkyRgT7VGPf0f0nVKTLQbdAlqUZFBvp0Y8I1dEnVKTPQJ5sXubBBl6SalBnoNuiSVKGBAj0idkTEfRGxEBF7TjHu5RHxZET8yfBKXDkbdEmq0bKBHhFjwA3ATmA7cGVEbO8z7r3ALcMucqWmbdAlqUKDzNAvBRYy8/7MPA7cBOzqMe6vgE8BDw+xvtMybYMuSRUaJNC3AA903D/c2rYkIrYAbwT2nuoPRcTuiJiPiPnFxcWV1jowG3RJqtEggR49tnV/feSfgOsz85SfQmbmvsycy8y52dnZAUtcOVvoSqrRpgHGHAa2dtw/HzjSNWYOuCkiADYDl0fEicz8zDCKXKl2g65HXUOXVJFBAv024KKIuBD4PnAF8KbOAZl5Yft2RNwIfG69whxs0CWpTssGemaeiIjraH57ZQzYn5mHIuKa1v5TrpuvFxt0SarNIDN0MvMgcLBrW88gz8y/OPOyztz05IQNuiRVpcgzRQGmG+M26JJUlYID3QZdkupSbKDPTDbX0G3QJakWxQb6VGOC4zboklSRYgPdBl2SalNsoC+d/u8Ho5IqUW6gT9rPRVJdyg10G3RJqkyxgW6DLkm1KTbQlxp0GeiSKlFsoC816LLjoqRKFBvoYIMuSXUpOtCnJz39X1I9yg70hksukupReKA7Q5dUj6ID3QZdkmpSdKBPTzYbdD1+3AZdkspXdqA3mg26PFtUUg0KD3QbdEmqR9GBvnT6vzN0SRUoOtCnWjN0LxYtqQZFB7oNuiTVpOhAt0GXpJoUHeg26JJUk6IDHWzQJakexQe6Dbok1aL8QG9M+KGopCpUEOjjHHMNXVIFig90G3RJqkXxgW6DLkm1GCjQI2JHRNwXEQsRsafH/jdHxF2tny9HxMXDL/X02KBLUi2WDfSIGANuAHYC24ErI2J717BvA7+TmS8F3gPsG3ahp8sGXZJqMcgM/VJgITPvz8zjwE3Ars4BmfnlzHy0dfdW4Pzhlnn6bNAlqRaDBPoW4IGO+4db2/p5K/D5XjsiYndEzEfE/OLi4uBVnoHpyfYM3UCXVLZBAj16bOv5lZGI+F2agX59r/2ZuS8z5zJzbnZ2dvAqz8DSkoszdEmF2zTAmMPA1o775wNHugdFxEuBjwA7M/OHwynvzNmgS1ItBpmh3wZcFBEXRsQEcAVwoHNARFwA3Az8eWZ+Y/hlnr6xs4Kps8ddQ5dUvGVn6Jl5IiKuA24BxoD9mXkoIq5p7d8LvAt4LvChiAA4kZlzq1f2ykw3Juy4KKl4gyy5kJkHgYNd2/Z23H4b8LbhljY8NuiSVIPizxQFG3RJqkMlgT7ut1wkFa+KQJ+ZbK6h26BLUsmqCHQbdEmqQRWBPtM6uch1dEklqyLQp1odF73QhaSSVRHoNuiSVIMqAt0GXZJqUEeg26BLUgWqCHQbdEmqQRWBboMuSTWoItCh3c/Fb7lIKlc9gd6YcA1dUtGqCnRPLJJUsmoCfWbSBl2SylZNoLcvcmGDLkmlqifQbdAlqXDVBLoNuiSVrppAbzfoch1dUqmqCfR2gy4vFi2pVNUEug26JJWumkB3DV1S6aoJ9Oe0GnQdcw1dUqGqCXQbdEkqXTWBDjboklS2ugLdfi6SClZdoPs9dEmlqirQbdAlqWRVBXp7Dd0GXZJKVFegNyY4/qQNuiSVqapA9+QiSSUbKNAjYkdE3BcRCxGxp8f+iIgPtPbfFRGXDL/UM7d0+r/r6JIKtGygR8QYcAOwE9gOXBkR27uG7QQuav3sBj485DqHYnqp46LfRZdUnk0DjLkUWMjM+wEi4iZgF/C1jjG7gI9l89PGWyNiKiLOy8wHh17xGWjP0Pd86i7OedYg/9Mlafj+7OVbedurXzT0vztIqm0BHui4fxh4xQBjtgAnBXpE7KY5g+eCCy5Yaa1nbNtzJ7nqVS/kkZ/+fM2fW5LaNp/zrFX5u4MEevTY1v29v0HGkJn7gH0Ac3Nza/7dwbGzgr/b9Wtr/bSStCYG+VD0MLC14/75wJHTGCNJWkWDBPptwEURcWFETABXAAe6xhwArmp92+WVwI822vq5JJVu2SWXzDwREdcBtwBjwP7MPBQR17T27wUOApcDC8DjwNWrV7IkqZeBvuqRmQdphnbntr0dtxO4drilSZJWoqozRSWpZAa6JBXCQJekQhjoklSIWK/e4BGxCHz3NB++GXhkiOUMy0atCzZubda1Mta1MiXW9cLMnO21Y90C/UxExHxmzq13Hd02al2wcWuzrpWxrpWprS6XXCSpEAa6JBViVAN933oX0MdGrQs2bm3WtTLWtTJV1TWSa+iSpGca1Rm6JKmLgS5JhdjQgb4RL04dEVsj4osRcW9EHIqIt/cYc1lE/Cgi7mj9vGu162o973ci4u7Wc8732L8ex+slHcfhjoj4cUS8o2vMmh2viNgfEQ9HxD0d22Yi4r8i4put39N9HnvK9+Mq1PW+iPh667X6dERM9XnsKV/3Vajr3RHx/Y7X6/I+j13r4/XJjpq+ExF39Hnsqhyvftmwpu+vzNyQPzRb9X4LeBEwAdwJbO8acznweZpXTHol8JU1qOs84JLW7WcD3+hR12XA59bhmH0H2HyK/Wt+vHq8pj+geWLEuhwv4DXAJcA9Hdv+HtjTur0HeO/pvB9Xoa7XAZtat9/bq65BXvdVqOvdwF8P8Fqv6fHq2v9+4F1rebz6ZcNavr828gx96eLUmXkcaF+cutPSxakz81ZgKiLOW82iMvPBzPxq6/ZPgHtpXj91FKz58eryWuBbmXm6Zwifscz8EnC0a/Mu4KOt2x8F/qjHQwd5Pw61rsz8QmaeaN29leaVwNZUn+M1iDU/Xm0REcCfAp8Y1vMNWFO/bFiz99dGDvR+F55e6ZhVExHbgF8HvtJj96si4s6I+HxE/OoalZTAFyLi9mhekLvbuh4vmle76vcf2Xocr7bnZ+sKW63fz+sxZr2P3V/S/NdVL8u97qvhutZS0P4+SwjrebxeDTyUmd/ss3/Vj1dXNqzZ+2sjB/rQLk69GiLiHOBTwDsy88ddu79Kc1nhYuCfgc+sRU3Ab2XmJcBO4NqIeE3X/vU8XhPAG4B/77F7vY7XSqznsXsncAL4eJ8hy73uw/Zh4JeAlwEP0lze6LZuxwu4klPPzlf1eC2TDX0f1mPbio/XRg70DXtx6ogYp/mCfTwzb+7en5k/zsyftm4fBMYjYvNq15WZR1q/HwY+TfOfcZ3W82LeO4GvZuZD3TvW63h1eKi99NT6/XCPMev1XnsL8IfAm7O12NptgNd9qDLzocx8MjOfAv6lz/Ot1/HaBPwx8Ml+Y1bzePXJhjV7f23kQN+QF6durc/9K3BvZv5jnzG/2BpHRFxK8zj/cJXrmoyIZ7dv0/xA7Z6uYet5Me++s6b1OF5dDgBvad1+C/DZHmMGeT8OVUTsAK4H3pCZj/cZM8jrPuy6Oj93eWOf51vz49Xye8DXM/Nwr52rebxOkQ1r9/4a9ie9Q/7U+HKanxR/C3hna9s1wDWt2wHc0Np/NzC3BjX9Ns1/Ct0F3NH6ubyrruuAQzQ/qb4V+M01qOtFree7s/XcG+J4tZ63QTOgz+3Yti7Hi+b/qTwIPEFzVvRW4LnAfwPfbP2eaY19AXDwVO/HVa5rgea6avt9tre7rn6v+yrX9W+t989dNEPnvI1wvFrbb2y/rzrGrsnxOkU2rNn7y1P/JakQG3nJRZK0Aga6JBXCQJekQhjoklQIA12SCmGgS1IhDHRJKsT/A565cvQWm/HQAAAAAElFTkSuQmCC\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "viable_distr = [0, 0, 0, 1, 2, 3]\n", + "plot_generations(viable_distr, 20)" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "viable_distr = [0, 0, 0, 4]\n", + "plot_generations(viable_distr, 20)" + ] + } + ], + "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.9" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/source_code/welford_algorithm.ipynb b/source_code/welford_algorithm.ipynb new file mode 100644 index 0000000..45fd22c --- /dev/null +++ b/source_code/welford_algorithm.ipynb @@ -0,0 +1,435 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Computing statistics for online data can result in considerable loss of accuracy. Here we experiment with the Welford algorithm that gives good results for the standard deviation.\n", + "\n", + "See [Wikipedia](https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Welford's_online_algorithm) for a description of the algorithm." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Requirements" + ] + }, + { + "cell_type": "code", + "execution_count": 53, + "metadata": {}, + "outputs": [], + "source": [ + "import decimal as dc\n", + "import math\n", + "import numpy as np\n", + "import random" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Data set" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We create a data set consisting of two parts, the first contains number an order of magnitude larger than the second." + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [], + "source": [ + "data_large = np.arange(1_000_000.0, 1_000_000_000_000.0, 100_000.0)" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "4.9999994999955e+18" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "data_large.sum()" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [], + "source": [ + "data_small = np.arange(0.001, 100.0, 0.01)" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "499959.99999999994" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "data_small.sum()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The data sets are combined in two ways, the small number first, followed by the larger numbers, and vice versa." + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": {}, + "outputs": [], + "source": [ + "small_first_data = np.concatenate([data_small, data_large])" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "4.999999499995999e+18" + ] + }, + "execution_count": 27, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "small_first_data.sum()" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": {}, + "outputs": [], + "source": [ + "large_first_data = np.concatenate([data_large, data_small])" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "4.999999499995992e+18\n", + "4.999999499995991e+18\n", + "4.999999499996007e+18\n", + "4.999999499995999e+18\n", + "4.999999499996e+18\n" + ] + } + ], + "source": [ + "for _ in range(5):\n", + " np.random.shuffle(large_first_data)\n", + " print(large_first_data.sum())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Algorithms" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For convenience, all the algorithms are implemented in classes with a uniform interface." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The first algorithm is the Welford algorithm." + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "metadata": {}, + "outputs": [], + "source": [ + "class WelfordStats:\n", + " \n", + " def __init__(self):\n", + " self._n = 0\n", + " self._avg = 0.0\n", + " self._m2 = 0.0\n", + " \n", + " def add(self, value):\n", + " self._n += 1\n", + " prev_avg = self._avg\n", + " self._avg += (value - self._avg)/self._n\n", + " self._m2 += (value - self._avg)*(value -prev_avg)\n", + " \n", + " @property\n", + " def n(self):\n", + " return self._n\n", + " \n", + " @property\n", + " def mean(self):\n", + " return self._avg\n", + " \n", + " @property\n", + " def stddev(self):\n", + " return math.sqrt((self._m2/(self._n - 1)))\n", + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The second algorithm is the naive one." + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "metadata": {}, + "outputs": [], + "source": [ + "class Stats:\n", + " \n", + " def __init__(self):\n", + " self._n = 0\n", + " self._sum = 0.0\n", + " self._sum2 = 0.0\n", + " \n", + " def add(self, value):\n", + " self._n += 1\n", + " self._sum += value\n", + " self._sum2 += value**2\n", + " \n", + " @property\n", + " def n(self):\n", + " return self._n\n", + " \n", + " @property\n", + " def mean(self):\n", + " return self._sum/self._n\n", + " \n", + " @property\n", + " def stddev(self):\n", + " return math.sqrt((self._sum2 - (self._sum*self._sum)/self._n)/(self._n - 1))\n", + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The third implementation uses Python's `decimal` module to preserve accuracy." + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "metadata": {}, + "outputs": [], + "source": [ + "class DecimalStats:\n", + " \n", + " def __init__(self, precision=50):\n", + " dc.getcontext().prec = precision\n", + " self._n = dc.Decimal(0)\n", + " self._sum = dc.Decimal(0.0)\n", + " self._sum2 = dc.Decimal(0.0)\n", + " \n", + " def add(self, value):\n", + " self._n += dc.Decimal(1)\n", + " self._sum += dc.Decimal(value)\n", + " self._sum2 += dc.Decimal(value**2)\n", + " \n", + " @property\n", + " def n(self):\n", + " return self._n\n", + " \n", + " @property\n", + " def mean(self):\n", + " return self._sum/self._n\n", + " \n", + " @property\n", + " def stddev(self):\n", + " return ((self._sum2 - (self._sum*self._sum)/self._n)/(self._n - 1)).sqrt()\n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": 55, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2.5 1.2909944487358056\n", + "2.5 1.2909944487358056\n", + "2.5 1.2909944487358056283930884665941332036109739017639\n" + ] + } + ], + "source": [ + "stats = Stats()\n", + "welford_stats = WelfordStats()\n", + "decimal_stats = DecimalStats()\n", + "for value in np.arange(1.0, 5.0, 1.0):\n", + " stats.add(value)\n", + " welford_stats.add(value)\n", + " decimal_stats.add(value)\n", + "print(stats.mean, stats.stddev)\n", + "print(welford_stats.mean, welford_stats.stddev)\n", + "print(decimal_stats.mean, decimal_stats.stddev)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Comparison" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We run the algorithm on one of the data sets." + ] + }, + { + "cell_type": "code", + "execution_count": 62, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.06717778612280627061929645420703303721\n", + "0.00010014940405627061929645420703303721\n" + ] + } + ], + "source": [ + "stats = Stats()\n", + "welford_stats = WelfordStats()\n", + "decimal_stats = DecimalStats()\n", + "for value in large_first_data:\n", + " stats.add(value)\n", + " welford_stats.add(value)\n", + " decimal_stats.add(value)\n", + "print((dc.Decimal(stats.stddev) - decimal_stats.stddev))\n", + "print((dc.Decimal(welford_stats.stddev) - decimal_stats.stddev))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "It is indeed clear that the standard deviation computed using the Welford algorithm is indeed more accurate than that by the naive method. Note however that the mean value is more accurate by simply summing the data values." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "It is not a panacea though." + ] + }, + { + "cell_type": "code", + "execution_count": 61, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "5.5380491384252865937575867883120891E-11\n", + "2.02718633071467240479812196008120891E-10\n" + ] + } + ], + "source": [ + "stats = Stats()\n", + "welford_stats = WelfordStats()\n", + "decimal_stats = DecimalStats()\n", + "for _ in range(1_000_000):\n", + " value = random.gauss(mu=0.0, sigma=10_000.0)\n", + " stats.add(value)\n", + " welford_stats.add(value)\n", + " decimal_stats.add(value)\n", + "print((dc.Decimal(stats.stddev) - decimal_stats.stddev))\n", + "print((dc.Decimal(welford_stats.stddev) - decimal_stats.stddev))" + ] + } + ], + "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.9" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/source_code/word_chains.ipynb b/source_code/word_chains.ipynb new file mode 100644 index 0000000..9652217 --- /dev/null +++ b/source_code/word_chains.ipynb @@ -0,0 +1,266 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "67934854-e835-43a0-8ac8-7494bba890bc", + "metadata": {}, + "source": [ + "# Requirements" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "7981c28c-994a-440f-891c-667f860247b6", + "metadata": {}, + "outputs": [], + "source": [ + "import itertools" + ] + }, + { + "cell_type": "markdown", + "id": "126fa4fc-a743-40e1-93b1-7818cb5b9895", + "metadata": {}, + "source": [ + "# Problem setting" + ] + }, + { + "cell_type": "markdown", + "id": "6559b167-1eea-4875-999c-7463d8034ea4", + "metadata": {}, + "source": [ + "We are given a list of words, and we have to find the longest chain of words we can build such that each words starts with the same character as the previous word in the chain ends with. Addiotionally, words should not repeat." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "e47396f9-8994-4fc0-a020-6a9936397b96", + "metadata": {}, + "outputs": [], + "source": [ + "words = {\n", + " 'apple', 'pear', 'orange', 'eternal',\n", + " 'entry', 'late', 'evening', 'ghost',\n", + " 'tale', 'error', 'reason',\n", + "}" + ] + }, + { + "cell_type": "markdown", + "id": "7302bcf6-49db-4edd-97da-8aa1e3fd233b", + "metadata": {}, + "source": [ + "# Implementation" + ] + }, + { + "cell_type": "markdown", + "id": "8ee44fb6-8a93-4ab5-9edc-58670d23bd2b", + "metadata": {}, + "source": [ + "It is convenient to compute an adjecency dictionary, i.e., a dictionary that contains all the given words as keys, and a set of words that can succeed it as valeus." + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "07ab5d6e-bd70-46c6-9d7f-36507e6c04d5", + "metadata": {}, + "outputs": [], + "source": [ + "def create_adj_dict(words):\n", + " adj_dict = {word: set() for word in words}\n", + " for word1, word2 in itertools.product(words, repeat=2):\n", + " if word1 != word2:\n", + " if word1[-1] == word2[0]:\n", + " adj_dict[word1].add(word2)\n", + " return adj_dict" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "45d029ce-4c7f-4bb8-9f70-6d5aef8a3eb4", + "metadata": {}, + "outputs": [], + "source": [ + "adj_dict = create_adj_dict(words)" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "d59dd3ca-8171-42e4-9b71-7e5a8ebce5fe", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'eternal': {'late'},\n", + " 'orange': {'entry', 'error', 'eternal', 'evening'},\n", + " 'ghost': {'tale'},\n", + " 'apple': {'entry', 'error', 'eternal', 'evening'},\n", + " 'pear': {'reason'},\n", + " 'entry': set(),\n", + " 'error': {'reason'},\n", + " 'late': {'entry', 'error', 'eternal', 'evening'},\n", + " 'evening': {'ghost'},\n", + " 'tale': {'entry', 'error', 'eternal', 'evening'},\n", + " 'reason': set()}" + ] + }, + "execution_count": 35, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "adj_dict" + ] + }, + { + "cell_type": "markdown", + "id": "005397d9-43ca-467b-8eae-6f1244b79c4d", + "metadata": {}, + "source": [ + "It is clear that if the chain contains \"entry\" or \"reason\", those words will be the last in the chain." + ] + }, + { + "cell_type": "markdown", + "id": "155d57fd-aff7-47a9-9b24-bb5571bf0c73", + "metadata": {}, + "source": [ + "The following function extends a given chain maximally, i.e., it adds the maximum number of words." + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "e6b00551-1497-4f8d-b2d2-84886e9d478a", + "metadata": {}, + "outputs": [], + "source": [ + "def extend_chain(chain, adj_dict):\n", + " longest_chain = chain\n", + " for successor in adj_dict[chain[-1]]:\n", + " if successor not in chain:\n", + " longer_chain = extend_chain(chain + [successor], adj_dict)\n", + " if len(longer_chain) > len(longest_chain):\n", + " longest_chain = longer_chain\n", + " return longest_chain" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "47154107-3b34-4bb8-89f5-f2250a634237", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['late', 'evening', 'ghost', 'tale', 'error', 'reason']" + ] + }, + "execution_count": 30, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "extend_chain(['late'], adj_dict)" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "098895be-4f38-4fc4-95cf-e5fea4fdac9b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['reason']" + ] + }, + "execution_count": 31, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "extend_chain(['reason'], adj_dict)" + ] + }, + { + "cell_type": "markdown", + "id": "57cb724f-2d35-413d-a2e9-bcd008a7fbd5", + "metadata": {}, + "source": [ + "We now define a function to find the longest chain." + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "a5920733-f130-441d-b1c0-89440cc2f0d9", + "metadata": {}, + "outputs": [], + "source": [ + "def create_longest_chain(adj_dict):\n", + " longest_chain = []\n", + " for word in adj_dict:\n", + " chain = extend_chain([word], adj_dict)\n", + " if len(chain) > len(longest_chain):\n", + " longest_chain = chain\n", + " return longest_chain" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "90c835e1-4ab0-4dcf-8ca9-a51a4bbc4301", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['orange', 'eternal', 'late', 'evening', 'ghost', 'tale', 'error', 'reason']" + ] + }, + "execution_count": 37, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "create_longest_chain(adj_dict)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.4" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}