From 69cc3ce239c0f7198fdedc4e880e128db62a9028 Mon Sep 17 00:00:00 2001 From: Peter Norvig Date: Thu, 26 Dec 2024 01:35:10 -0800 Subject: [PATCH] Add files via upload --- ipynb/Advent-2024.ipynb | 1810 ++++++++++++++++++++++++++++++++------- ipynb/AdventUtils.ipynb | 12 +- 2 files changed, 1522 insertions(+), 300 deletions(-) diff --git a/ipynb/Advent-2024.ipynb b/ipynb/Advent-2024.ipynb index eacf85b..4d9d4c3 100644 --- a/ipynb/Advent-2024.ipynb +++ b/ipynb/Advent-2024.ipynb @@ -13,7 +13,7 @@ "\n", "\"GaryJGrady\n", "\n", - "I traditionally start by loading up my [**AdventUtils.ipynb**](AdventUtils.ipynb) notebook (same as last time except for the `current_year`):" + "Even before December first I can start by loading up my [**AdventUtils.ipynb**](AdventUtils.ipynb) notebook (same as last time except for the `current_year`):" ] }, { @@ -43,13 +43,13 @@ "metadata": {}, "source": [ "Each day's solution consists of three parts, making use of my `parse` and `answer` utilities:\n", - "- **Reading the day's input**. E.g. `pairs = parse(1, ints)`. \n", + "- **Reading the input**. Parse the input file with, e.g. `pairs = parse(1, ints)`. \n", "- **Solving Part One**. Find the solution and record it with, e.g., `answer(1.1, 4, lambda: 2 + 2)`.\n", "- **Solving Part Two**. Find the solution and record it with, e.g., `answer(1.2, 9, lambda: 3 * 3)`.\n", "\n", - "The function `parse` assumes that the input is a sequence of records (default one per line), each of which should be parsed in some way (default just left as a string, but the argument `ints` says to treat each record as a tuple of integers). The function `answer` records the correct answer (for regression testing), and records the run time (that's why a `lambda:` is used).\n", + "The function `parse` assumes that the input is a sequence of sections (default one per line), each of which should be parsed in some way and then returned as a tuple. The parsing method `ints` says to treat each section as a tuple of integers. The function `answer` checks that the correct answer is computed (useful for regression testing), and records the run time (that's why a `lambda:` is used). You can read the [**AdventUtils.ipynb**](AdventUtils.ipynb) notebook for more on these functions (and the other utilities used throughout this notebook, such as the `Grid` class).\n", "\n", - "To fully understand each day's puzzle, and to follow along the drama involving Santa, the elves, the reindeer, the elephants, the Chief Historian, and all the rest, you need to read the descriptions on the [**AoC**](https://adventofcode.com/) site, as linked in the header for each of my day's solutions, e.g. [**Day 1**](https://adventofcode.com/2023/day/1) below. I can't copy the content of AoC here, nor show my input files; you need to go to the site for that." + "To fully understand each day's puzzle, and to follow along the drama involving Santa, the elves, the reindeer, some elephants, the Chief Historian, and all the rest, you need to read the descriptions on the [**AoC**](https://adventofcode.com/) site, as linked in the header for each of each day's solutions (e.g. [**Day 1**](https://adventofcode.com/2023/day/1) below). I can't copy the content of AoC here, nor show my input files; you need to go to the site for that." ] }, { @@ -112,7 +112,7 @@ "\n", "### Part 1: What is the total distance between your lists?\n", "\n", - "The **distance** between two numbers is the absolute value of their difference, and the **total distance** between two lists is the sum of the distances between respective pairs, where \"respective\" means to sort each list and then take the distance between the first element of each list, plus the distance between the second element of each list, and so on. (I use the transpose utility function, `T`, to turn the input sequence of 1000 pairs into two lists, each of 1000 integers.)" + "The **distance** between two numbers is defined as the absolute value of their difference, and the **total distance** between two lists is the sum of the distances between respective pairs, where \"respective\" means to sort each list and then take the distance between the first element of each list, plus the distance between the second element of each list, and so on. (I use the transpose utility function, `T`, to turn the input sequence of 1000 pairs into two lists, each of 1000 integers.)" ] }, { @@ -156,7 +156,7 @@ "source": [ "### Part 2: What is their similarity score?\n", "\n", - "The **similarity score** is the sum of each element of the left list times the number of times that value appears in the right list." + "The **similarity score** is defined as the sum of each element of the left list times the number of times that value appears in the right list." ] }, { @@ -258,26 +258,42 @@ { "cell_type": "code", "execution_count": 15, - "id": "368cbe1c-b6b6-4a82-bef9-599ee9725899", + "id": "0f6b6744-e93d-47cf-accd-daab9f3650d0", "metadata": {}, "outputs": [], "source": [ "def is_safe(report: Ints) -> bool:\n", - " \"\"\"A report is safe if all differences are either in {1, 2, 3} or in {-1, -2, -3}.\"\"\"\n", + " \"\"\"A report is safe if all adjacent-number-differences are either in {1, 2, 3} or in {-1, -2, -3}.\"\"\"\n", " deltas = diffs(report)\n", " return deltas.issubset({1, 2, 3}) or deltas.issubset({-1, -2, -3})\n", " \n", "def diffs(report: Ints) -> set:\n", " \"\"\"The set of differences between adjacent numbers in the report.\"\"\"\n", - " return {report[i] - report[i - 1] for i in range(1, len(report))}\n", - "\n", + " return {report[i] - report[i - 1] for i in range(1, len(report))}" + ] + }, + { + "cell_type": "markdown", + "id": "5625ad98-f571-4451-ad19-8614833dfb10", + "metadata": {}, + "source": [ + "Here are examples of how these functions work:" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "c0cc052b-e9ef-4757-a860-4cd34dd00fb8", + "metadata": {}, + "outputs": [], + "source": [ "assert diffs((7, 6, 4, 2, 1)) == {-1, -2}\n", "assert is_safe((7, 6, 4, 2, 1)) == True" ] }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 18, "id": "e662bf10-4d6a-40f1-95ce-dfc39f5b3fc2", "metadata": {}, "outputs": [ @@ -287,7 +303,7 @@ "Puzzle 2.1: .000 seconds, answer 257 ok" ] }, - "execution_count": 16, + "execution_count": 18, "metadata": {}, "output_type": "execute_result" } @@ -302,6 +318,8 @@ "id": "ee48bf63-8a67-407b-9a73-df097811eabc", "metadata": {}, "source": [ + "Note: I used my AdventUtil function `quantify`, where `quantify(reports, is_safe)` means the number of items in `reports` for which `is_safe` is true.\n", + "\n", "### Part 2: How many reports are safe using the Problem Dampener?\n", "\n", "The **problem dampener** says that a report is safe if you can drop one element and get a safe report." @@ -309,7 +327,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 20, "id": "67ba1d53-95b7-4811-b225-2ff15d6bdc5c", "metadata": {}, "outputs": [], @@ -327,7 +345,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 21, "id": "d1b9ffb5-af7a-465f-a063-c31df2d0605c", "metadata": {}, "outputs": [ @@ -337,7 +355,7 @@ "Puzzle 2.2: .002 seconds, answer 328 ok" ] }, - "execution_count": 19, + "execution_count": 21, "metadata": {}, "output_type": "execute_result" } @@ -359,7 +377,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 23, "id": "78080200-0f9f-4492-9bee-c936737ee96f", "metadata": {}, "outputs": [ @@ -398,21 +416,21 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 25, "id": "bf6366b1-6952-47d8-8b3c-09f8d05ec093", "metadata": {}, "outputs": [], "source": [ "def execute(program: str) -> int:\n", " \"\"\"The sum of the results of the multiply instructions.\"\"\"\n", - " return sum(prod(ints(m)) for m in re.findall(multiplications, program))\n", + " return sum(prod(ints(m)) for m in findall_multiplications(program))\n", "\n", - "multiplications = r'mul\\(\\d+,\\d+\\)'" + "findall_multiplications = re.compile(r'mul\\(\\d+,\\d+\\)').findall" ] }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 26, "id": "2032c903-5f23-4c16-ba68-410b6c1750e1", "metadata": {}, "outputs": [ @@ -422,7 +440,7 @@ "Puzzle 3.1: .001 seconds, answer 156388521 ok" ] }, - "execution_count": 24, + "execution_count": 26, "metadata": {}, "output_type": "execute_result" } @@ -432,6 +450,27 @@ " execute(program))" ] }, + { + "cell_type": "code", + "execution_count": 27, + "id": "85844f51-1396-4299-ba5b-c61064ee02b6", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['mul(1,2)', 'mul(34,5)']" + ] + }, + "execution_count": 27, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "findall_multiplications(\"mul(1,2) + mul(34,5) + mul(67,89]\")" + ] + }, { "cell_type": "markdown", "id": "622d7010-145e-422a-a592-d4b446afcc0f", @@ -444,19 +483,19 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 29, "id": "4525d01a-bac0-41c2-92b8-baf0fd395e88", "metadata": {}, "outputs": [], "source": [ - "def enabled(program: str) -> str:\n", + "def enabled_part(program: str) -> str:\n", " \"\"\"Just the part of the program that is enabled; remove \"don't()...do()\" text.\"\"\"\n", " return re.sub(r\"don't\\(\\).*?(do\\(\\)|$)\", \" \", program)" ] }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 30, "id": "ce40f258-ca76-48c3-9965-27a6979a4243", "metadata": {}, "outputs": [ @@ -466,14 +505,14 @@ "Puzzle 3.2: .000 seconds, answer 75920122 ok" ] }, - "execution_count": 27, + "execution_count": 30, "metadata": {}, "output_type": "execute_result" } ], "source": [ "answer(3.2, 75920122, lambda:\n", - " execute(enabled(program)))" + " execute(enabled_part(program)))" ] }, { @@ -488,7 +527,7 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 32, "id": "a0d903b9-018e-4861-9314-cafed59055fd", "metadata": {}, "outputs": [ @@ -527,7 +566,7 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 34, "id": "72d48abb-7a82-452f-b91d-838b3836a90f", "metadata": {}, "outputs": [], @@ -546,7 +585,7 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 35, "id": "6175362b-d8b4-45d1-b70c-d8575a0fe188", "metadata": {}, "outputs": [ @@ -556,7 +595,7 @@ "Puzzle 4.1: .033 seconds, answer 2401 ok" ] }, - "execution_count": 32, + "execution_count": 35, "metadata": {}, "output_type": "execute_result" } @@ -584,7 +623,7 @@ }, { "cell_type": "code", - "execution_count": 34, + "execution_count": 37, "id": "3d8a051f-cf7b-4e8c-b0fb-78c3f089989d", "metadata": {}, "outputs": [], @@ -601,17 +640,17 @@ }, { "cell_type": "code", - "execution_count": 35, + "execution_count": 38, "id": "64cde1d9-f58c-4633-b5da-87908a02f76d", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "Puzzle 4.2: .027 seconds, answer 1822 ok" + "Puzzle 4.2: .026 seconds, answer 1822 ok" ] }, - "execution_count": 35, + "execution_count": 38, "metadata": {}, "output_type": "execute_result" } @@ -637,7 +676,7 @@ }, { "cell_type": "code", - "execution_count": 37, + "execution_count": 40, "id": "b77a5a1f-a43b-4ce8-a60c-94d69a595505", "metadata": {}, "outputs": [ @@ -692,7 +731,7 @@ }, { "cell_type": "code", - "execution_count": 38, + "execution_count": 41, "id": "4c85a23e-686a-4129-a14c-ff6f6a88b9ac", "metadata": {}, "outputs": [], @@ -713,7 +752,7 @@ }, { "cell_type": "code", - "execution_count": 40, + "execution_count": 43, "id": "78898d37-46ff-4367-9d89-b2a107a90aa1", "metadata": {}, "outputs": [], @@ -731,7 +770,7 @@ }, { "cell_type": "code", - "execution_count": 41, + "execution_count": 44, "id": "b1c87359-1d2d-4a90-8305-9d152ce5d547", "metadata": {}, "outputs": [ @@ -741,7 +780,7 @@ "Puzzle 5.1: .001 seconds, answer 5762 ok" ] }, - "execution_count": 41, + "execution_count": 44, "metadata": {}, "output_type": "execute_result" } @@ -764,7 +803,7 @@ }, { "cell_type": "code", - "execution_count": 43, + "execution_count": 46, "id": "d8718c3e-0b3b-49ce-8cca-abd82aa788d7", "metadata": {}, "outputs": [ @@ -774,7 +813,7 @@ "23" ] }, - "execution_count": 43, + "execution_count": 46, "metadata": {}, "output_type": "execute_result" } @@ -797,7 +836,7 @@ }, { "cell_type": "code", - "execution_count": 45, + "execution_count": 48, "id": "7222dc1c-067f-4bb5-84e1-3c2fc72fd53a", "metadata": {}, "outputs": [], @@ -816,7 +855,7 @@ }, { "cell_type": "code", - "execution_count": 46, + "execution_count": 49, "id": "dc1fbda9-2cfd-442a-afef-12c9b0d2b17f", "metadata": {}, "outputs": [ @@ -826,7 +865,7 @@ "Puzzle 5.2: .001 seconds, answer 4130 ok" ] }, - "execution_count": 46, + "execution_count": 49, "metadata": {}, "output_type": "execute_result" } @@ -856,7 +895,7 @@ }, { "cell_type": "code", - "execution_count": 49, + "execution_count": 52, "id": "6ec71cf8-c43d-457e-8e14-0e9eb99b956a", "metadata": {}, "outputs": [ @@ -893,12 +932,12 @@ "The guard follows this protocol: If there is something directly in front of you, turn right 90 degrees.\n", "Otherwise, take a step forward.\n", "\n", - "I'll define `follow_path` to output a list of all the positions the guard occupies. I realize the puzzle is only asking for a count of the positions, but the path might be useful for Part 2, or for debugging, so I'll return it. I worried that it is also possible for a path to become a loop, but the problem statement says that can't happen, so I won't test for it." + "I'll define `follow_path` to output a list of all the positions the guard occupies. I realize the puzzle is only asking for a count of the positions, but the path might be useful for Part 2, or for debugging, so I'll return it. I worried that it is also possible for a path to become a loop, but the puzzle statement says that can't happen, so I won't test for it." ] }, { "cell_type": "code", - "execution_count": 51, + "execution_count": 54, "id": "95f0b409-a6d6-47bc-8ce5-1c2df80f2b18", "metadata": {}, "outputs": [], @@ -916,7 +955,7 @@ }, { "cell_type": "code", - "execution_count": 52, + "execution_count": 55, "id": "f4be3d1f-7f24-4d55-8221-df0026178e1e", "metadata": {}, "outputs": [ @@ -926,7 +965,7 @@ "Puzzle 6.1: .002 seconds, answer 5329 ok" ] }, - "execution_count": 52, + "execution_count": 55, "metadata": {}, "output_type": "execute_result" } @@ -956,7 +995,7 @@ }, { "cell_type": "code", - "execution_count": 54, + "execution_count": 57, "id": "1718fecb-aa3e-4162-9948-1c06d4ec5e8a", "metadata": {}, "outputs": [], @@ -986,17 +1025,17 @@ }, { "cell_type": "code", - "execution_count": 55, + "execution_count": 58, "id": "36196264-eb33-4fc0-95d5-06c985105ebf", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "Puzzle 6.2: 1.968 seconds, answer 2162 ok" + "Puzzle 6.2: 1.967 seconds, answer 2162 ok" ] }, - "execution_count": 55, + "execution_count": 58, "metadata": {}, "output_type": "execute_result" } @@ -1026,7 +1065,7 @@ }, { "cell_type": "code", - "execution_count": 58, + "execution_count": 61, "id": "c1c6cee8-122c-43c9-8c7d-ed8980ea2b76", "metadata": {}, "outputs": [ @@ -1087,7 +1126,7 @@ }, { "cell_type": "code", - "execution_count": 61, + "execution_count": 64, "id": "6fa3907c-0e1a-4d4a-9fc3-f809b9325674", "metadata": {}, "outputs": [ @@ -1097,7 +1136,7 @@ "13" ] }, - "execution_count": 61, + "execution_count": 64, "metadata": {}, "output_type": "execute_result" } @@ -1116,7 +1155,7 @@ }, { "cell_type": "code", - "execution_count": 63, + "execution_count": 66, "id": "5dfe0edf-cf29-4623-bb2c-6180f832f4d7", "metadata": {}, "outputs": [], @@ -1132,17 +1171,17 @@ }, { "cell_type": "code", - "execution_count": 64, + "execution_count": 67, "id": "3085596d-f5ec-4ba8-b05a-cf70cf276a0c", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "Puzzle 7.1: .014 seconds, answer 1985268524462 ok" + "Puzzle 7.1: .013 seconds, answer 1985268524462 ok" ] }, - "execution_count": 64, + "execution_count": 67, "metadata": {}, "output_type": "execute_result" } @@ -1164,7 +1203,7 @@ }, { "cell_type": "code", - "execution_count": 66, + "execution_count": 69, "id": "393a50cf-f136-446a-a97e-c501669ce89f", "metadata": {}, "outputs": [], @@ -1174,17 +1213,17 @@ }, { "cell_type": "code", - "execution_count": 67, + "execution_count": 70, "id": "f8e75ea3-e8ba-4b33-8efe-8bf74357e35d", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "Puzzle 7.2: .801 seconds, answer 150077710195188 ok" + "Puzzle 7.2: .811 seconds, answer 150077710195188 ok" ] }, - "execution_count": 67, + "execution_count": 70, "metadata": {}, "output_type": "execute_result" } @@ -1204,7 +1243,7 @@ }, { "cell_type": "code", - "execution_count": 69, + "execution_count": 72, "id": "6fe6adad-a3a6-49b8-b49e-6098b27e3a44", "metadata": {}, "outputs": [], @@ -1222,17 +1261,17 @@ }, { "cell_type": "code", - "execution_count": 70, + "execution_count": 73, "id": "ffb673f1-af9d-4d15-8f8d-92e29489dd78", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "Puzzle 7.2: .656 seconds, answer 150077710195188 ok" + "Puzzle 7.2: .660 seconds, answer 150077710195188 ok" ] }, - "execution_count": 70, + "execution_count": 73, "metadata": {}, "output_type": "execute_result" } @@ -1254,7 +1293,7 @@ }, { "cell_type": "code", - "execution_count": 72, + "execution_count": 75, "id": "cf6361a7-e3bc-42ec-ae16-f9eec166055e", "metadata": {}, "outputs": [ @@ -1295,7 +1334,7 @@ }, { "cell_type": "code", - "execution_count": 74, + "execution_count": 77, "id": "22180ce8-5d03-4aee-8c73-62f2afbddf71", "metadata": {}, "outputs": [], @@ -1316,7 +1355,7 @@ }, { "cell_type": "code", - "execution_count": 75, + "execution_count": 78, "id": "dd173ce9-cbbb-4282-b43f-c7cff662bd90", "metadata": {}, "outputs": [ @@ -1326,7 +1365,7 @@ "Puzzle 8.1: .003 seconds, answer 220 ok" ] }, - "execution_count": 75, + "execution_count": 78, "metadata": {}, "output_type": "execute_result" } @@ -1350,7 +1389,7 @@ }, { "cell_type": "code", - "execution_count": 77, + "execution_count": 80, "id": "d30f8ce9-f186-46a0-a2e7-f74eceae6905", "metadata": {}, "outputs": [], @@ -1371,7 +1410,7 @@ }, { "cell_type": "code", - "execution_count": 78, + "execution_count": 81, "id": "6bf85b57-8b8f-4196-9903-6d5fe082f404", "metadata": {}, "outputs": [ @@ -1381,7 +1420,7 @@ "Puzzle 8.1: .003 seconds, answer 220 ok" ] }, - "execution_count": 78, + "execution_count": 81, "metadata": {}, "output_type": "execute_result" } @@ -1393,7 +1432,7 @@ }, { "cell_type": "code", - "execution_count": 79, + "execution_count": 82, "id": "f232952c-5fc6-4696-a8b1-d0b54137ac02", "metadata": {}, "outputs": [ @@ -1403,7 +1442,7 @@ "Puzzle 8.2: .003 seconds, answer 813 ok" ] }, - "execution_count": 79, + "execution_count": 82, "metadata": {}, "output_type": "execute_result" } @@ -1433,7 +1472,7 @@ }, { "cell_type": "code", - "execution_count": 82, + "execution_count": 85, "id": "0e944f9e-5c16-440c-b12e-178058a87048", "metadata": {}, "outputs": [ @@ -1474,7 +1513,7 @@ }, { "cell_type": "code", - "execution_count": 84, + "execution_count": 87, "id": "76e8454d-a2f3-4b6b-92df-182116cf46e0", "metadata": {}, "outputs": [], @@ -1506,17 +1545,17 @@ }, { "cell_type": "code", - "execution_count": 85, + "execution_count": 88, "id": "2aa7e2b9-844e-49ed-b41b-4a4cecff86b7", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "Puzzle 9.1: .020 seconds, answer 6332189866718 ok" + "Puzzle 9.1: .019 seconds, answer 6332189866718 ok" ] }, - "execution_count": 85, + "execution_count": 88, "metadata": {}, "output_type": "execute_result" } @@ -1544,7 +1583,7 @@ }, { "cell_type": "code", - "execution_count": 87, + "execution_count": 90, "id": "fcf4d832-3d7d-4987-aa57-e6e0f1df16bf", "metadata": {}, "outputs": [], @@ -1586,17 +1625,17 @@ }, { "cell_type": "code", - "execution_count": 88, + "execution_count": 91, "id": "e3036875-88d0-496e-9d2f-facd0e80a5b2", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "Puzzle 9.2: 2.731 seconds, answer 6353648390778 ok" + "Puzzle 9.2: 2.754 seconds, answer 6353648390778 ok" ] }, - "execution_count": 88, + "execution_count": 91, "metadata": {}, "output_type": "execute_result" } @@ -1626,7 +1665,7 @@ }, { "cell_type": "code", - "execution_count": 91, + "execution_count": 94, "id": "5804fb03-05f3-402f-b6cc-6804c5d22512", "metadata": {}, "outputs": [ @@ -1679,7 +1718,7 @@ }, { "cell_type": "code", - "execution_count": 93, + "execution_count": 96, "id": "76b5379e-ee19-4607-91b8-88ec7b38023f", "metadata": {}, "outputs": [], @@ -1695,7 +1734,7 @@ }, { "cell_type": "code", - "execution_count": 94, + "execution_count": 97, "id": "97cf05f7-fa56-4a90-b2d8-2cd4d9b81f95", "metadata": {}, "outputs": [ @@ -1705,7 +1744,7 @@ "Puzzle 10.1: .005 seconds, answer 744 ok" ] }, - "execution_count": 94, + "execution_count": 97, "metadata": {}, "output_type": "execute_result" } @@ -1729,7 +1768,7 @@ }, { "cell_type": "code", - "execution_count": 96, + "execution_count": 99, "id": "b763450f-a565-4936-bee4-e531c2eeebdb", "metadata": {}, "outputs": [], @@ -1746,7 +1785,7 @@ }, { "cell_type": "code", - "execution_count": 97, + "execution_count": 100, "id": "f8a87032-6556-4fc9-9bb8-573611aee8dc", "metadata": {}, "outputs": [ @@ -1756,7 +1795,7 @@ "Puzzle 10.2: .006 seconds, answer 1651 ok" ] }, - "execution_count": 97, + "execution_count": 100, "metadata": {}, "output_type": "execute_result" } @@ -1778,7 +1817,7 @@ }, { "cell_type": "code", - "execution_count": 99, + "execution_count": 102, "id": "4b35defa-a19e-46c5-bd04-3af55bea14e4", "metadata": {}, "outputs": [ @@ -1818,7 +1857,7 @@ }, { "cell_type": "code", - "execution_count": 101, + "execution_count": 104, "id": "76b68cef-d8de-4145-b65c-b254fedf1671", "metadata": {}, "outputs": [ @@ -1862,7 +1901,7 @@ }, { "cell_type": "code", - "execution_count": 103, + "execution_count": 106, "id": "1513df56-3d6f-42cf-8aec-1bdbeb991d90", "metadata": {}, "outputs": [], @@ -1888,7 +1927,7 @@ }, { "cell_type": "code", - "execution_count": 104, + "execution_count": 107, "id": "eff17cd0-a2c7-4d69-bc55-c0ef97917915", "metadata": {}, "outputs": [ @@ -1898,7 +1937,7 @@ "Puzzle 11.1: .068 seconds, answer 194482 ok" ] }, - "execution_count": 104, + "execution_count": 107, "metadata": {}, "output_type": "execute_result" } @@ -1924,7 +1963,7 @@ }, { "cell_type": "code", - "execution_count": 106, + "execution_count": 109, "id": "707b5a97-0296-48df-bdab-e34064cc67c2", "metadata": {}, "outputs": [], @@ -1949,7 +1988,7 @@ }, { "cell_type": "code", - "execution_count": 108, + "execution_count": 111, "id": "efdcdbf8-e8ec-4a85-9d09-90a20e08c66a", "metadata": {}, "outputs": [ @@ -1959,7 +1998,7 @@ "Puzzle 11.1: .002 seconds, answer 194482 ok" ] }, - "execution_count": 108, + "execution_count": 111, "metadata": {}, "output_type": "execute_result" } @@ -1971,17 +2010,17 @@ }, { "cell_type": "code", - "execution_count": 109, + "execution_count": 112, "id": "657b1f13-ffcc-44c6-84f1-398fa2fcdac7", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "Puzzle 11.2: .060 seconds, answer 232454623677743 ok" + "Puzzle 11.2: .061 seconds, answer 232454623677743 ok" ] }, - "execution_count": 109, + "execution_count": 112, "metadata": {}, "output_type": "execute_result" } @@ -2011,7 +2050,7 @@ }, { "cell_type": "code", - "execution_count": 112, + "execution_count": 115, "id": "8161ee7e-76e3-499a-abf8-a607991c9602", "metadata": {}, "outputs": [ @@ -2050,7 +2089,7 @@ }, { "cell_type": "code", - "execution_count": 114, + "execution_count": 117, "id": "79f91f38-e325-44f2-9e53-b64ce12d9d35", "metadata": {}, "outputs": [], @@ -2079,7 +2118,7 @@ }, { "cell_type": "code", - "execution_count": 116, + "execution_count": 119, "id": "1fbabbfb-50c8-4197-8517-e7cee9582765", "metadata": {}, "outputs": [], @@ -2103,7 +2142,7 @@ }, { "cell_type": "code", - "execution_count": 117, + "execution_count": 120, "id": "cdaf655b-d12c-4973-b19b-3132e5e691c6", "metadata": {}, "outputs": [ @@ -2113,7 +2152,7 @@ "Puzzle 12.1: .033 seconds, answer 1402544 ok" ] }, - "execution_count": 117, + "execution_count": 120, "metadata": {}, "output_type": "execute_result" } @@ -2153,7 +2192,7 @@ }, { "cell_type": "code", - "execution_count": 119, + "execution_count": 122, "id": "38c30e15-3a33-40c2-b734-163a15af7a8a", "metadata": {}, "outputs": [], @@ -2176,17 +2215,17 @@ }, { "cell_type": "code", - "execution_count": 120, + "execution_count": 123, "id": "72175812-dcd0-4f1b-9efa-0dceeeafa609", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "Puzzle 12.1: .051 seconds, answer 1402544 ok" + "Puzzle 12.1: .052 seconds, answer 1402544 ok" ] }, - "execution_count": 120, + "execution_count": 123, "metadata": {}, "output_type": "execute_result" } @@ -2198,7 +2237,7 @@ }, { "cell_type": "code", - "execution_count": 121, + "execution_count": 124, "id": "9defcd35-91bc-41d4-a16f-bb7a4ede75e7", "metadata": {}, "outputs": [ @@ -2208,7 +2247,7 @@ "Puzzle 12.2: .042 seconds, answer 862486 ok" ] }, - "execution_count": 121, + "execution_count": 124, "metadata": {}, "output_type": "execute_result" } @@ -2225,12 +2264,14 @@ "source": [ "# [Day 13](https://adventofcode.com/2024/day/13): Claw Contraption\n", "\n", - "Today's puzzle involves arcade claw machines. Each input paragraph describes a machine with two buttons, **A** and **B**; every push of a button moves the claw a specified amount in the X and Y directions. The prize is at a specified location. We can parse the input by defining a `parse_claw` function and a `Claw` datatype to represent a claw machine:" + "Today's puzzle involves arcade claw machines. Each input paragraph describes a machine with two buttons, **A** and **B**; every push of a button moves the claw a specified amount in the X and Y directions. The prize is at a specified location. We can parse the input by defining a `parse_claw` function and a `Claw` datatype to represent a claw machine.\n", + "\n", + "" ] }, { "cell_type": "code", - "execution_count": 123, + "execution_count": 126, "id": "e78f45c0-c420-4661-aad2-14e122b4473b", "metadata": {}, "outputs": [ @@ -2294,7 +2335,7 @@ }, { "cell_type": "code", - "execution_count": 125, + "execution_count": 128, "id": "c2c4bbc9-42cd-483d-8da2-97cf051e93fe", "metadata": {}, "outputs": [], @@ -2318,7 +2359,7 @@ }, { "cell_type": "code", - "execution_count": 126, + "execution_count": 129, "id": "f5638ed4-1e59-4b9f-b1fc-427d2eb0d036", "metadata": {}, "outputs": [ @@ -2328,7 +2369,7 @@ "Puzzle 13.1: .010 seconds, answer 29598 ok" ] }, - "execution_count": 126, + "execution_count": 129, "metadata": {}, "output_type": "execute_result" } @@ -2365,7 +2406,7 @@ }, { "cell_type": "code", - "execution_count": 128, + "execution_count": 131, "id": "df8da2ae-52f9-409b-a54f-ad7d21b32e45", "metadata": {}, "outputs": [ @@ -2375,7 +2416,7 @@ "Counter({0: 168, 1: 152})" ] }, - "execution_count": 128, + "execution_count": 131, "metadata": {}, "output_type": "execute_result" } @@ -2404,7 +2445,7 @@ }, { "cell_type": "code", - "execution_count": 130, + "execution_count": 133, "id": "6bbd0934-d962-4c93-940b-810651e9e568", "metadata": {}, "outputs": [], @@ -2428,7 +2469,7 @@ }, { "cell_type": "code", - "execution_count": 132, + "execution_count": 135, "id": "dd38ba4c-44ba-426b-b1c8-0e10adbdd642", "metadata": {}, "outputs": [], @@ -2440,7 +2481,7 @@ }, { "cell_type": "code", - "execution_count": 133, + "execution_count": 136, "id": "9f578b3e-6b6d-4eb0-9228-c98122a84747", "metadata": {}, "outputs": [ @@ -2450,7 +2491,7 @@ "Puzzle 13.2: .000 seconds, answer 93217456941970 ok" ] }, - "execution_count": 133, + "execution_count": 136, "metadata": {}, "output_type": "execute_result" } @@ -2470,7 +2511,7 @@ }, { "cell_type": "code", - "execution_count": 135, + "execution_count": 138, "id": "609ed4ce-548c-4af4-8e09-c621aca0124e", "metadata": {}, "outputs": [ @@ -2480,7 +2521,7 @@ "Puzzle 13.1: .000 seconds, answer 29598 ok" ] }, - "execution_count": 135, + "execution_count": 138, "metadata": {}, "output_type": "execute_result" } @@ -2502,7 +2543,7 @@ }, { "cell_type": "code", - "execution_count": 137, + "execution_count": 140, "id": "1a5f5875-426d-47ea-a35a-405c39ced5dd", "metadata": {}, "outputs": [ @@ -2553,7 +2594,7 @@ }, { "cell_type": "code", - "execution_count": 139, + "execution_count": 142, "id": "be22ac94-7401-4cf6-ab83-e43775536af7", "metadata": {}, "outputs": [], @@ -2575,7 +2616,7 @@ }, { "cell_type": "code", - "execution_count": 140, + "execution_count": 143, "id": "69093001-79aa-463a-b801-51cd5b4de4eb", "metadata": {}, "outputs": [ @@ -2585,7 +2626,7 @@ "Puzzle 14.1: .000 seconds, answer 216027840 ok" ] }, - "execution_count": 140, + "execution_count": 143, "metadata": {}, "output_type": "execute_result" } @@ -2613,7 +2654,7 @@ }, { "cell_type": "code", - "execution_count": 142, + "execution_count": 145, "id": "664c686e-0c3d-43b8-970f-88c0bf47dbf6", "metadata": {}, "outputs": [], @@ -2645,7 +2686,7 @@ }, { "cell_type": "code", - "execution_count": 144, + "execution_count": 147, "id": "87843969-cb37-4fa5-9788-6a1b71c43521", "metadata": {}, "outputs": [ @@ -2837,42 +2878,42 @@ "\n", "\n", "
\n", - " \n", + " \n", "
\n", - " \n", + " oninput=\"anim5d9cef03daf64662882705f9f9bba16b.set_frame(parseInt(this.value));\">\n", "
\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", "
\n", - "
\n", - " \n", - " \n", - " Once\n", + " \n", - " \n", - " Loop\n", + " \n", - " \n", + " \n", "
\n", "
\n", "
\n", @@ -2882,9 +2923,9 @@ " /* Instantiate the Animation class. */\n", " /* The IDs given should match those used in the template above. */\n", " (function() {\n", - " var img_id = \"_anim_img48c518432d5b41ef99bf29888413d888\";\n", - " var slider_id = \"_anim_slider48c518432d5b41ef99bf29888413d888\";\n", - " var loop_select_id = \"_anim_loop_select48c518432d5b41ef99bf29888413d888\";\n", + " var img_id = \"_anim_img5d9cef03daf64662882705f9f9bba16b\";\n", + " var slider_id = \"_anim_slider5d9cef03daf64662882705f9f9bba16b\";\n", + " var loop_select_id = \"_anim_loop_select5d9cef03daf64662882705f9f9bba16b\";\n", " var frames = new Array(3);\n", " \n", " frames[0] = \"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA8AAAALQCAYAAABfdxm0AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90\\\n", @@ -5656,17 +5697,17 @@ " /* set a timeout to make sure all the above elements are created before\n", " the object is initialized. */\n", " setTimeout(function() {\n", - " anim48c518432d5b41ef99bf29888413d888 = new Animation(frames, img_id, slider_id, 200.0,\n", + " anim5d9cef03daf64662882705f9f9bba16b = new Animation(frames, img_id, slider_id, 200.0,\n", " loop_select_id);\n", " }, 0);\n", " })()\n", "\n" ], "text/plain": [ - "" + "" ] }, - "execution_count": 144, + "execution_count": 147, "metadata": {}, "output_type": "execute_result" } @@ -5687,7 +5728,7 @@ }, { "cell_type": "code", - "execution_count": 146, + "execution_count": 149, "id": "9563d49c-54a3-439f-a833-f48c2a070609", "metadata": {}, "outputs": [], @@ -5704,7 +5745,7 @@ }, { "cell_type": "code", - "execution_count": 147, + "execution_count": 150, "id": "ab8c7e3b-f400-4976-ad0d-5f92cbadec02", "metadata": {}, "outputs": [ @@ -5896,42 +5937,42 @@ "\n", "\n", "
\n", - " \n", + " \n", "
\n", - " \n", + " oninput=\"animc28e0e9e1b144da299de29e5ae4bfa26.set_frame(parseInt(this.value));\">\n", "
\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", "
\n", - "
\n", - " \n", - " \n", - " Once\n", + " \n", - " \n", - " Loop\n", + " \n", - " \n", + " \n", "
\n", "
\n", "
\n", @@ -5941,9 +5982,9 @@ " /* Instantiate the Animation class. */\n", " /* The IDs given should match those used in the template above. */\n", " (function() {\n", - " var img_id = \"_anim_img75c15803e4bc421ab6d6396a5ebe956b\";\n", - " var slider_id = \"_anim_slider75c15803e4bc421ab6d6396a5ebe956b\";\n", - " var loop_select_id = \"_anim_loop_select75c15803e4bc421ab6d6396a5ebe956b\";\n", + " var img_id = \"_anim_imgc28e0e9e1b144da299de29e5ae4bfa26\";\n", + " var slider_id = \"_anim_sliderc28e0e9e1b144da299de29e5ae4bfa26\";\n", + " var loop_select_id = \"_anim_loop_selectc28e0e9e1b144da299de29e5ae4bfa26\";\n", " var frames = new Array(1);\n", " \n", " frames[0] = \"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA8AAAALQCAYAAABfdxm0AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90\\\n", @@ -6621,17 +6662,17 @@ " /* set a timeout to make sure all the above elements are created before\n", " the object is initialized. */\n", " setTimeout(function() {\n", - " anim75c15803e4bc421ab6d6396a5ebe956b = new Animation(frames, img_id, slider_id, 200.0,\n", + " animc28e0e9e1b144da299de29e5ae4bfa26 = new Animation(frames, img_id, slider_id, 200.0,\n", " loop_select_id);\n", " }, 0);\n", " })()\n", "\n" ], "text/plain": [ - "" + "" ] }, - "execution_count": 147, + "execution_count": 150, "metadata": {}, "output_type": "execute_result" } @@ -6653,17 +6694,17 @@ }, { "cell_type": "code", - "execution_count": 149, + "execution_count": 152, "id": "75434bc8-35ae-4d8b-b747-01d773472541", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "Puzzle 14.2: 1.893 seconds, answer 6876 ok" + "Puzzle 14.2: 1.869 seconds, answer 6876 ok" ] }, - "execution_count": 149, + "execution_count": 152, "metadata": {}, "output_type": "execute_result" } @@ -6685,7 +6726,7 @@ }, { "cell_type": "code", - "execution_count": 151, + "execution_count": 154, "id": "20be45ec-f6fc-472c-9b49-872b7334528c", "metadata": {}, "outputs": [ @@ -6736,7 +6777,7 @@ }, { "cell_type": "code", - "execution_count": 153, + "execution_count": 156, "id": "248d51c0-424e-4bbb-bbf4-37cdc18313e1", "metadata": {}, "outputs": [], @@ -6772,7 +6813,7 @@ }, { "cell_type": "code", - "execution_count": 154, + "execution_count": 157, "id": "52e93218-6ac2-4cf7-aced-f4f3a3df77b5", "metadata": {}, "outputs": [ @@ -6782,7 +6823,7 @@ "Puzzle 15.1: .025 seconds, answer 1563092 ok" ] }, - "execution_count": 154, + "execution_count": 157, "metadata": {}, "output_type": "execute_result" } @@ -6799,7 +6840,7 @@ "source": [ "### Part 2: What is the sum of all boxes' final GPS coordinates on the double-wide grid?\n", "\n", - "In Part 2, there is another warehouse that is similar to the first, but twice as wide. Each position in the original warehouse is replaced by two copies of the contents, except that the robot is replaced by `@.` and a box, `O`, is replaced by `[]`, indicating the two halves of a double-wide box. The rules for movement and pushing are the same, except that when the robot is moving North or South and is pushing a box, that box, since it is two positions wide, can push *two* boxes if they are lined up right. (Presumably, those two boxes could then push three boxes in the next row, and so on, although the problem description did not explicitly specify that). Finally, the GPS coordinates are taken for the `[` part of the box.\n", + "In Part 2, there is another warehouse that is similar to the first, but twice as wide. Each position in the original warehouse is replaced by two copies of the contents, except that the robot is replaced by `@.` and a box, `O`, is replaced by `[]`, indicating the two halves of a double-wide box. The rules for movement and pushing are the same, except that when the robot is moving North or South and is pushing a box, that box, since it is two positions wide, can push *two* boxes if they are lined up right. (Presumably, those two boxes could then push three boxes in the next row, and so on, although the puzzle description did not explicitly specify that). Finally, the GPS coordinates are taken for the `[` part of the box.\n", "\n", "So this is mostly the same as Part 1, but the criteria of what to move is different. Here are the changes I'll make:\n", "- I'll need to make the double-wide grid, by applying `doublewide` to the original text to get `warehouse2`.\n", @@ -6811,7 +6852,7 @@ }, { "cell_type": "code", - "execution_count": 156, + "execution_count": 159, "id": "2d9afbc1-75b2-479d-81fc-fbab69d25753", "metadata": {}, "outputs": [], @@ -6849,7 +6890,7 @@ }, { "cell_type": "code", - "execution_count": 157, + "execution_count": 160, "id": "7af49bbf-dd10-4221-9096-6f548dec44c0", "metadata": {}, "outputs": [ @@ -6859,7 +6900,7 @@ "Puzzle 15.1: .029 seconds, answer 1563092 ok" ] }, - "execution_count": 157, + "execution_count": 160, "metadata": {}, "output_type": "execute_result" } @@ -6871,17 +6912,17 @@ }, { "cell_type": "code", - "execution_count": 158, + "execution_count": 161, "id": "99246602-a51e-41aa-a7e9-7cdbc8d449ca", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "Puzzle 15.2: .042 seconds, answer 1582688 ok" + "Puzzle 15.2: .041 seconds, answer 1582688 ok" ] }, - "execution_count": 158, + "execution_count": 161, "metadata": {}, "output_type": "execute_result" } @@ -6896,12 +6937,12 @@ "id": "4361f8c3-1d15-441e-9a41-b1ea20707651", "metadata": {}, "source": [ - "I had a frustrating time **debugging** this one; this was by far my worst performance. First I had a silly typo in `moveables`; I was able to fix it after looking at the results on the smaller test problem:" + "I had a frustrating time **debugging** this one; this was by far my worst performance. First I had a silly typo in `moveables`; I was able to fix it after looking at the results on the smaller test puzzle:" ] }, { "cell_type": "code", - "execution_count": 160, + "execution_count": 163, "id": "47c99f0f-ab65-4ae1-a308-42668feacdd5", "metadata": {}, "outputs": [ @@ -6954,7 +6995,7 @@ }, { "cell_type": "code", - "execution_count": 163, + "execution_count": 166, "id": "03255fd5-95d9-4a90-b1bb-abdf6bbf1d85", "metadata": {}, "outputs": [ @@ -6986,6 +7027,10 @@ "id": "74e37bd1-f994-4e98-b6ed-8469ca7f3805", "metadata": {}, "source": [ + "\n", + "\n", + "\n", + "\n", "### Part 1: What is the lowest score a Reindeer could possibly get?\n", "\n", "The race through the maze starts at the `S` position and ends at the `E`. A reindeer is allowed to take a step forward (as long as there isn't a `#` wall there) or to turn 90 degrees right or left. The reindeer is initially facing East. The **score** for a path is one point for every forward step and 1000 points for every turn.\n", @@ -6995,7 +7040,7 @@ }, { "cell_type": "code", - "execution_count": 165, + "execution_count": 168, "id": "e6e4bfba-0170-436a-961b-d4749f2cd66e", "metadata": {}, "outputs": [], @@ -7028,7 +7073,7 @@ }, { "cell_type": "code", - "execution_count": 166, + "execution_count": 169, "id": "4974c14c-86da-4b86-a55c-6ae8781df207", "metadata": {}, "outputs": [ @@ -7038,7 +7083,7 @@ "Puzzle 16.1: .147 seconds, answer 103512 ok" ] }, - "execution_count": 166, + "execution_count": 169, "metadata": {}, "output_type": "execute_result" } @@ -7060,7 +7105,7 @@ }, { "cell_type": "code", - "execution_count": 168, + "execution_count": 171, "id": "692abfea-f9c8-477c-8369-386cdd2a7606", "metadata": {}, "outputs": [], @@ -7092,17 +7137,17 @@ }, { "cell_type": "code", - "execution_count": 169, + "execution_count": 172, "id": "ea9bf9f3-0e6b-4949-a641-6b3db2fd9d32", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "Puzzle 16.2: .855 seconds, answer 554 ok" + "Puzzle 16.2: .852 seconds, answer 554 ok" ] }, - "execution_count": 169, + "execution_count": 172, "metadata": {}, "output_type": "execute_result" } @@ -7124,7 +7169,7 @@ }, { "cell_type": "code", - "execution_count": 171, + "execution_count": 174, "id": "50fd3cb8-0a6f-4edd-b5cb-f7b1e6f9d987", "metadata": {}, "outputs": [ @@ -7149,22 +7194,12 @@ "()\n", "('Program', 2, 4, 1, 7, 7, 5, 0, 3, 4, 4, 1, 7, 5, 5, 3, 0)\n" ] - }, - { - "data": { - "text/plain": [ - "Computer(A=52042868, B=0, C=0, prog=[2, 4, 1, 7, 7, 5, 0, 3, 4, 4, 1, 7, 5, 5, 3, 0])" - ] - }, - "execution_count": 171, - "metadata": {}, - "output_type": "execute_result" } ], "source": [ "Computer = namedtuple('Computer', 'A, B, C, prog')\n", " \n", - "def initialize(information) -> Computer:\n", + "def initialize_computer(information) -> Computer:\n", " \"\"\"Initialize the computer with the debugging information.\"\"\"\n", " kwds = {}\n", " for info in information:\n", @@ -7173,7 +7208,27 @@ " case ['Program', *vals]: kwds['prog'] = vals\n", " return Computer(**kwds)\n", " \n", - "computer = initialize(parse(17, atoms))\n", + "computer = initialize_computer(parse(17, atoms))" + ] + }, + { + "cell_type": "code", + "execution_count": 175, + "id": "c23b8a44-e352-4a30-abe5-3c939ffc743c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Computer(A=52042868, B=0, C=0, prog=[2, 4, 1, 7, 7, 5, 0, 3, 4, 4, 1, 7, 5, 5, 3, 0])" + ] + }, + "execution_count": 175, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ "computer" ] }, @@ -7189,7 +7244,7 @@ }, { "cell_type": "code", - "execution_count": 173, + "execution_count": 177, "id": "d98f88cc-c435-43fc-bcb5-52e6dd70fdb1", "metadata": {}, "outputs": [], @@ -7224,7 +7279,7 @@ }, { "cell_type": "code", - "execution_count": 175, + "execution_count": 179, "id": "860f24e5-92ad-4361-8920-102ebc573598", "metadata": {}, "outputs": [ @@ -7234,7 +7289,7 @@ "Puzzle 17.1: .000 seconds, answer 2,1,0,1,7,2,5,0,3 ok" ] }, - "execution_count": 175, + "execution_count": 179, "metadata": {}, "output_type": "execute_result" } @@ -7251,7 +7306,7 @@ "source": [ "### Part 2: What is the lowest positive initial value for register A that causes the program to output a copy of itself?\n", "\n", - "In Part 2, we find that register A has been corrupted, and we need to restore it to the value that will make the program output a copy of itself (a [Quine](https://en.wikipedia.org/wiki/Quine_%28computing%29)). I was afraid of this! AoC always has a problem where you have to write an interpreter for a program in some obscure language, but then in Part 2 you have to actually understand what the program is doing; you can't use brute force. (I tried brute force up to A=10,000,000 with no luck.)\n", + "In Part 2, we find that register A has been corrupted, and we need to restore it to the value that will make the program output a copy of itself (a [Quine](https://en.wikipedia.org/wiki/Quine_%28computing%29)). I was afraid of this! AoC always has a puzzle where you have to write an interpreter for a program in some obscure language, but then in Part 2 you have to actually understand what the program is doing; you can't use brute force. (I tried brute force up to A=10,000,000 with no luck.)\n", "\n", "To try to understand my program, here it is in pseudocode:\n", "\n", @@ -7278,7 +7333,7 @@ }, { "cell_type": "code", - "execution_count": 177, + "execution_count": 181, "id": "bb745303-dd20-486f-bbdd-7ae77f995c2c", "metadata": {}, "outputs": [], @@ -7290,7 +7345,7 @@ }, { "cell_type": "code", - "execution_count": 178, + "execution_count": 182, "id": "54ac9b9c-70d9-4356-91c5-40fb285634b4", "metadata": {}, "outputs": [ @@ -7300,7 +7355,7 @@ "[2, 4, 1, 7, 7, 5, 0, 3, 4, 4, 1, 7, 5, 5, 3, 0]" ] }, - "execution_count": 178, + "execution_count": 182, "metadata": {}, "output_type": "execute_result" } @@ -7311,7 +7366,7 @@ }, { "cell_type": "code", - "execution_count": 179, + "execution_count": 183, "id": "36d89d9a-c8fc-41be-820b-cb13e40793c0", "metadata": {}, "outputs": [ @@ -7321,7 +7376,7 @@ "[0]" ] }, - "execution_count": 179, + "execution_count": 183, "metadata": {}, "output_type": "execute_result" } @@ -7332,7 +7387,7 @@ }, { "cell_type": "code", - "execution_count": 180, + "execution_count": 184, "id": "892fef38-5f9b-4370-a242-f7a10df5487b", "metadata": {}, "outputs": [ @@ -7342,7 +7397,7 @@ "[3, 0]" ] }, - "execution_count": 180, + "execution_count": 184, "metadata": {}, "output_type": "execute_result" } @@ -7353,7 +7408,7 @@ }, { "cell_type": "code", - "execution_count": 181, + "execution_count": 185, "id": "b3d11d5e-30c2-419f-bc11-3f26fbdddfbb", "metadata": {}, "outputs": [ @@ -7363,7 +7418,7 @@ "[5, 3, 0]" ] }, - "execution_count": 181, + "execution_count": 185, "metadata": {}, "output_type": "execute_result" } @@ -7388,17 +7443,17 @@ }, { "cell_type": "code", - "execution_count": 183, + "execution_count": 187, "id": "a49c6de9-4e6b-47e5-bcf0-2972a95c1af3", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "Puzzle 17.2: .024 seconds, answer 267265166222235 ok" + "Puzzle 17.2: .023 seconds, answer 267265166222235 ok" ] }, - "execution_count": 183, + "execution_count": 187, "metadata": {}, "output_type": "execute_result" } @@ -7429,7 +7484,7 @@ }, { "cell_type": "code", - "execution_count": 185, + "execution_count": 189, "id": "d14e1966-2feb-4553-9a0a-12595ef4f7d7", "metadata": {}, "outputs": [ @@ -7473,16 +7528,18 @@ "id": "1229cec7-a456-4dd6-a668-8a00591c63f7", "metadata": {}, "source": [ + "\n", + "\n", "### Part 1: What is the minimum number of steps needed to reach the exit?\n", "\n", "When a byte falls it creates a barrier. Our task is to find a path that avoids the barriers, from the start in the upper left to the exit in the lower right of a 71 x 71 grid that is the memory board. \n", "\n", - "This is another search problem, like the maze in Day 16, but without the complications (e.g. costs for turning right or left). The problem description for today says that we should first consider just the first kilobyte (1024 falling bytes), and I was worried that if I just hand those points to my `Grid` class, it wouldn't cover the whole 71 x 71 grid. Therefore, I created a grid with empty spaces, and then updated with the falling bytes. The function `memory_path` returns a path, and we can then ask for its length to get the answer." + "This is another search puzzle, like the maze in Day 16, but without the complications (e.g. costs for turning right or left). The puzzle description for today says that we should first consider just the first kilobyte (1024 falling bytes), and I was worried that if I just hand those points to my `Grid` class, it wouldn't cover the whole 71 x 71 grid. Therefore, I created a grid with empty spaces, and then updated with the falling bytes. The function `memory_path` returns a path, and we can then ask for its length to get the answer." ] }, { "cell_type": "code", - "execution_count": 187, + "execution_count": 191, "id": "83af4751-38c9-4830-a2fa-78515b59bc97", "metadata": {}, "outputs": [], @@ -7497,7 +7554,7 @@ }, { "cell_type": "code", - "execution_count": 188, + "execution_count": 192, "id": "29da25e2-f3c2-43e3-8769-1d4fcecb807b", "metadata": {}, "outputs": [ @@ -7507,7 +7564,7 @@ "Puzzle 18.1: .014 seconds, answer 344 ok" ] }, - "execution_count": 188, + "execution_count": 192, "metadata": {}, "output_type": "execute_result" } @@ -7531,7 +7588,7 @@ }, { "cell_type": "code", - "execution_count": 190, + "execution_count": 194, "id": "4c0a8dcb-c3af-45e7-9273-8776e8c3ea1d", "metadata": {}, "outputs": [], @@ -7550,17 +7607,17 @@ }, { "cell_type": "code", - "execution_count": 191, + "execution_count": 195, "id": "22371144-5d51-440a-918f-a63de73b13ad", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "Puzzle 18.2: .033 seconds, answer 46,18 ok" + "Puzzle 18.2: .032 seconds, answer 46,18 ok" ] }, - "execution_count": 191, + "execution_count": 195, "metadata": {}, "output_type": "execute_result" } @@ -7592,7 +7649,7 @@ }, { "cell_type": "code", - "execution_count": 194, + "execution_count": 198, "id": "689df92b-92d7-44e6-8b4e-d67bf3f153df", "metadata": {}, "outputs": [ @@ -7636,7 +7693,7 @@ }, { "cell_type": "code", - "execution_count": 196, + "execution_count": 200, "id": "e0b96545-5f60-4c8e-9a0f-c77608c19128", "metadata": {}, "outputs": [], @@ -7650,17 +7707,17 @@ }, { "cell_type": "code", - "execution_count": 197, + "execution_count": 201, "id": "3c3fba1b-d3e5-494e-aad0-42ca0566ae1f", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "Puzzle 19.1: .040 seconds, answer 242 ok" + "Puzzle 19.1: .039 seconds, answer 242 ok" ] }, - "execution_count": 197, + "execution_count": 201, "metadata": {}, "output_type": "execute_result" } @@ -7682,7 +7739,7 @@ }, { "cell_type": "code", - "execution_count": 199, + "execution_count": 203, "id": "77d4fae1-d506-4733-9bb2-467619012f97", "metadata": {}, "outputs": [], @@ -7697,17 +7754,17 @@ }, { "cell_type": "code", - "execution_count": 200, + "execution_count": 204, "id": "14116eca-0b0f-484a-a169-9726e4ac7fbf", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "Puzzle 19.2: .183 seconds, answer 595975512785325 ok" + "Puzzle 19.2: .187 seconds, answer 595975512785325 ok" ] }, - "execution_count": 200, + "execution_count": 204, "metadata": {}, "output_type": "execute_result" } @@ -7727,7 +7784,7 @@ }, { "cell_type": "code", - "execution_count": 202, + "execution_count": 206, "id": "f992b197-be14-47dc-8678-10cc63a1afd7", "metadata": {}, "outputs": [], @@ -7746,7 +7803,7 @@ }, { "cell_type": "code", - "execution_count": 204, + "execution_count": 208, "id": "57296f79-d0a0-4de3-bb1c-b3ed84ba1a59", "metadata": {}, "outputs": [ @@ -7756,7 +7813,7 @@ "Puzzle 19.1: .004 seconds, answer 242 ok" ] }, - "execution_count": 204, + "execution_count": 208, "metadata": {}, "output_type": "execute_result" } @@ -7778,12 +7835,12 @@ "source": [ "# [Day 20](https://adventofcode.com/2024/day/20): Race Condition\n", "\n", - "Yet another problem with a grid, this one depicting a racetrack:" + "Yet another puzzle with a grid, this one depicting a racetrack:" ] }, { "cell_type": "code", - "execution_count": 206, + "execution_count": 210, "id": "156dcbf7-79ec-41a7-a9f2-397a827b9856", "metadata": {}, "outputs": [ @@ -7815,18 +7872,18 @@ "id": "57918fd0-bb72-4c5e-8e1f-3bcf164a1d72", "metadata": {}, "source": [ - "### How many cheats would save you at least 100 picoseconds?\n", + "### Part 1: How many cheats would save you at least 100 picoseconds?\n", "\n", "We are told that the grid depicts a single path from start (`S`) to end (`E`). Each step (in one of the four cardinal directions) takes one picosecond. But you are allowed to cheat once during the race by going through a wall: you can take two steps where the first step is into a wall and the second is back on the track. We are asked how many such cheats would save 100 picoseconds or more.\n", "\n", - "This is an all-paths-to-the-goal problem, which should make you think [Dijkstra's algorithm](https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm). My function `dijkstra` will return a dict of `{(x, y): distance_to_end}`. It works by maintaining a queue, `Q` of points to be considered, and one at a time popping a point off the queue, and for each neighbor that is not a wall, add the neighbor to the queue and record the distance to the neighbor as being one more than the distance to the point in the dict `D`. Since we are told the grid is single-path, we don't have to worry about updating entries in `D` for a second path to a point.\n", + "This is an all-paths-to-the-goal puzzle, which should make you think [Dijkstra's algorithm](https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm). My function `dijkstra` will return a dict of `{(x, y): distance_to_end}`. It works by maintaining a queue, `Q` of points to be considered, and one at a time popping a point off the queue, and for each neighbor that is not a wall, add the neighbor to the queue and record the distance to the neighbor as being one more than the distance to the point in the dict `D`. Since we are told the grid is single-path, we don't have to worry about updating entries in `D` for a second path to a point.\n", "\n", "Then my function `cheats` yields all `(start_position, end_position, time_saved)` tuples where the time saved is at least the given lower bound." ] }, { "cell_type": "code", - "execution_count": 208, + "execution_count": 212, "id": "4c4ef05c-b548-49f9-b092-847e9752e745", "metadata": {}, "outputs": [], @@ -7856,17 +7913,17 @@ }, { "cell_type": "code", - "execution_count": 209, + "execution_count": 213, "id": "1bbd8b72-c503-4384-aaea-a5bed45a4491", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "Puzzle 20.1: .029 seconds, answer 1343 ok" + "Puzzle 20.1: .028 seconds, answer 1343 ok" ] }, - "execution_count": 209, + "execution_count": 213, "metadata": {}, "output_type": "execute_result" } @@ -7881,26 +7938,29 @@ "id": "0f86c3a4-47e7-4731-8acf-9133b3e41838", "metadata": {}, "source": [ - "At first I had a puzzling **bug**. I had a lot of confidence in my code, so I addressed it by carefully re-reading the problem description. Then I realized my mistake: I didn't count the 2 picoseconds of cheating as part of the total time. I fixed that by inserting the \"`-2`\" in the last line of `cheats`. In the process of carefully re-reading, I realized that the path through the grid is a single path; I didn't need a queue of points in `dijkstra`; the queue will always be just one point. But changing it wouldn't have a big effect on efficiency, so I'll keep it as is.\n", + "At first I had a puzzling **bug**. I had a lot of confidence in my code, so I addressed it by carefully re-reading the puzzle description. Then I realized my mistake: I didn't count the 2 picoseconds of cheating as part of the total time. I fixed that by inserting the \"`-2`\" in the last line of `cheats`. In the process of carefully re-reading, I realized that the path through the grid is a single path; I didn't need a queue of points in `dijkstra`; the queue will always be just one point. But changing it wouldn't have a big effect on efficiency, so I'll keep it as is.\n", "\n", - "### How many big cheats would save you at least 100 picoseconds?\n", + "\n", + "\n", + "\n", + "### Part 2: How many big cheats would save you at least 100 picoseconds?\n", "\n", "In Part 2 you can use a much bigger cheat, of up to 20 picoseconds. (But you can still only use one cheat.) I'll tackle this by looking at all points in a neighborhood of radius 20 from each starting point on the path." ] }, { "cell_type": "code", - "execution_count": 211, + "execution_count": 215, "id": "d370e24c-9b82-4415-82a5-7afe2be17654", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "Puzzle 20.2: .764 seconds, answer 982891 ok" + "Puzzle 20.2: .770 seconds, answer 982891 ok" ] }, - "execution_count": 211, + "execution_count": 215, "metadata": {}, "output_type": "execute_result" } @@ -7935,7 +7995,7 @@ }, { "cell_type": "code", - "execution_count": 213, + "execution_count": 217, "id": "332e9c72-db9a-4b90-a649-45f7bf955e84", "metadata": {}, "outputs": [ @@ -7945,7 +8005,7 @@ "Puzzle 20.1: .023 seconds, answer 1343 ok" ] }, - "execution_count": 213, + "execution_count": 217, "metadata": {}, "output_type": "execute_result" } @@ -7957,17 +8017,1166 @@ }, { "cell_type": "markdown", - "id": "c3317844-2b4a-4756-8a59-b765aa467445", + "id": "6f136dce-d68b-47b1-9b2e-31293f73ed6c", "metadata": {}, "source": [ - "# Summary\n", + "# [Day 21](https://adventofcode.com/2024/day/21): Keypad Conundrum\n", "\n", - "So far, I've solved all the puzzles. The median run time is about 10 milliseconds, but three problems take over a second." + "Today's puzzle is simple: unlock the door to Santa's starship by entering some lock codes on a **numeric keypad**. Here are the lock codes:" ] }, { "cell_type": "code", - "execution_count": 215, + "execution_count": 219, + "id": "9d0eea9f-9d8f-4410-97ec-13d8b136925b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "────────────────────────────────────────────────────────────────────────────────────────────────────\n", + "Puzzle input ➜ 5 strs:\n", + "────────────────────────────────────────────────────────────────────────────────────────────────────\n", + "839A\n", + "169A\n", + "579A\n", + "670A\n", + "638A\n" + ] + } + ], + "source": [ + "codes = parse(21)" + ] + }, + { + "cell_type": "markdown", + "id": "adeabd3b-f534-4457-ab7e-3f057c34600f", + "metadata": {}, + "source": [ + "### Part 1: What is the sum of the complexities of the five codes on your list?\n", + "\n", + "The problem is that the numeric keypad is inaccessible, so we have to get a robot to press the buttons, and we have to command the robot's arm by pressing some buttons on a **remote control directional keypad**: arrow keys to move the arm, and the \"`A`\" (activate) button to make the arm press the button it is currently pointing at. Here are the two keypads:\n", + "\n", + " +---+---+---+ +---+---+\n", + " | 7 | 8 | 9 | | ^ | A |\n", + " +---+---+---+ +---+---+---+\n", + " | 4 | 5 | 6 | | < | v | > |\n", + " +---+---+---+ +---+---+---+\n", + " | 1 | 2 | 3 |\n", + " +---+---+---+\n", + " | 0 | A |\n", + " +---+---+\n", + " \n", + "Long story short: it turns out the directional keypad is *also* inaccessible, and we actually need **three robots**, each pressing buttons on the next keypad, so there are four levels of button pressing all together:\n", + "\n", + "- Level 4: You press some buttons on Robot 3's directional keypad (e.g. \">\"...)\n", + "- Level 2: Robot 2 presses some buttons on Robot 1's directional keypad (e.g. \"'])" + ] + }, + { + "cell_type": "code", + "execution_count": 222, + "id": "c915ada2-9dfe-42fe-bd08-855ff5d0e837", + "metadata": {}, + "outputs": [], + "source": [ + "def possible_paths(key1, key2, keypad) -> Iterable[str]:\n", + " \"\"\"Should we try, e.g. \">>^\" or \"^>>\", or both, to get from pos1 to pos2 on keypad?\"\"\"\n", + " pos1, pos2 = keypad[key1], keypad[key2]\n", + " (dx, dy) = sub(pos2, pos1)\n", + " horizontal = abs(dx) * ('>' if dx > 0 else '<')\n", + " vertical = abs(dy) * ('v' if dy > 0 else '^')\n", + " if add(pos1, (dx, 0)) != keypad.avoid:\n", + " yield horizontal + vertical + 'A'\n", + " if add(pos1, (0, dy)) != keypad.avoid: \n", + " yield vertical + horizontal + 'A'" + ] + }, + { + "cell_type": "code", + "execution_count": 223, + "id": "391f6120-6373-43f2-b697-585c90e45e54", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['>>vvA', 'vv>>A']" + ] + }, + "execution_count": 223, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "list(possible_paths('7', '3', numeric_keypad))" + ] + }, + { + "cell_type": "code", + "execution_count": 224, + "id": "aec8849e-5fe7-47ab-9569-7fcf4f3ede17", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['>>vvvA']" + ] + }, + "execution_count": 224, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "list(possible_paths('7', 'A', numeric_keypad))" + ] + }, + { + "cell_type": "code", + "execution_count": 225, + "id": "9f5fe5bb-de7c-435d-a912-d80a6c4e5de2", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'ok'" + ] + }, + "execution_count": 225, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def min_keypresses(code, num_remotes, remote_keypad=remote_keypad, numeric_keypad=numeric_keypad):\n", + " \"\"\"The number of keypresses to type `code`, going through `num_remotes`.\"\"\"\n", + " def total_presses(level: int, keys: str) -> int:\n", + " return sum(cached_lengths[(level, key1, key2)] \n", + " for key1, key2 in sliding_window('A' + keys, 2))\n", + " ## Initializing the cached_lengths[level, position1, position2] dict\n", + " cached_lengths = {(0, key1, key2): 1 for key1 in remote_keypad for key2 in remote_keypad}\n", + " for level in range(1, num_remotes + 1):\n", + " keypad = numeric_keypad if level == num_remotes else remote_keypad\n", + " for key1 in keypad:\n", + " for key2 in keypad:\n", + " paths = possible_paths(key1, key2, keypad)\n", + " cached_lengths[(level, key1, key2)] = min(total_presses(level - 1, path) for path in paths)\n", + " ## Go through the cache, finding the shortest total number of presses\n", + " return total_presses(level, code)\n", + "\n", + "part_1 = sum(min_keypresses(code, 3) * int(code[:-1]) for code in codes)\n", + "assert part_1 == 205160\n", + "part_2 = sum(min_keypresses(code, 26) * int(code[:-1]) for code in codes)\n", + "assert part_2 == 252473394928452\n", + "'ok'" + ] + }, + { + "cell_type": "code", + "execution_count": 226, + "id": "1e2e8c59-744f-4f36-9251-c8ea5fd60aed", + "metadata": {}, + "outputs": [], + "source": [ + "def complexity(code, presses: int): \n", + " \"\"\"The integer part of the code times the number of keypresses.\"\"\"\n", + " return int(cat(digits(code))) * presses" + ] + }, + { + "cell_type": "code", + "execution_count": 227, + "id": "cfd67700-2b5c-4f2f-a631-382816a30ebf", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'ok'" + ] + }, + "execution_count": 227, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def min_keypresses(code: str, levels: int, remote_keypad=remote_keypad, numeric_keypad=numeric_keypad):\n", + " \"\"\"The number of keypresses to type `code`, going through `levels`.\"\"\"\n", + " def total_presses(keys: str, level: int) -> int:\n", + " return sum(cached_lengths[(level, key1, key2)] \n", + " for key1, key2 in sliding_window('A' + keys, 2))\n", + " ## Initializing the cached_lengths[level, position1, position2] dict\n", + " cached_lengths = {(0, key1, key2): 1 for key1 in remote_keypad for key2 in remote_keypad}\n", + " for level in range(1, levels):\n", + " keypad = numeric_keypad if level == levels - 1 else remote_keypad\n", + " for key1 in keypad:\n", + " for key2 in keypad:\n", + " paths = possible_paths(key1, key2, keypad)\n", + " cached_lengths[(level, key1, key2)] = min(total_presses(path, level - 1) for path in paths)\n", + " ## Go through the cache, finding the shortest total number of presses\n", + " return total_presses(code, levels - 1)\n", + "\n", + "part_1 = sum(min_keypresses(code, 4) * int(code[:-1]) for code in codes)\n", + "assert part_1 == 205160\n", + "part_2 = sum(min_keypresses(code, 27) * int(code[:-1]) for code in codes)\n", + "assert part_2 == 252473394928452\n", + "'ok'" + ] + }, + { + "cell_type": "code", + "execution_count": 228, + "id": "704ade67-31e6-4f39-b48c-0a7185c42c48", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'ok'" + ] + }, + "execution_count": 228, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def min_keypresses(code: str, levels: int) -> int:\n", + " \"\"\"The number of keypresses to type `code`, going through `levels`.\"\"\"\n", + " def total_presses(keys: str, level: int) -> int:\n", + " return sum(cached_lengths[(level, key1, key2)] \n", + " for key1, key2 in sliding_window('A' + keys, 2))\n", + " ## Initializing the cached_lengths[level, position1, position2] dict\n", + " cached_lengths = {(0, key1, key2): 1 for key1 in remote_keypad for key2 in remote_keypad}\n", + " for level in range(1, levels):\n", + " keypad = numeric_keypad if level == levels - 1 else remote_keypad\n", + " for key1 in keypad:\n", + " for key2 in keypad:\n", + " paths = possible_paths(key1, key2, keypad)\n", + " cached_lengths[(level, key1, key2)] = min(total_presses(path, level - 1) for path in paths)\n", + " ## Go through the cache, finding the shortest total number of presses\n", + " return total_presses(code, levels - 1)\n", + "\n", + "part_1 = sum(min_keypresses(code, 4) * int(code[:-1]) for code in codes)\n", + "assert part_1 == 205160\n", + "part_2 = sum(min_keypresses(code, 27) * int(code[:-1]) for code in codes)\n", + "assert part_2 == 252473394928452\n", + "'ok'" + ] + }, + { + "cell_type": "code", + "execution_count": 229, + "id": "393a8c6b-6a1c-4495-aad5-92f48711fbf1", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Puzzle 21.1: .002 seconds, answer 205160 ok" + ] + }, + "execution_count": 229, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "answer(21.1, 205160, lambda:\n", + " sum(complexity(code, min_keypresses(code, 4)) for code in codes))" + ] + }, + { + "cell_type": "code", + "execution_count": 230, + "id": "563c5959-692d-4368-8c0b-469fa0142678", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Puzzle 21.2: .008 seconds, answer 252473394928452 ok" + ] + }, + "execution_count": 230, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "answer(21.2, 252473394928452, lambda:\n", + " sum(complexity(code, min_keypresses(code, 27)) for code in codes))" + ] + }, + { + "cell_type": "markdown", + "id": "ab5218d4-3c0c-47d8-a105-d0dbced92cd4", + "metadata": {}, + "source": [ + "# [Day 22](https://adventofcode.com/2024/day/22): Monkey Market\n", + "\n", + "Today we are introduced to the monkey exchange market. The monkeys are **buyers** of hiding spots, offering **bananas** as payment. We need bananas, and fortunately we have many hiding spots to sell. Each monkey has a **secret number** (today's input):" + ] + }, + { + "cell_type": "code", + "execution_count": 232, + "id": "2c1e7612-4ec5-4ce1-b591-5c3a14f8ea61", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "────────────────────────────────────────────────────────────────────────────────────────────────────\n", + "Puzzle input ➜ 1685 strs:\n", + "────────────────────────────────────────────────────────────────────────────────────────────────────\n", + "9524047\n", + "7448470\n", + "4573369\n", + "4782321\n", + "14221799\n", + "5609429\n", + "16652319\n", + "2515459\n", + "...\n", + "────────────────────────────────────────────────────────────────────────────────────────────────────\n", + "Parsed representation ➜ 1685 ints:\n", + "────────────────────────────────────────────────────────────────────────────────────────────────────\n", + "9524047\n", + "7448470\n", + "4573369\n", + "4782321\n", + "14221799\n", + "5609429\n", + "16652319\n", + "2515459\n", + "...\n" + ] + } + ], + "source": [ + "secrets = parse(22, int)" + ] + }, + { + "cell_type": "markdown", + "id": "b7f4cf5f-0e07-4e61-88ce-c3dfd6e3f0c8", + "metadata": {}, + "source": [ + "### Part 1: What is the sum of the 2000th secret number generated by each buyer?\n", + "\n", + "Each buyer's secret number changes on each time step. There is a detailed process for generating the next secret number from the previous secret number, involving dividing, multiplying, XOR-ing, and taking modulos. We are asked for the sum of each buyer's 2000th next secret number. Easy enough:" + ] + }, + { + "cell_type": "code", + "execution_count": 234, + "id": "3807adbc-261a-4108-88ba-6e2b061526b5", + "metadata": {}, + "outputs": [], + "source": [ + "def nth_secret(secret: int, n=2000) -> int:\n", + " \"\"\"Compute the `nth` next secret number after this secret number.\"\"\"\n", + " for _ in range(n):\n", + " secret ^= (secret * 64) % 16777216\n", + " secret ^= (secret // 32) % 16777216\n", + " secret ^= (secret * 2048) % 16777216\n", + " return secret" + ] + }, + { + "cell_type": "code", + "execution_count": 235, + "id": "fce754cd-f6da-4e7d-b406-3314c9c28ca8", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Puzzle 22.1: .321 seconds, answer 14273043166 ok" + ] + }, + "execution_count": 235, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "answer(22.1, 14273043166, lambda:\n", + " sum(map(nth_secret, secrets)))" + ] + }, + { + "cell_type": "markdown", + "id": "80fea986-b9f3-4184-94b5-5fda551ce115", + "metadata": {}, + "source": [ + "I note that all the the integer constants in `nth_new_secret` are powers of 2; I wonder if I will have to analyze exactly what is going on with these bit operations for Part 2. \n", + "\n", + "### Part 2: What is the most bananas you can get?\n", + "\n", + "Knowing the secret numbers is not the whole story. Each buyer monkey has a **price** in bananas that they are offering at each time step. The price is the ones digit of their secret number (i.e., the secret number mod 10). Each monkey wants to buy one hiding spot. We would like to wait for the highest price from each monkey, but we don't speak monkeese. We do have a translator who speaks a few words, put unfortunately, the translator doesn't know enough to help us sell as soon as the price hits 9 (or 8, or whatever). The translator has only the ability help as follows:\n", + "- We tell the translator a watch for a sequence of four deltas in price from one time step to the next for the first monkey.\n", + " - For example, watch for the deltas (**+1, -2, 0, +3**) in the price timeline.\n", + "- As soon as that sequence of 4 deltas appears in the monkey's price history, we sell a hiding spot for the current price.\n", + " - For example, if the prices are (3, 3, 3, 5, 6, 4, 4, 7, ...) then the deltas are (0, 0, +2, **+1, -2, 0, +3**, ...) and we sell for 7 bananas.\n", + "- The translator simultaneously does the same for all the other monkeys.\n", + "\n", + "Our task is to get the most possible bananas under these constraints. \n", + "\n", + "Can we brute-force it? Each delta is between -9 and +9, so the number of possible 4-tuples of deltas is 194 or about 100,000; a small number. However, if for each possible 4-tuple of deltas I looked at all 2000 steps of all 1685 monkeys, then 194 * 2000 * 1685 > 400 billion is a large number. So instead I'll do this:\n", + "\n", + " For each monkey:\n", + " For each 4-tuple of price deltas in the monkey's price timeline:\n", + " If this is the first time this 4-tuple appeared in this monkey's sequence of deltas:\n", + " then increment the total number of bananas for this 4-tuple by the current price\n", + " Finally, take the maximum of the numbers of bananas, across all the recorded 4-tuples\n", + "\n", + "Some details:\n", + "- A `defaultdict(int)` is a great data structure for the 4-tuple to bananas mapping.\n", + "- A `deque` with `maxlen=4` is a good data structure for keeping the 4-tuple; just append to it.\n", + "- I now call `nth_secret(secret, 1)`, so I wish I had made a separate function for that in Part 1; it would probably speed things up by a few percent." + ] + }, + { + "cell_type": "code", + "execution_count": 237, + "id": "cbbb4793-b13d-4a95-ba03-673b3ebaa229", + "metadata": {}, + "outputs": [], + "source": [ + "Deltas = Tuple[int, int, int, int]\n", + "\n", + "def total_bananas(secrets: Ints, n=2000) -> Dict[Deltas, int]:\n", + " \"\"\"Pairs of {delta-4-tuple: total_bananas_gained} over all secret numbers,\n", + " for the first `n` 4-tuples of deltas.\"\"\"\n", + " bananas = defaultdict(int) # {deltas: total_bananas}\n", + " for secret in secrets: # For each monkey ...\n", + " for deltas, price in price_timeline(secret, n).items():\n", + " bananas[deltas] += price\n", + " return bananas\n", + " \n", + "def price_timeline(secret, n=2000) -> Dict[Deltas, int]:\n", + " \"\"\"Each {delta-4-tuple: price} in the timeline of this secret number for `n` steps.\"\"\"\n", + " timeline = {} # {delta-4-tuple: price} pairs\n", + " deltas = deque(maxlen=4) # The 4 most recent price deltas \n", + " price = secret % 10 # Initial price\n", + " for _ in range(n):\n", + " secret = nth_secret(secret, 1)\n", + " price, previous_price = secret % 10, price\n", + " deltas.append(price - previous_price)\n", + " D = tuple(deltas)\n", + " if len(D) == 4 and D not in timeline:\n", + " timeline[D] = price\n", + " return timeline" + ] + }, + { + "cell_type": "code", + "execution_count": 238, + "id": "34eaf7fe-fe9b-4858-b1e8-e4c3713ea093", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Puzzle 22.2: 1.265 seconds, answer 1667 ok" + ] + }, + "execution_count": 238, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "answer(22.2, 1667, lambda:\n", + " max(total_bananas(secrets).values()))" + ] + }, + { + "cell_type": "markdown", + "id": "bfc067a9-e6de-4390-bf78-3da283d68465", + "metadata": {}, + "source": [ + "I'm surprised the answer is so low; less than one banana per monkey. And yet it is the correct answer. Let's investigate. First, what is the best 4-tuple of deltas?" + ] + }, + { + "cell_type": "code", + "execution_count": 240, + "id": "a2da8c78-d240-4c52-a83a-f3599f4f69a4", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(-3, 2, -1, 2)" + ] + }, + "execution_count": 240, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bananas = total_bananas(secrets)\n", + "best = max(bananas, key=bananas.get) \n", + "best" + ] + }, + { + "cell_type": "markdown", + "id": "1e1c48b3-bfff-4e5d-8f6f-9c9fff5f6baa", + "metadata": {}, + "source": [ + "Now, how many monkeys had this in their timeline, and what prices did each buy at? What was the mean price?" + ] + }, + { + "cell_type": "code", + "execution_count": 242, + "id": "9de3f365-c546-494a-a18c-2ae58d88792b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "271 monkeys paid 1667 total at prices: {0: 0, 1: 0, 2: 0, 3: 43, 4: 34, 5: 33, 6: 32, 7: 37, 8: 42, 9: 50}, mean: 6.2\n" + ] + } + ], + "source": [ + "prices = [p for secret in secrets for d, p in price_timeline(secret).items() if d == best]\n", + "histo = {p: prices.count(p) for p in range(10)}\n", + "print(f'{len(prices)} monkeys paid {sum(prices)} total at prices: {histo}, mean: {mean(prices):3.1f}')" + ] + }, + { + "cell_type": "markdown", + "id": "bd6330b8-9ddd-4d58-9314-83b47cb2fb78", + "metadata": {}, + "source": [ + "# [Day 23](https://adventofcode.com/2024/day/23): LAN Party\n", + "\n", + "We see that there is a LAN party scheduled for today. Our input is a map of bidirectional connections between computers:" + ] + }, + { + "cell_type": "code", + "execution_count": 244, + "id": "85f5b145-8c5e-448c-8b45-ad5750252ff2", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "────────────────────────────────────────────────────────────────────────────────────────────────────\n", + "Puzzle input ➜ 3380 strs:\n", + "────────────────────────────────────────────────────────────────────────────────────────────────────\n", + "ht-nz\n", + "pt-uj\n", + "nr-me\n", + "qq-up\n", + "zr-pa\n", + "ly-wv\n", + "ei-ry\n", + "sm-md\n", + "...\n", + "────────────────────────────────────────────────────────────────────────────────────────────────────\n", + "Parsed representation ➜ 3380 tuples:\n", + "────────────────────────────────────────────────────────────────────────────────────────────────────\n", + "('ht', 'nz')\n", + "('pt', 'uj')\n", + "('nr', 'me')\n", + "('qq', 'up')\n", + "('zr', 'pa')\n", + "('ly', 'wv')\n", + "('ei', 'ry')\n", + "('sm', 'md')\n", + "...\n" + ] + } + ], + "source": [ + "connections = parse(23, atoms)" + ] + }, + { + "cell_type": "markdown", + "id": "a22e4214-736c-477a-810c-e77fcd68f1b4", + "metadata": {}, + "source": [ + "### Part 1: Find all the sets of three inter-connected computers. How many contain at least one computer with a name that starts with t?\n", + "\n", + "We're asked to find three inter-connected computers, e.g., three computers A, B, C, such that A-B, B-C, and C-A are all in the list of connections (but connections are bidirectional, so either C-A or A-C could be in the input). A set where every node is connected to every other is called a **clique**. I'll represent the network **graph** as a dict of the form `{A: {B, C}}` to denote that node `A` is connected to `B` and `C`. The function `bidrected_graph` forms a graph from an iterable of (A, B) connections. Then `triples_starting_with` finds all cliques of size 3, where at least one of them starts with the given letter. The answer for Part 1 is then the number of triples that start with `t`." + ] + }, + { + "cell_type": "code", + "execution_count": 246, + "id": "289d2325-1e58-41f5-b4b2-b90ae26e7887", + "metadata": {}, + "outputs": [], + "source": [ + "Clique = set\n", + "\n", + "def bidirected_graph(connections) -> dict:\n", + " \"\"\"A graph where, e.g., {A: {B, C}} means A is connected to B and C.\"\"\"\n", + " graph = defaultdict(set)\n", + " for (A, B) in connections:\n", + " graph[A].add(B)\n", + " graph[B].add(A)\n", + " return graph\n", + "\n", + "def triples_starting_with(graph, start='') -> Set[Clique]:\n", + " \"\"\"All cliques of 3 nodes, where one starts with `start`.\"\"\"\n", + " return {frozenset((A, B, C)) \n", + " for A in graph if A.startswith(start)\n", + " for B in graph[A]\n", + " for C in graph[B] if C in graph[A]}" + ] + }, + { + "cell_type": "code", + "execution_count": 247, + "id": "6425577d-4ca9-45de-9698-cd9b026f7ce6", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Puzzle 23.1: .001 seconds, answer 1170 ok" + ] + }, + "execution_count": 247, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "answer(23.1, 1170, lambda:\n", + " len(triples_starting_with(bidirected_graph(connections), 't')))" + ] + }, + { + "cell_type": "markdown", + "id": "af420aeb-314b-4750-a424-7daad0ffdd62", + "metadata": {}, + "source": [ + "### Part 2: What is the password to get into the LAN party?\n", + "\n", + "In Part 2 we learn two important facts. First, the LAN party is the largest **clique** in the graph. Second, the password is formed by listing the names of all the computers in the LAN party in alphabetical order, and separating them by commas. The function `maximum_clique` finds the largest clique; and `password` creates the password. The function `expand_clique` takes a node, a graph, and a partially-formed clique. It adds the node to the clique, and recursively looks for and adds any other nodes that should be added, finally returning the clique." + ] + }, + { + "cell_type": "code", + "execution_count": 249, + "id": "88811073-22b2-4378-afa5-9d38462c63b5", + "metadata": {}, + "outputs": [], + "source": [ + "def maximum_clique(graph) -> Clique: \n", + " \"\"\"The clique in `graph` with the most members.\"\"\"\n", + " return max((expand_clique(A, graph) for A in graph), key=len)\n", + "\n", + "def password(names) -> str: return ','.join(sorted(names))\n", + " \n", + "def expand_clique(A, graph, clique=None) -> Clique:\n", + " \"\"\"The largest clique in the graph that has A as a member.\"\"\"\n", + " if clique is None: clique = set() # If clique wasn't started yet, start it here\n", + " clique.add(A)\n", + " for B in graph[A]:\n", + " if B not in clique and clique.issubset(graph[B]):\n", + " expand_clique(B, graph, clique)\n", + " return clique" + ] + }, + { + "cell_type": "code", + "execution_count": 250, + "id": "0b5f08ac-18e2-4933-9737-cdbc842c5809", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Puzzle 23.2: .003 seconds, answer bo,dd,eq,ik,lo,lu,ph,ro,rr,rw,uo,wx,yg ok" + ] + }, + "execution_count": 250, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "answer(23.2, 'bo,dd,eq,ik,lo,lu,ph,ro,rr,rw,uo,wx,yg', lambda:\n", + " password(maximum_clique(bidirected_graph(connections))))" + ] + }, + { + "cell_type": "markdown", + "id": "88d8883e-922a-4d08-bbec-a01d5bf225ef", + "metadata": {}, + "source": [ + "# [Day 24](https://adventofcode.com/2024/day/24): Crossed Wire\n", + "\n", + "Today we are trying to help the historian to debug a malfunctioning monitoring device, which consistes of some wires and logic gates. The day's input described the signals (0 or 1) on some input wires, and the gates, which each have two input wires and one output wire, and a Boolean operator. There are two types of inputs: an input wire is described with itrs name and its bit value: `x00: 1`, and a logic gate is described with the two input wire names, the operation, and the output wire name: `x00 AND y00 -> z00`.\n", + "\n", + "I'll parse each line as a sequence of atoms, and assemble them into a `Device`, a subclass of `dict` where the keys are wire names and the values are either bits or gates." + ] + }, + { + "cell_type": "code", + "execution_count": 252, + "id": "47421581-71df-4c72-a62e-c40d0596fdbb", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "────────────────────────────────────────────────────────────────────────────────────────────────────\n", + "Puzzle input ➜ 313 strs:\n", + "────────────────────────────────────────────────────────────────────────────────────────────────────\n", + "x00: 1\n", + "x01: 1\n", + "x02: 0\n", + "x03: 0\n", + "x04: 0\n", + "x05: 1\n", + "x06: 0\n", + "x07: 1\n", + "...\n", + "────────────────────────────────────────────────────────────────────────────────────────────────────\n", + "Parsed representation ➜ 313 tuples:\n", + "────────────────────────────────────────────────────────────────────────────────────────────────────\n", + "('x00', 1)\n", + "('x01', 1)\n", + "('x02', 0)\n", + "('x03', 0)\n", + "('x04', 0)\n", + "('x05', 1)\n", + "('x06', 0)\n", + "('x07', 1)\n", + "...\n" + ] + } + ], + "source": [ + "class Device(dict):\n", + " \"\"\"A mapping of {wire_name: bit_value_or_gate}, assembled from a device specification.\"\"\"\n", + " def __init__(self, lines: Tuple[Tuple]):\n", + " for line in lines:\n", + " match line: \n", + " case (wire, bit): # e.g., ('x00', 1)\n", + " self[wire] = bit\n", + " case (x, op, y, out): # e.g., ('y33', 'AND', 'x33', 'bfn')\n", + " self[out] = (x, op, y)\n", + " \n", + "device = Device(parse(24, atoms))" + ] + }, + { + "cell_type": "markdown", + "id": "25787cce-ef81-4390-9656-5085262f2bfb", + "metadata": {}, + "source": [ + "### Part 1: Simulate the system of gates and wires. What decimal number does it output on the wires starting with z?\n", + "\n", + "We can't just treat the device as a program where we execute statements in order, because in a line like `z01 = bfn XOR y33`, the values of `bfn` and `y33` may not yet be determined. But we can execute *backwards*, as a [dataflow architecture](https://en.wikipedia.org/wiki/Dataflow_architecture). \n", + "\n", + "The function `simulate` does that. We start from the outputs (the wires whose first letter is `'z'`) and try to **get** their values. If the value is a bit, great, we got it. If the value is a gate, then we recursively **get** the values of both input wires to the gate, and perform the gate's boolean operation on them. When we're done calculating all the output variables, the function `simulate` returns their values as a string of `'0'` or `'1'` characters." + ] + }, + { + "cell_type": "code", + "execution_count": 254, + "id": "a9a4780f-6033-452f-b49c-74b97c9e2440", + "metadata": {}, + "outputs": [], + "source": [ + "def simulate(device: Device) -> str:\n", + " \"\"\"Use dataflow to get the values of the output bits of this device (as a \"01\" str).\"\"\"\n", + " def get(w) -> int: \n", + " match device[w]:\n", + " case (x, op, y): return operators[op](get(x), get(y))\n", + " case bit: return bit\n", + " return cat(map(get, wires(device, 'z')))\n", + "\n", + "operators = dict(XOR=operator.xor, AND=min, OR=max)\n", + "\n", + "def wires(device, letter) -> List[str]: \n", + " \"\"\"The names of all the wires that start with the given letter, in sorted order.\"\"\"\n", + " return sorted([w for w in device if w.startswith(letter)], reverse=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 255, + "id": "72437439-dddf-4202-9944-36e796800304", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Puzzle 24.1: .001 seconds, answer 36035961805936 ok" + ] + }, + "execution_count": 255, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "answer(24.1, 36035961805936, lambda:\n", + " int(simulate(device), base=2))" + ] + }, + { + "cell_type": "markdown", + "id": "7ddc7897-5ff2-46bc-999d-302c92c137da", + "metadata": {}, + "source": [ + "At first I had a **bug**. I fell victim to one of the [**classic blunders**](https://youtu.be/RWW6aDpUvbQ?si=0uvZFEYgY9i_441N): using the wrong [**endianness**](https://en.wikipedia.org/wiki/Endianness) of my bits. After re-reading the puzzle description, I added the `reverse=True` to `wires`." + ] + }, + { + "cell_type": "markdown", + "id": "c42bd211-b999-4dd8-9e34-aebf042e55e4", + "metadata": {}, + "source": [ + "### Part 2: What do you get if you sort the names of the eight wires involved in a swap and then join those names with commas?\n", + "\n", + "In Part 2 we are told that the system is attempting to do addition: treating the `x` wires as a binary number and the `y` wires as another binary number, and computing their sum and outputting it to the `z` wires. But four pairs of output wires are swapped with each other. For example, we might have gates `a XOR b -> z01` and `c OR d -> z02`, but the correct gates are `a XOR b -> z02` and `c OR d -> z01`; then the swapped wires are `z01` and `z02`. Our job is to find all eight swapped wires.\n", + "\n", + "I have four ideas, none of them completely compelling:\n", + "1) Plot the circuit diagram and see if anythng looks funny.\n", + " - Making a pretty plot might be tedious, and I'm worried about the ordering of the two input wires to each gate.\n", + "2) Examine the structure of the device and look for something that breaks the pattern.\n", + " - For example, calculate the depth, number of transitive inputs, number of transitive outputs, etc. for each gate, and look for broken patterns.\n", + " - This might help me, as a detective, uncover the answer, but if I was given another input device I'd have to start all over.\n", + "3) Choose `x` and `y` numbers as input, run the simulation, and compare the resulting `z` values to the correct answer.\n", + " - If I chose random numbers it might take a long time to isolate the faults.\n", + " - It is easy to identify faulty output (`z`) values; trickier to identify faults in internal gates.\n", + " - I could test the one-bit adders one at a time (with and without carry). But I don;t know if that would always work.\n", + "4) I know a bit about how an adder circuit is normally built; I can walk the graph of gates and see if it conforms to what I think it should be.\n", + " - When it isn't, I can swap wires into place and keep looking.\n", + " - I don't know if this approach will find all faults.\n", + " \n", + "Here's a picture of a full adder; we'd have one of these for each output wire (except for `z00`, which doesn't have to deal with a carry bit). \n", + "![Adder from Wikipedia](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhCXZpXA19TudYZz-S51V6WZ_b1HPAbA-uozACxQdnavllWn9oUrVwID5eOmtOhUhFHZZKadar5TY3os2H-Vs4uiCK92wEERCFSOAamOTwOZTQ_q034zdMUhSmxYNihUHWRIRVwLJVoR7Zo1u7-dPL9gysEifyvQqO8AnTvGv9oEAgkxTZMuVyNVN-D/w400-h245/nbit_1.png) Let's see if this matches the number of gates in our device:" + ] + }, + { + "cell_type": "code", + "execution_count": 258, + "id": "3b41a166-d33b-402b-971b-1dc1d33135ec", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Counter({'AND': 89, 'XOR': 89, 'OR': 44})" + ] + }, + "execution_count": 258, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "Counter(val[1] for val in device.values() if val not in (0, 1))" + ] + }, + { + "cell_type": "markdown", + "id": "c1f072b2-8c7f-40b2-9712-d0fc0dee62e7", + "metadata": {}, + "source": [ + "It does match, so I guess our device is implementing this circuit layout, so I'll try approach (4). I basically trace through, checking if the right gates are connected to the right wires. Since we are told that the swaps are one-for-one, and that fixing them gives a valid adder circuit, I take that to mean that the gates all have the right operator; we only have to switch some wires.\n", + " \n", + "The first trouble I ran into was that the two inputs to a gate can be in either order. It will save trouble to canonicalize this, so that the first input wire is always alphabetically before the second one. The function `canon` enforces this, and I had to redefine `Device` to canonicalize it." + ] + }, + { + "cell_type": "code", + "execution_count": 260, + "id": "ec8d86d4-8caf-40f2-b516-c7da4acbda19", + "metadata": {}, + "outputs": [], + "source": [ + "class Device(dict):\n", + " \"\"\"A mapping of {wire_name: bit_value_or_gate}, assembled from a device specification.\"\"\"\n", + " def __init__(self, lines: Tuple[Tuple]):\n", + " for line in lines:\n", + " match line: \n", + " case (wire, bit): # e.g., ('x00', 1)\n", + " self[wire] = bit\n", + " case (x, op, y, out): # e.g., ('y33', 'AND', 'x33', 'bfn')\n", + " self[out] = canon(x, op, y) # canonicalize order \n", + " \n", + "def canon(x, op, y) -> Tuple: \n", + " \"\"\"The canonical ordering of the two input wires (and the operator).\"\"\"\n", + " return (min(x, y), op, max(x, y))\n", + "\n", + "device = Device(parse(24, atoms, show=0)) # I put 'show=0' so I don't print the inoput file again." + ] + }, + { + "cell_type": "markdown", + "id": "47ef587b-d8de-401d-9015-e4b81e1eef96", + "metadata": {}, + "source": [ + "Here's the code to walk through the diagram, looking for the XOR and AND gates for each input, finding misplaced wires, and swapping them." + ] + }, + { + "cell_type": "code", + "execution_count": 262, + "id": "2a06af7f-2b9a-4b1f-b9dd-9ec6fb94a1e6", + "metadata": {}, + "outputs": [], + "source": [ + "def find_swaps(device):\n", + " \"\"\"Find all wires that need to be swapped to have the right architecture for an adder.\n", + " This uses a few heuristics to look for how the carry bits are XOR-ed and AND-ed in.\n", + " This may not work on all faulty devices, but it worked on the one we were given.\"\"\"\n", + " device = device.copy()\n", + " inv = {device[w]: w for w in device} # Inverse mapping\n", + " def lookup(x, op, y): return inv.get(canon(x, op, y), None)\n", + " \n", + " for i in range(len(wires(device, 'x'))):\n", + " x, y, z = [f'{v}{i:02d}' for v in 'xyz']\n", + " XOR_gate = lookup(x, 'XOR', y)\n", + " AND_gate = lookup(x, 'AND', y)\n", + " if x == 'x00':\n", + " carry = AND_gate\n", + " else:\n", + " a, b = carry, XOR_gate\n", + " if not lookup(a, 'XOR', b):\n", + " e, op, f, = device[z]\n", + " a, b = {e, f} ^ {a, b}\n", + " yield from swap(a, b, device, inv)\n", + " elif lookup(a, 'XOR', b) != z:\n", + " yield from swap(z, lookup(a, 'XOR', b), device, inv)\n", + " XOR_gate = lookup(x, 'XOR', y) # After the swap, need to get the right gates\n", + " AND_gate = lookup(x, 'AND', y)\n", + " carry = lookup(carry, 'AND', XOR_gate) # The carry gets propagated through two gates\n", + " carry = lookup(carry, 'OR', AND_gate)\n", + "\n", + "def swap(a: str, b: str, device: dict, inv: dict) -> Set[str]:\n", + " \"\"\"Swap wires `a` and `b` in device (and also update the inverse mapping).\"\"\"\n", + " device[a], device[b] = device[b], device[a]\n", + " inv[device[a]], inv[device[b]] = inv[device[b]], inv[device[a]]\n", + " return a, b" + ] + }, + { + "cell_type": "code", + "execution_count": 263, + "id": "e9b87c38-f67c-4948-9af6-251021747e9d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Puzzle 24.2: .000 seconds, answer jqf,mdd,skh,wpd,wts,z11,z19,z37 ok" + ] + }, + "execution_count": 263, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "answer(24.2, 'jqf,mdd,skh,wpd,wts,z11,z19,z37', lambda:\n", + " ','.join(sorted(find_swaps(device))))" + ] + }, + { + "cell_type": "markdown", + "id": "b8d3967e-3473-47b0-b1b8-687c56e49138", + "metadata": {}, + "source": [ + "# [Day 25](https://adventofcode.com/2024/day/25): Code Chronicle\n", + "\n", + "We're trying to get into the Chief Historian's office, but the door is locked. We have the schematics of all the locks and keys on the floor we're on, and we need to see which key could fit which lock. A lock is described with 5 columns of different length growing down from the top, and a key has five columns growing up from the bottom. A key and a lock are a possible fit if none of the five lines overlap; that is, since the lock cylinder has height 7 units, it is a possible fit if none of the five columns has more than 7 total `'#'` marks for the lock and the key combined.\n", + "\n", + "We'll parse the schematics into paragraphs, where each one is a list of lines." + ] + }, + { + "cell_type": "code", + "execution_count": 265, + "id": "f5971853-7139-4f17-bdc5-6c51e12a928d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "────────────────────────────────────────────────────────────────────────────────────────────────────\n", + "Puzzle input ➜ 3999 strs:\n", + "────────────────────────────────────────────────────────────────────────────────────────────────────\n", + "#####\n", + "####.\n", + "####.\n", + "####.\n", + "##.#.\n", + "#....\n", + ".....\n", + "\n", + "...\n", + "────────────────────────────────────────────────────────────────────────────────────────────────────\n", + "Parsed representation ➜ 500 lists:\n", + "────────────────────────────────────────────────────────────────────────────────────────────────────\n", + "['#####', '####.', '####.', '####.', '##.#.', '#....', '.....']\n", + "['#####', '####.', '##.#.', '##.#.', '.#.#.', '.#.#.', '.....']\n", + "['.....', '.....', '#..#.', '#..#.', '##.##', '#####', '#####']\n", + "['#####', '###.#', '#.#.#', '#.#.#', '#.#..', '#....', '.....']\n", + "['#####', '##.#.', '#..#.', '#..#.', '#..#.', '#..#.', '.....']\n", + "['#####', '#####', '#####', '#.##.', '..##.', '...#.', '.....']\n", + "['#####', '#.###', '...#.', '.....', '.....', '.....', '.....']\n", + "['.....', '.....', '.....', '.#.#.', '##.#.', '#####', '#####']\n", + "...\n" + ] + } + ], + "source": [ + "schematics = parse(25, lines, sections=paragraphs)" + ] + }, + { + "cell_type": "markdown", + "id": "3bece291-f0e6-463a-9ac9-401563c5184b", + "metadata": {}, + "source": [ + "### Part 1: How many unique lock/key pairs fit together without overlapping in any column?\n", + "\n", + "First we separate the schematics into locks and keys, transforming each one into a list of column heights. To begin with, each schematic is a list of rows. The function `heights` takes the transpose, `T(schematic)`, to get a list of columns, then uses `count` to count the number of `'#'` characters in each column. For example, the first lock is:\n", + "\n", + " ['#####', '####.', '####.', '####.', '##.#.', '#....', '.....']\n", + "\n", + "We transpose that to get a list of columns, and count the number of `'#'` characters in each column:\n", + "\n", + " [6, 5, 4, 5, 1]\n", + "\n", + "Then for each lock/key pair, we see if they can fit by checking that all columns have no more than 7 total `'#'` characters." + ] + }, + { + "cell_type": "code", + "execution_count": 267, + "id": "5302ac58-91fc-475a-83d9-cea91457df3b", + "metadata": {}, + "outputs": [], + "source": [ + "def count_fits(schematics) -> List:\n", + " \"\"\"How many key/lock combinations fit each other.\"\"\"\n", + " locks = [heights(s) for s in schematics if s[0] == '#####']\n", + " keys = [heights(s) for s in schematics if s[0] != '#####']\n", + " return quantify(can_fit(lock, key) for lock in locks for key in keys)\n", + " \n", + "def can_fit(lock, key): \n", + " \"\"\"The key can fit the lock if the sum of the '#' count in each column is <= 7.\"\"\"\n", + " return all(lock[c] + key[c] <= 7 for c in range(5))\n", + "\n", + "def heights(schematic) -> List[int]:\n", + " \"\"\"A counter of {column_number: (number of '#' in that column)}.\"\"\"\n", + " return [column.count('#') for column in T(schematic)]" + ] + }, + { + "cell_type": "code", + "execution_count": 268, + "id": "c415f5c1-76c3-486c-9075-c46a5a8c5bcb", + "metadata": {}, + "outputs": [], + "source": [ + "assert heights(['#####', '####.', '####.', '####.', '##.#.', '#....', '.....']) == [6, 5, 4, 5, 1]" + ] + }, + { + "cell_type": "code", + "execution_count": 269, + "id": "89c28b74-ed31-4bb5-b463-7177952a95ae", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Puzzle 25.1: .022 seconds, answer 3196 ok" + ] + }, + "execution_count": 269, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "answer(25.1, 3196, lambda:\n", + " count_fits(schematics))" + ] + }, + { + "cell_type": "markdown", + "id": "3e78ac0f-5866-4620-8114-1b05b5c44de1", + "metadata": {}, + "source": [ + "# Summary\n", + "\n", + "Here are all the puzzle answers and timings. The median run time is under 10 milliseconds, but 4 puzzles take over a second." + ] + }, + { + "cell_type": "code", + "execution_count": 271, "id": "34813fc9-a000-4cd8-88ae-692851b3242c", "metadata": {}, "outputs": [ @@ -7982,43 +9191,52 @@ "Puzzle 3.1: .001 seconds, answer 156388521 ok\n", "Puzzle 3.2: .000 seconds, answer 75920122 ok\n", "Puzzle 4.1: .033 seconds, answer 2401 ok\n", - "Puzzle 4.2: .027 seconds, answer 1822 ok\n", + "Puzzle 4.2: .026 seconds, answer 1822 ok\n", "Puzzle 5.1: .001 seconds, answer 5762 ok\n", "Puzzle 5.2: .001 seconds, answer 4130 ok\n", "Puzzle 6.1: .002 seconds, answer 5329 ok\n", - "Puzzle 6.2: 1.968 seconds, answer 2162 ok\n", - "Puzzle 7.1: .014 seconds, answer 1985268524462 ok\n", - "Puzzle 7.2: .656 seconds, answer 150077710195188 ok\n", + "Puzzle 6.2: 1.967 seconds, answer 2162 ok\n", + "Puzzle 7.1: .013 seconds, answer 1985268524462 ok\n", + "Puzzle 7.2: .660 seconds, answer 150077710195188 ok\n", "Puzzle 8.1: .003 seconds, answer 220 ok\n", "Puzzle 8.2: .003 seconds, answer 813 ok\n", - "Puzzle 9.1: .020 seconds, answer 6332189866718 ok\n", - "Puzzle 9.2: 2.731 seconds, answer 6353648390778 ok\n", + "Puzzle 9.1: .019 seconds, answer 6332189866718 ok\n", + "Puzzle 9.2: 2.754 seconds, answer 6353648390778 ok\n", "Puzzle 10.1: .005 seconds, answer 744 ok\n", "Puzzle 10.2: .006 seconds, answer 1651 ok\n", "Puzzle 11.1: .002 seconds, answer 194482 ok\n", - "Puzzle 11.2: .060 seconds, answer 232454623677743 ok\n", - "Puzzle 12.1: .051 seconds, answer 1402544 ok\n", + "Puzzle 11.2: .061 seconds, answer 232454623677743 ok\n", + "Puzzle 12.1: .052 seconds, answer 1402544 ok\n", "Puzzle 12.2: .042 seconds, answer 862486 ok\n", "Puzzle 13.1: .000 seconds, answer 29598 ok\n", "Puzzle 13.2: .000 seconds, answer 93217456941970 ok\n", "Puzzle 14.1: .000 seconds, answer 216027840 ok\n", - "Puzzle 14.2: 1.893 seconds, answer 6876 ok\n", + "Puzzle 14.2: 1.869 seconds, answer 6876 ok\n", "Puzzle 15.1: .029 seconds, answer 1563092 ok\n", - "Puzzle 15.2: .042 seconds, answer 1582688 ok\n", + "Puzzle 15.2: .041 seconds, answer 1582688 ok\n", "Puzzle 16.1: .147 seconds, answer 103512 ok\n", - "Puzzle 16.2: .855 seconds, answer 554 ok\n", + "Puzzle 16.2: .852 seconds, answer 554 ok\n", "Puzzle 17.1: .000 seconds, answer 2,1,0,1,7,2,5,0,3 ok\n", - "Puzzle 17.2: .024 seconds, answer 267265166222235 ok\n", + "Puzzle 17.2: .023 seconds, answer 267265166222235 ok\n", "Puzzle 18.1: .014 seconds, answer 344 ok\n", - "Puzzle 18.2: .033 seconds, answer 46,18 ok\n", + "Puzzle 18.2: .032 seconds, answer 46,18 ok\n", "Puzzle 19.1: .004 seconds, answer 242 ok\n", - "Puzzle 19.2: .183 seconds, answer 595975512785325 ok\n", + "Puzzle 19.2: .187 seconds, answer 595975512785325 ok\n", "Puzzle 20.1: .023 seconds, answer 1343 ok\n", - "Puzzle 20.2: .764 seconds, answer 982891 ok\n", + "Puzzle 20.2: .770 seconds, answer 982891 ok\n", + "Puzzle 21.1: .002 seconds, answer 205160 ok\n", + "Puzzle 21.2: .008 seconds, answer 252473394928452 ok\n", + "Puzzle 22.1: .321 seconds, answer 14273043166 ok\n", + "Puzzle 22.2: 1.265 seconds, answer 1667 ok\n", + "Puzzle 23.1: .001 seconds, answer 1170 ok\n", + "Puzzle 23.2: .003 seconds, answer bo,dd,eq,ik,lo,lu,ph,ro,rr,rw,uo,wx,yg ok\n", + "Puzzle 24.1: .001 seconds, answer 36035961805936 ok\n", + "Puzzle 24.2: .000 seconds, answer jqf,mdd,skh,wpd,wts,z11,z19,z37 ok\n", + "Puzzle 25.1: .022 seconds, answer 3196 ok\n", "\n", - "Correct: 40/40\n", + "Correct: 49/49\n", "\n", - "Time in seconds: 0.014 median, 0.241 mean, 9.638 total.\n" + "Time in seconds: 0.008 median, 0.230 mean, 11.268 total.\n" ] } ], diff --git a/ipynb/AdventUtils.ipynb b/ipynb/AdventUtils.ipynb index fa258fd..61194a5 100644 --- a/ipynb/AdventUtils.ipynb +++ b/ipynb/AdventUtils.ipynb @@ -21,7 +21,7 @@ "source": [ "from collections import Counter, defaultdict, namedtuple, deque, abc\n", "from dataclasses import dataclass, field\n", - "from itertools import permutations, combinations, cycle, chain, islice\n", + "from itertools import permutations, combinations, cycle, chain, islice, filterfalse\n", "from itertools import count as count_from, product as cross_product, takewhile\n", "from typing import *\n", "from statistics import mean, median\n", @@ -173,7 +173,6 @@ "outputs": [], "source": [ "answers = {} # `answers` is a dict of {puzzle_number: answer}\n", - "\n", "unknown = 'unknown'\n", "\n", "class answer:\n", @@ -754,7 +753,12 @@ " \n", "class HCounter(Counter):\n", " \"\"\"A Counter, but it is hashable.\"\"\"\n", - " def __hash__(self): return hash(tuple(sorted(self.items())))" + " def __hash__(self): return hash(tuple(sorted(self.items())))\n", + "\n", + "class EqualityIsIdentity:\n", + " \"\"\"A mixin to say that objects of this class are equal only if they are identical.\"\"\"\n", + " def __hash__(self): return id(self)\n", + " def __eq__(self, other): return self is other" ] }, { @@ -766,7 +770,7 @@ "class Graph(defaultdict):\n", " \"\"\"A graph of {node: [neighboring_nodes...]}. \n", " Can store other kwd attributes on it (which you can't do with a dict).\"\"\"\n", - " def __init__(self, contents, **kwds):\n", + " def __init__(self, contents=(), **kwds):\n", " self.update(contents)\n", " self.default_factory = list\n", " self.__dict__.update(**kwds)"