{ "cells": [ { "attachments": {}, "cell_type": "markdown", "id": "3c99bb5b-3e7b-486e-b888-440b5862b48c", "metadata": {}, "source": [ "
Peter Norvig
2024
\n", "\n", "# The Number Bracelets Game\n", "\n", "[Susan Addington](https://www.youtube.com/@multiplyington/playlists) describes [**the number bracelets game**](http://www.geom.uiuc.edu/~addingto/number_bracelets/number_bracelets.html):\n", "\n", "*Imagine that you have lots of beads, numbered from 0 through 9, as many as you want of each kind. Here are the rules for making a number bracelet:*\n", "\n", "- *Pick a first and a second bead. They can have the same number.*\n", "- *To get the third bead, add the numbers on the first and second beads. If the sum is more than 9, just use the last digit of the sum.*\n", " - *(For example, if the first two beads are 6 and 7, then the sum is 13, and the next bead is 3.)*\n", "- *Keep adding beads in this manner until you get back to the first and second beads, in that order.*\n", "- *Then pop off the last two beads.*" ] }, { "cell_type": "markdown", "id": "3a10c327-1c04-400f-a2a8-d51cedfb89a9", "metadata": {}, "source": [ "# Making Bracelets\n", "\n", "To explore the concept of number bracelets with Python, I'll first introduce some data types: " ] }, { "cell_type": "code", "execution_count": 1, "id": "45a1293f-21cb-44a2-8469-0c2172fc46e2", "metadata": {}, "outputs": [], "source": [ "from typing import List\n", "\n", "Bead = int\n", "Bracelet = List[Bead]\n", "Bracelets = List[Bracelet]" ] }, { "cell_type": "markdown", "id": "6255b9fd-e9e7-4695-b218-e27992d439ca", "metadata": {}, "source": [ "And now define the `number_bracelet` function:" ] }, { "cell_type": "code", "execution_count": 2, "id": "2d5f4a73-f4eb-4afa-8592-2274c9d8aac8", "metadata": {}, "outputs": [], "source": [ "def number_bracelet(A: Bead, B: Bead) -> Bracelet:\n", " \"\"\"Starting with a pair of beads, keep appending a bead equal to the sum \n", " of the last two beads mod 10, until the last two beads match the first two.\"\"\"\n", " bracelet = [A, B]\n", " while True: \n", " bead = (bracelet[-2] + bracelet[-1]) % 10\n", " bracelet.append(bead) \n", " if bracelet[:2] == bracelet[-2:]: \n", " return bracelet[:-2]" ] }, { "cell_type": "markdown", "id": "a1060352-f4c1-4d6e-b852-8273d2938847", "metadata": {}, "source": [ "For example:" ] }, { "cell_type": "code", "execution_count": 3, "id": "85ac6e69-7dc6-4fb2-9359-f38f606d1917", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[2, 6, 8, 4]" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "number_bracelet(2, 6)" ] }, { "cell_type": "markdown", "id": "4ca0ab11-17ed-43a2-8da4-2863a43ede3c", "metadata": {}, "source": [ "![](http://www.geom.uiuc.edu/~addingto/number_bracelets/2,6bracelet.gif)" ] }, { "cell_type": "code", "execution_count": 4, "id": "e4bb755e-d538-4443-8387-5784bb4e4211", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[1, 3, 4, 7, 1, 8, 9, 7, 6, 3, 9, 2]" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "number_bracelet(1, 3)" ] }, { "cell_type": "markdown", "id": "1a7ae42e-fc59-422d-b592-8e7defc04ffd", "metadata": {}, "source": [ "![](http://www.geom.uiuc.edu/~addingto/number_bracelets/1,3bracelet.gif)" ] }, { "cell_type": "markdown", "id": "72b69698-6e08-4dd9-a725-533a658a175a", "metadata": {}, "source": [ "One point of ambiguity in the rules: If the two starting beads are both 0, then any following bead will also be 0. When do we stop adding 0s before we pop off the last two? Is the smallest possible bracelet one bead? Two? Zero? To me, the rules imply that a single bead is indeed a number bracelet, but I'm open to other interpretations." ] }, { "cell_type": "markdown", "id": "85c13666-e925-49fd-b8ca-c3ea7dc100cd", "metadata": {}, "source": [ "# All Possible Bracelets\n", "\n", "There are 100 possible two-digit starting pairs, so there should be 100 possible bracelets. We can make all of them:" ] }, { "cell_type": "code", "execution_count": 5, "id": "f248d81c-2738-405c-a62e-b127a63a0a42", "metadata": {}, "outputs": [], "source": [ "all_bracelets = [number_bracelet(A, B) for A in range(10) for B in range(10)]" ] }, { "cell_type": "markdown", "id": "ae759911-d85e-4d53-9f2c-a637fb4ee7e9", "metadata": {}, "source": [ "I'll define the function `show` to print out bracelets, with the number of beads in the bracelet printed first:" ] }, { "cell_type": "code", "execution_count": 6, "id": "c3e2013d-cf84-4828-aa23-6454f93b630d", "metadata": {}, "outputs": [], "source": [ "def show(bracelets: Bracelets) -> None:\n", " \"\"\"Print each of the bracelets, preceeded by its number of beads.\"\"\"\n", " for bracelet in bracelets:\n", " print(f'{len(bracelet):2} beads: ', *bracelet, sep='')" ] }, { "cell_type": "code", "execution_count": 7, "id": "ae9abce1-6db3-4193-bff3-85616138cb3b", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " 1 beads: 0\n", "60 beads: 011235831459437077415617853819099875279651673033695493257291\n", "20 beads: 02246066280886404482\n", "60 beads: 033695493257291011235831459437077415617853819099875279651673\n", "20 beads: 04482022460662808864\n", " 3 beads: 055\n", "20 beads: 06628088640448202246\n", "60 beads: 077415617853819099875279651673033695493257291011235831459437\n", "20 beads: 08864044820224606628\n", "60 beads: 099875279651673033695493257291011235831459437077415617853819\n", "60 beads: 101123583145943707741561785381909987527965167303369549325729\n", "60 beads: 112358314594370774156178538190998752796516730336954932572910\n", "60 beads: 123583145943707741561785381909987527965167303369549325729101\n", "12 beads: 134718976392\n", "60 beads: 145943707741561785381909987527965167303369549325729101123583\n", "60 beads: 156178538190998752796516730336954932572910112358314594370774\n", "60 beads: 167303369549325729101123583145943707741561785381909987527965\n", "60 beads: 178538190998752796516730336954932572910112358314594370774156\n", "12 beads: 189763921347\n", "60 beads: 190998752796516730336954932572910112358314594370774156178538\n", "20 beads: 20224606628088640448\n", "12 beads: 213471897639\n", "20 beads: 22460662808864044820\n", "60 beads: 235831459437077415617853819099875279651673033695493257291011\n", "20 beads: 24606628088640448202\n", "60 beads: 257291011235831459437077415617853819099875279651673033695493\n", " 4 beads: 2684\n", "60 beads: 279651673033695493257291011235831459437077415617853819099875\n", "20 beads: 28088640448202246066\n", "60 beads: 291011235831459437077415617853819099875279651673033695493257\n", "60 beads: 303369549325729101123583145943707741561785381909987527965167\n", "60 beads: 314594370774156178538190998752796516730336954932572910112358\n", "60 beads: 325729101123583145943707741561785381909987527965167303369549\n", "60 beads: 336954932572910112358314594370774156178538190998752796516730\n", "12 beads: 347189763921\n", "60 beads: 358314594370774156178538190998752796516730336954932572910112\n", "60 beads: 369549325729101123583145943707741561785381909987527965167303\n", "60 beads: 370774156178538190998752796516730336954932572910112358314594\n", "60 beads: 381909987527965167303369549325729101123583145943707741561785\n", "12 beads: 392134718976\n", "20 beads: 40448202246066280886\n", "60 beads: 415617853819099875279651673033695493257291011235831459437077\n", " 4 beads: 4268\n", "60 beads: 437077415617853819099875279651673033695493257291011235831459\n", "20 beads: 44820224606628088640\n", "60 beads: 459437077415617853819099875279651673033695493257291011235831\n", "20 beads: 46066280886404482022\n", "12 beads: 471897639213\n", "20 beads: 48202246066280886404\n", "60 beads: 493257291011235831459437077415617853819099875279651673033695\n", " 3 beads: 505\n", "60 beads: 516730336954932572910112358314594370774156178538190998752796\n", "60 beads: 527965167303369549325729101123583145943707741561785381909987\n", "60 beads: 538190998752796516730336954932572910112358314594370774156178\n", "60 beads: 549325729101123583145943707741561785381909987527965167303369\n", " 3 beads: 550\n", "60 beads: 561785381909987527965167303369549325729101123583145943707741\n", "60 beads: 572910112358314594370774156178538190998752796516730336954932\n", "60 beads: 583145943707741561785381909987527965167303369549325729101123\n", "60 beads: 594370774156178538190998752796516730336954932572910112358314\n", "20 beads: 60662808864044820224\n", "60 beads: 617853819099875279651673033695493257291011235831459437077415\n", "20 beads: 62808864044820224606\n", "12 beads: 639213471897\n", "20 beads: 64044820224606628088\n", "60 beads: 651673033695493257291011235831459437077415617853819099875279\n", "20 beads: 66280886404482022460\n", "60 beads: 673033695493257291011235831459437077415617853819099875279651\n", " 4 beads: 6842\n", "60 beads: 695493257291011235831459437077415617853819099875279651673033\n", "60 beads: 707741561785381909987527965167303369549325729101123583145943\n", "12 beads: 718976392134\n", "60 beads: 729101123583145943707741561785381909987527965167303369549325\n", "60 beads: 730336954932572910112358314594370774156178538190998752796516\n", "60 beads: 741561785381909987527965167303369549325729101123583145943707\n", "60 beads: 752796516730336954932572910112358314594370774156178538190998\n", "12 beads: 763921347189\n", "60 beads: 774156178538190998752796516730336954932572910112358314594370\n", "60 beads: 785381909987527965167303369549325729101123583145943707741561\n", "60 beads: 796516730336954932572910112358314594370774156178538190998752\n", "20 beads: 80886404482022460662\n", "60 beads: 819099875279651673033695493257291011235831459437077415617853\n", "20 beads: 82022460662808864044\n", "60 beads: 831459437077415617853819099875279651673033695493257291011235\n", " 4 beads: 8426\n", "60 beads: 853819099875279651673033695493257291011235831459437077415617\n", "20 beads: 86404482022460662808\n", "60 beads: 875279651673033695493257291011235831459437077415617853819099\n", "20 beads: 88640448202246066280\n", "12 beads: 897639213471\n", "60 beads: 909987527965167303369549325729101123583145943707741561785381\n", "60 beads: 910112358314594370774156178538190998752796516730336954932572\n", "12 beads: 921347189763\n", "60 beads: 932572910112358314594370774156178538190998752796516730336954\n", "60 beads: 943707741561785381909987527965167303369549325729101123583145\n", "60 beads: 954932572910112358314594370774156178538190998752796516730336\n", "60 beads: 965167303369549325729101123583145943707741561785381909987527\n", "12 beads: 976392134718\n", "60 beads: 987527965167303369549325729101123583145943707741561785381909\n", "60 beads: 998752796516730336954932572910112358314594370774156178538190\n" ] } ], "source": [ "show(all_bracelets)" ] }, { "cell_type": "markdown", "id": "21dc3fe1-dc06-4b57-a7b8-e21502086ac9", "metadata": {}, "source": [ "# All Possible *Different* Bracelets?\n", "\n", "Consider the 4-bead bracelet I showed at the top of the page: \n", "\n", "![](http://www.geom.uiuc.edu/~addingto/number_bracelets/2,6bracelet.gif)\n", "\n", "That can be represented by `[2, 6, 8, 4]` or `[6, 8, 4, 2]` or `[8, 4, 2, 6]` or `[4, 2, 6, 8]`. Bracelets are circular, so all four of these lists are really the same bracelet. \n", "\n", "How many *different* bracelets are there? To find out, I'll map each bracelet into a *canonical* representation, which is chosen by considering every possible rotation of the beads in a bracelet, and picking the rotation that has the minimum lexicographical value. (And I'll make it a tuple rather than a list so that it can be put into a set.) Thus, each of the four orderings mentioned above has the canonical representation `(2, 6, 8, 4)`:" ] }, { "cell_type": "code", "execution_count": 8, "id": "8f1aa4fb-fef8-4969-a8d1-bc8a56b10c44", "metadata": {}, "outputs": [], "source": [ "def canon(bracelet: Bracelet) -> Bracelet:\n", " \"\"\"The canonical (lexicographically minimum) rotation of the bracelet.\"\"\"\n", " rotations = (bracelet[i:] + bracelet[:i] for i in range(len(bracelet)))\n", " return tuple(min(rotations))" ] }, { "cell_type": "code", "execution_count": 9, "id": "d8a8e358-b9cf-4bd3-88e2-789145329764", "metadata": {}, "outputs": [], "source": [ "assert canon([2, 6, 8, 4]) == canon([6, 8, 4, 2]) == canon([8, 4, 2, 6]) == canon([4, 2, 6, 8]) == (2, 6, 8, 4)" ] }, { "cell_type": "markdown", "id": "e9da261c-1000-4c2b-9c87-4d1894d7ae41", "metadata": {}, "source": [ "Now we're ready to show the complete set of all the distinct bracelets. " ] }, { "cell_type": "code", "execution_count": 10, "id": "0cb6ff68-e15a-4bdf-92e4-2ba63963b8f7", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " 1 beads: 0\n", "60 beads: 011235831459437077415617853819099875279651673033695493257291\n", "20 beads: 02246066280886404482\n", " 3 beads: 055\n", "12 beads: 134718976392\n", " 4 beads: 2684\n" ] } ], "source": [ "distinct_bracelets = sorted({canon(b) for b in all_bracelets})\n", "\n", "show(distinct_bracelets)" ] }, { "cell_type": "markdown", "id": "5276c097-6a6b-4b6a-b146-ef2fae4a8ecd", "metadata": {}, "source": [ "We see there are only **six** different bracelets." ] } ], "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.13.1" } }, "nbformat": 4, "nbformat_minor": 5 }