Number Bracelets: add type hints
This commit is contained in:
@@ -6,16 +6,18 @@
|
||||
"id": "3c99bb5b-3e7b-486e-b888-440b5862b48c",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<div align=\"right\" style=\"text-align: right\"><i>Peter Norvig<br>2024</i></div>\n",
|
||||
"\n",
|
||||
"# The Number Bracelets Game\n",
|
||||
"\n",
|
||||
"Susan Addington describes [the numbers bracelet game](http://www.geom.uiuc.edu/~addingto/number_bracelets/number_bracelets.html):\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 (ones) digit of the sum.*\n",
|
||||
"- *To get the next bead, add the numbers on the last two beads you used, and use only the ones digit. So to get the fourth bead, add the numbers on the second and third beads, and use the ones digit.*\n",
|
||||
"- *Keep going until you get back to the first and second beads, in that order.*\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.*"
|
||||
]
|
||||
},
|
||||
@@ -26,22 +28,47 @@
|
||||
"source": [
|
||||
"# Making Bracelets\n",
|
||||
"\n",
|
||||
"The function `number_bracelet` will make a number bracelet, if you give it a pair of starting beads:"
|
||||
"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(beads):\n",
|
||||
" \"\"\"Given a pair of beads, extend the beads until the first two match the last two.\"\"\"\n",
|
||||
"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",
|
||||
" next_bead = (beads[-1] + beads[-2]) % 10\n",
|
||||
" beads = (*beads, next_bead)\n",
|
||||
" if beads[:2] == beads[-2:]: return beads[:-2]"
|
||||
" bead = (bracelet[-2] + bracelet[-1]) % 10\n",
|
||||
" bracelet.append(bead) \n",
|
||||
" if bracelet[:2] == bracelet[-2:]: \n",
|
||||
" return bracelet[:-2]"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -54,23 +81,23 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"execution_count": 3,
|
||||
"id": "85ac6e69-7dc6-4fb2-9359-f38f606d1917",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"(2, 6, 8, 4)"
|
||||
"[2, 6, 8, 4]"
|
||||
]
|
||||
},
|
||||
"execution_count": 2,
|
||||
"execution_count": 3,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"number_bracelet((2, 6))"
|
||||
"number_bracelet(2, 6)"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -83,23 +110,23 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"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)"
|
||||
"[1, 3, 4, 7, 1, 8, 9, 7, 6, 3, 9, 2]"
|
||||
]
|
||||
},
|
||||
"execution_count": 3,
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"number_bracelet((1, 3))"
|
||||
"number_bracelet(1, 3)"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -115,7 +142,7 @@
|
||||
"id": "72b69698-6e08-4dd9-a725-533a658a175a",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"One question: If the two starting beads are both 0, then all the following beads will also be 0. When do we stop? Do we stop after (0, 0, 0), drop the last two, and end up with a single 0? Or do we keep going to (0, 0, 0, 0), drop the last two, and end up with (0, 0)? I chose the former, but either choice would be ok, I think."
|
||||
"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? Three? To me, the rules imply that a single bead is indeed a number bracelet, but I'm open to other interpretations."
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -130,14 +157,12 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"execution_count": 5,
|
||||
"id": "f248d81c-2738-405c-a62e-b127a63a0a42",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"digits = (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)\n",
|
||||
"\n",
|
||||
"all_bracelets = [number_bracelet((first, second)) for first in digits for second in digits]"
|
||||
"all_bracelets = [number_bracelet(A, B) for A in range(10) for B in range(10)]"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -145,13 +170,26 @@
|
||||
"id": "ae759911-d85e-4d53-9f2c-a637fb4ee7e9",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"I'll define `show` to print out each bracelet on a line, with the number of beads in the bracelet first:"
|
||||
"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": 5,
|
||||
"id": "70773cb8-9359-401a-b79b-165aa12161a6",
|
||||
"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": [
|
||||
{
|
||||
@@ -262,12 +300,6 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"def show(bracelets):\n",
|
||||
" \"\"\"Print each of the bracelets, preceeded by its number of beads.\n",
|
||||
" Squish the beads together with no spaces between them, so they fit on one line.\"\"\"\n",
|
||||
" for bracelet in bracelets:\n",
|
||||
" print(f'{len(bracelet):2} beads: ', *bracelet, sep='')\n",
|
||||
"\n",
|
||||
"show(all_bracelets)"
|
||||
]
|
||||
},
|
||||
@@ -276,76 +308,51 @@
|
||||
"id": "21dc3fe1-dc06-4b57-a7b8-e21502086ac9",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# How Many Bracelets?\n",
|
||||
"# All Possible *Different* Bracelets?\n",
|
||||
"\n",
|
||||
"I have a list of 100 bracelets, but consider the 4-bead bracelet I showed at the top of the page; the one with the list of beads `(2, 6, 8, 4)`. Bracelets are circular, so that's really the same bracelet as `(6, 8, 4, 2)` or `(8, 4, 2, 6)` or `(4, 2, 6, 8)`. So how many *different* bracelets are there?\n",
|
||||
"Consider the 4-bead bracelet I showed at the top of the page: \n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"To find out, I'll put all bracelets in a *common form*: I'll choose the sequence that starts with the lowest number in the bracelet, so that would be `(2, 6, 8, 4)`. If a bracelet repeats the lowest number more than once, start with the lowest two-digit pair, and so on. Once I have all the bracelets in common form, make a set out of them and `show` the distinct members of this set:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"id": "e3a97290-59bf-44dc-922c-6fbecaa21317",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def common_form(bracelet):\n",
|
||||
" \"\"\"Represent this bracelet by choosing the lowest numerical rotation out of all possible rotations.\"\"\"\n",
|
||||
" return min(rotations(bracelet))\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",
|
||||
"def rotations(bracelet):\n",
|
||||
" \"\"\"All possible rotations of bracelet.\"\"\"\n",
|
||||
" return [bracelet[i:] + bracelet[:i] for i in range(len(bracelet))]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"id": "d8a8e358-b9cf-4bd3-88e2-789145329764",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"[(2, 6, 8, 4), (6, 8, 4, 2), (8, 4, 2, 6), (4, 2, 6, 8)]"
|
||||
]
|
||||
},
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"rotations((2, 6, 8, 4))"
|
||||
"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": "64f66bdd-f5ff-4861-8861-b33dc2707182",
|
||||
"id": "8f1aa4fb-fef8-4969-a8d1-bc8a56b10c44",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"(2, 6, 8, 4)"
|
||||
]
|
||||
},
|
||||
"execution_count": 8,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"common_form((6, 8, 4, 2))"
|
||||
"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": [
|
||||
@@ -353,17 +360,19 @@
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
" 4 beads: 2684\n",
|
||||
" 1 beads: 0\n",
|
||||
"60 beads: 011235831459437077415617853819099875279651673033695493257291\n",
|
||||
"20 beads: 02246066280886404482\n",
|
||||
" 3 beads: 055\n",
|
||||
"12 beads: 134718976392\n",
|
||||
"60 beads: 011235831459437077415617853819099875279651673033695493257291\n",
|
||||
" 1 beads: 0\n",
|
||||
"20 beads: 02246066280886404482\n"
|
||||
" 4 beads: 2684\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"show({common_form(b) for b in all_bracelets})"
|
||||
"distinct_bracelets = sorted({canon(b) for b in all_bracelets})\n",
|
||||
"\n",
|
||||
"show(distinct_bracelets)"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -371,7 +380,7 @@
|
||||
"id": "5276c097-6a6b-4b6a-b146-ef2fae4a8ecd",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"We see there are only six distinct bracelets."
|
||||
"We see there are only **six** different bracelets."
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -391,7 +400,7 @@
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.8.15"
|
||||
"version": "3.13.1"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
|
||||
Reference in New Issue
Block a user