diff --git a/ipynb/Advent-2024.ipynb b/ipynb/Advent-2024.ipynb index 283bb5a..916310b 100644 --- a/ipynb/Advent-2024.ipynb +++ b/ipynb/Advent-2024.ipynb @@ -136,7 +136,7 @@ { "data": { "text/plain": [ - "Puzzle 1.1: .0002 seconds, answer 1830467 ok" + "Puzzle 1.1: .0002 seconds, answer 1830467 ok" ] }, "execution_count": 8, @@ -181,7 +181,7 @@ { "data": { "text/plain": [ - "Puzzle 1.2: .0001 seconds, answer 26674158 ok" + "Puzzle 1.2: .0001 seconds, answer 26674158 ok" ] }, "execution_count": 11, @@ -284,7 +284,7 @@ { "data": { "text/plain": [ - "Puzzle 2.1: .0004 seconds, answer 257 ok" + "Puzzle 2.1: .0004 seconds, answer 257 ok" ] }, "execution_count": 16, @@ -334,7 +334,7 @@ { "data": { "text/plain": [ - "Puzzle 2.2: .0021 seconds, answer 328 ok" + "Puzzle 2.2: .0023 seconds, answer 328 ok" ] }, "execution_count": 19, @@ -419,7 +419,7 @@ { "data": { "text/plain": [ - "Puzzle 3.1: .0006 seconds, answer 156388521 ok" + "Puzzle 3.1: .0006 seconds, answer 156388521 ok" ] }, "execution_count": 24, @@ -463,7 +463,7 @@ { "data": { "text/plain": [ - "Puzzle 3.2: .0004 seconds, answer 75920122 ok" + "Puzzle 3.2: .0005 seconds, answer 75920122 ok" ] }, "execution_count": 27, @@ -553,7 +553,7 @@ { "data": { "text/plain": [ - "Puzzle 4.1: .0328 seconds, answer 2401 ok" + "Puzzle 4.1: .0328 seconds, answer 2401 ok" ] }, "execution_count": 32, @@ -608,7 +608,7 @@ { "data": { "text/plain": [ - "Puzzle 4.2: .0272 seconds, answer 1822 ok" + "Puzzle 4.2: .0268 seconds, answer 1822 ok" ] }, "execution_count": 35, @@ -738,7 +738,7 @@ { "data": { "text/plain": [ - "Puzzle 5.1: .0007 seconds, answer 5762 ok" + "Puzzle 5.1: .0007 seconds, answer 5762 ok" ] }, "execution_count": 41, @@ -823,7 +823,7 @@ { "data": { "text/plain": [ - "Puzzle 5.2: .0009 seconds, answer 4130 ok" + "Puzzle 5.2: .0008 seconds, answer 4130 ok" ] }, "execution_count": 46, @@ -923,7 +923,7 @@ { "data": { "text/plain": [ - "Puzzle 6.1: .0016 seconds, answer 5329 ok" + "Puzzle 6.1: .0014 seconds, answer 5329 ok" ] }, "execution_count": 52, @@ -993,7 +993,7 @@ { "data": { "text/plain": [ - "Puzzle 6.2: 1.9934 seconds, answer 2162 ok" + "Puzzle 6.2: 1.9643 seconds, answer 2162 ok" ] }, "execution_count": 55, @@ -1121,14 +1121,6 @@ "metadata": {}, "outputs": [], "source": [ - "def can_be_calibrated(numbers: ints, operators=(operator.add, operator.mul)) -> bool:\n", - " \"\"\"Can the tuple of numbers be calibrated as a correct equation using '+' and '*' ?\"\"\"\n", - " target, first, *rest = numbers\n", - " results = {first} # A set of all possible results of the partial computation\n", - " for y in rest:\n", - " results = {op(x, y) for x in results if x <= target for op in operators}\n", - " return target in results\n", - "\n", "def can_be_calibrated(numbers: ints, operators=(operator.add, operator.mul)) -> bool:\n", " \"\"\"Can the tuple of numbers be calibrated as a correct equation using '+' and '*' ?\"\"\"\n", " target, first, *rest = numbers\n", @@ -1147,7 +1139,7 @@ { "data": { "text/plain": [ - "Puzzle 7.1: .0214 seconds, answer 1985268524462 ok" + "Puzzle 7.1: .0213 seconds, answer 1985268524462 ok" ] }, "execution_count": 64, @@ -1179,7 +1171,7 @@ { "data": { "text/plain": [ - "Puzzle 7.2: 1.0915 seconds, answer 150077710195188 ok" + "Puzzle 7.2: 1.0804 seconds, answer 150077710195188 ok" ] }, "execution_count": 66, @@ -1275,7 +1267,7 @@ { "data": { "text/plain": [ - "Puzzle 8.1: .0027 seconds, answer 220 ok" + "Puzzle 8.1: .0029 seconds, answer 220 ok" ] }, "execution_count": 71, @@ -1330,7 +1322,7 @@ { "data": { "text/plain": [ - "Puzzle 8.1: .0029 seconds, answer 220 ok" + "Puzzle 8.1: .0026 seconds, answer 220 ok" ] }, "execution_count": 74, @@ -1352,7 +1344,7 @@ { "data": { "text/plain": [ - "Puzzle 8.2: .0031 seconds, answer 813 ok" + "Puzzle 8.2: .0030 seconds, answer 813 ok" ] }, "execution_count": 75, @@ -1465,7 +1457,7 @@ { "data": { "text/plain": [ - "Puzzle 9.1: .0193 seconds, answer 6332189866718 ok" + "Puzzle 9.1: .0197 seconds, answer 6332189866718 ok" ] }, "execution_count": 81, @@ -1545,7 +1537,7 @@ { "data": { "text/plain": [ - "Puzzle 9.2: 2.7416 seconds, answer 6353648390778 ok" + "Puzzle 9.2: 2.7519 seconds, answer 6353648390778 ok" ] }, "execution_count": 84, @@ -1654,7 +1646,7 @@ { "data": { "text/plain": [ - "Puzzle 10.1: .0047 seconds, answer 744 ok" + "Puzzle 10.1: .0045 seconds, answer 744 ok" ] }, "execution_count": 90, @@ -1705,7 +1697,7 @@ { "data": { "text/plain": [ - "Puzzle 10.2: .0059 seconds, answer 1651 ok" + "Puzzle 10.2: .0062 seconds, answer 1651 ok" ] }, "execution_count": 93, @@ -1847,7 +1839,7 @@ { "data": { "text/plain": [ - "Puzzle 11.1: .0658 seconds, answer 194482 ok" + "Puzzle 11.1: .0671 seconds, answer 194482 ok" ] }, "execution_count": 100, @@ -1908,7 +1900,7 @@ { "data": { "text/plain": [ - "Puzzle 11.1: .0016 seconds, answer 194482 ok" + "Puzzle 11.1: .0015 seconds, answer 194482 ok" ] }, "execution_count": 104, @@ -1930,7 +1922,7 @@ { "data": { "text/plain": [ - "Puzzle 11.2: .0595 seconds, answer 232454623677743 ok" + "Puzzle 11.2: .0599 seconds, answer 232454623677743 ok" ] }, "execution_count": 105, @@ -2062,7 +2054,7 @@ { "data": { "text/plain": [ - "Puzzle 12.1: .0334 seconds, answer 1402544 ok" + "Puzzle 12.1: .0329 seconds, answer 1402544 ok" ] }, "execution_count": 113, @@ -2135,7 +2127,7 @@ { "data": { "text/plain": [ - "Puzzle 12.1: .0307 seconds, answer 1402544 ok" + "Puzzle 12.1: .0307 seconds, answer 1402544 ok" ] }, "execution_count": 116, @@ -2157,7 +2149,7 @@ { "data": { "text/plain": [ - "Puzzle 12.2: .0434 seconds, answer 862486 ok" + "Puzzle 12.2: .0430 seconds, answer 862486 ok" ] }, "execution_count": 117, @@ -2277,7 +2269,7 @@ { "data": { "text/plain": [ - "Puzzle 13.1: .0106 seconds, answer 29598 ok" + "Puzzle 13.1: .0102 seconds, answer 29598 ok" ] }, "execution_count": 122, @@ -2399,7 +2391,7 @@ { "data": { "text/plain": [ - "Puzzle 13.2: .0004 seconds, answer 93217456941970 ok" + "Puzzle 13.2: .0004 seconds, answer 93217456941970 ok" ] }, "execution_count": 129, @@ -2429,7 +2421,7 @@ { "data": { "text/plain": [ - "Puzzle 13.1: .0002 seconds, answer 29598 ok" + "Puzzle 13.1: .0002 seconds, answer 29598 ok" ] }, "execution_count": 131, @@ -2534,7 +2526,7 @@ { "data": { "text/plain": [ - "Puzzle 14.1: .0001 seconds, answer 216027840 ok" + "Puzzle 14.1: .0001 seconds, answer 216027840 ok" ] }, "execution_count": 136, @@ -2789,42 +2781,42 @@ "\n", "\n", "
\n", - " \n", + " \n", "
\n", - " \n", + " oninput=\"anim1e0a1734c1b64f26ba0a7f3051a934a3.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", @@ -2834,9 +2826,9 @@ " /* Instantiate the Animation class. */\n", " /* The IDs given should match those used in the template above. */\n", " (function() {\n", - " var img_id = \"_anim_imgae252cea00e945ac938aee942061fc97\";\n", - " var slider_id = \"_anim_sliderae252cea00e945ac938aee942061fc97\";\n", - " var loop_select_id = \"_anim_loop_selectae252cea00e945ac938aee942061fc97\";\n", + " var img_id = \"_anim_img1e0a1734c1b64f26ba0a7f3051a934a3\";\n", + " var slider_id = \"_anim_slider1e0a1734c1b64f26ba0a7f3051a934a3\";\n", + " var loop_select_id = \"_anim_loop_select1e0a1734c1b64f26ba0a7f3051a934a3\";\n", " var frames = new Array(3);\n", " \n", " frames[0] = \"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA8AAAALQCAYAAABfdxm0AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90\\\n", @@ -5608,14 +5600,14 @@ " /* set a timeout to make sure all the above elements are created before\n", " the object is initialized. */\n", " setTimeout(function() {\n", - " animae252cea00e945ac938aee942061fc97 = new Animation(frames, img_id, slider_id, 200.0,\n", + " anim1e0a1734c1b64f26ba0a7f3051a934a3 = new Animation(frames, img_id, slider_id, 200.0,\n", " loop_select_id);\n", " }, 0);\n", " })()\n", "\n" ], "text/plain": [ - "" + "" ] }, "execution_count": 140, @@ -5848,42 +5840,42 @@ "\n", "\n", "
\n", - " \n", + " \n", "
\n", - " \n", + " oninput=\"anim4e5b0d0e6fe24e0ea5f0bb1cec42557f.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", @@ -5893,9 +5885,9 @@ " /* Instantiate the Animation class. */\n", " /* The IDs given should match those used in the template above. */\n", " (function() {\n", - " var img_id = \"_anim_imge46cbc345c6d4e34aba519ec8cb115f1\";\n", - " var slider_id = \"_anim_slidere46cbc345c6d4e34aba519ec8cb115f1\";\n", - " var loop_select_id = \"_anim_loop_selecte46cbc345c6d4e34aba519ec8cb115f1\";\n", + " var img_id = \"_anim_img4e5b0d0e6fe24e0ea5f0bb1cec42557f\";\n", + " var slider_id = \"_anim_slider4e5b0d0e6fe24e0ea5f0bb1cec42557f\";\n", + " var loop_select_id = \"_anim_loop_select4e5b0d0e6fe24e0ea5f0bb1cec42557f\";\n", " var frames = new Array(1);\n", " \n", " frames[0] = \"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA8AAAALQCAYAAABfdxm0AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90\\\n", @@ -6573,14 +6565,14 @@ " /* set a timeout to make sure all the above elements are created before\n", " the object is initialized. */\n", " setTimeout(function() {\n", - " anime46cbc345c6d4e34aba519ec8cb115f1 = new Animation(frames, img_id, slider_id, 200.0,\n", + " anim4e5b0d0e6fe24e0ea5f0bb1cec42557f = new Animation(frames, img_id, slider_id, 200.0,\n", " loop_select_id);\n", " }, 0);\n", " })()\n", "\n" ], "text/plain": [ - "" + "" ] }, "execution_count": 143, @@ -6612,7 +6604,7 @@ { "data": { "text/plain": [ - "Puzzle 14.2: 1.8776 seconds, answer 6876 ok" + "Puzzle 14.2: 1.8645 seconds, answer 6876 ok" ] }, "execution_count": 145, @@ -6731,7 +6723,7 @@ { "data": { "text/plain": [ - "Puzzle 15.1: .0241 seconds, answer 1563092 ok" + "Puzzle 15.1: .0248 seconds, answer 1563092 ok" ] }, "execution_count": 150, @@ -6808,7 +6800,7 @@ { "data": { "text/plain": [ - "Puzzle 15.1: .0296 seconds, answer 1563092 ok" + "Puzzle 15.1: .0299 seconds, answer 1563092 ok" ] }, "execution_count": 153, @@ -6830,7 +6822,7 @@ { "data": { "text/plain": [ - "Puzzle 15.2: .0418 seconds, answer 1582688 ok" + "Puzzle 15.2: .0415 seconds, answer 1582688 ok" ] }, "execution_count": 154, @@ -6987,7 +6979,7 @@ { "data": { "text/plain": [ - "Puzzle 16.1: .1477 seconds, answer 103512 ok" + "Puzzle 16.1: .1473 seconds, answer 103512 ok" ] }, "execution_count": 162, @@ -7051,7 +7043,7 @@ { "data": { "text/plain": [ - "Puzzle 16.2: .8603 seconds, answer 554 ok" + "Puzzle 16.2: .8496 seconds, answer 554 ok" ] }, "execution_count": 165, @@ -7101,6 +7093,16 @@ "()\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": 167, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ @@ -7115,27 +7117,7 @@ " case ['Program', *vals]: kwds['prog'] = vals\n", " return Computer(**kwds)\n", " \n", - "computer = initialize(parse(17, atoms))" - ] - }, - { - "cell_type": "code", - "execution_count": 168, - "id": "68cecfae-c690-45f1-8a23-94733624a774", - "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": 168, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ + "computer = initialize(parse(17, atoms))\n", "computer" ] }, @@ -7151,7 +7133,7 @@ }, { "cell_type": "code", - "execution_count": 170, + "execution_count": 169, "id": "d98f88cc-c435-43fc-bcb5-52e6dd70fdb1", "metadata": {}, "outputs": [], @@ -7160,21 +7142,20 @@ " \"\"\"Run the program on the computer, yielding each output.\"\"\"\n", " A, B, C, prog = computer\n", " pc = 0\n", - " def combo(v): return A if v == 4 else B if v == 5 else C if v == 6 else v\n", " while pc < len(prog):\n", " op, val = prog[pc:pc+2]\n", " pc += 2\n", + " combo = (A if val == 4 else B if val == 5 else C if val == 6 else val)\n", " match op:\n", - " case 0: A = A // (2 ** combo(val))\n", - " case 6: B = A // (2 ** combo(val))\n", - " case 7: C = A // (2 ** combo(val))\n", + " case 0: A = A // (2 ** combo)\n", + " case 6: B = A // (2 ** combo)\n", + " case 7: C = A // (2 ** combo)\n", " case 1: B = B ^ val\n", " case 4: B = B ^ C\n", - " case 2: B = combo(val) % 8\n", - " case 5: yield combo(val) % 8\n", + " case 2: B = combo % 8\n", + " case 5: yield combo % 8\n", " case 3: \n", - " if A != 0:\n", - " pc = val" + " if A: pc = val" ] }, { @@ -7187,7 +7168,7 @@ }, { "cell_type": "code", - "execution_count": 172, + "execution_count": 171, "id": "860f24e5-92ad-4361-8920-102ebc573598", "metadata": {}, "outputs": [ @@ -7197,7 +7178,7 @@ "Puzzle 17.1: .0000 seconds, answer 2,1,0,1,7,2,5,0,3 ok" ] }, - "execution_count": 172, + "execution_count": 171, "metadata": {}, "output_type": "execute_result" } @@ -7214,19 +7195,53 @@ "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. I was afraid of this! In past years there were problems where you had to actually understand what a program was doing in order to answer; you couldn't use brute force. I'll try looking at some possible values, but I'm pretty sure it won't work. Another day I'll try to figure it out." + "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", + "\n", + "To try to understand my program, here it is in pseudocode:\n", + "\n", + " top: B = A % 8 # 2, 4\n", + " B = B ^ 7 # 1, 7\n", + " C = A / 2 ** B # 7, 5\n", + " A = A / 2 ** 3 # 0, 3\n", + " B = B ^ C # 4, 4\n", + " B = B ^ 7 # 1, 7\n", + " output B # 5, 5\n", + " if A: goto top # 3, 0\n", + "\n", + "I can summarize that as:\n", + "\n", + " top: B and C are defined in terms of the last octal digit of A, and prior value of B\n", + " A is shifted to eliminate the last octal digit\n", + " output B \n", + " if A: goto top \n", + "\n", + "So I realized that one octal digit of `A` is eliminated on each pass through the loop, and when `A` hits zero, we exit. Each pass outputs one octal digit, so `A` in octal has to be the same length as my program; somewhere in the ten trillion range. Good thing I gave up on brute force. \n", + "\n", + "I realized that I should go right-to-left, outputing one octal digit at a time, and appending one octal digit at a time to `A`. After some trial and error I got the following:" + ] + }, + { + "cell_type": "code", + "execution_count": 173, + "id": "bb745303-dd20-486f-bbdd-7ae77f995c2c", + "metadata": {}, + "outputs": [], + "source": [ + "def run_with(computer=computer, **kwds) -> Ints: \n", + " \"\"\"Run the program with registers set to the given values.\"\"\"\n", + " return list(run_program(computer._replace(**kwds)))" ] }, { "cell_type": "code", "execution_count": 174, - "id": "809bcd1b-e428-4449-971e-0bc63aca08fb", + "id": "54ac9b9c-70d9-4356-91c5-40fb285634b4", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "'No answer!'" + "[2, 4, 1, 7, 7, 5, 0, 3, 4, 4, 1, 7, 5, 5, 3, 0]" ] }, "execution_count": 174, @@ -7235,8 +7250,279 @@ } ], "source": [ - "first(i for i in range(100_000)\n", - " if list(run_program(computer._replace(A=i))) == computer.prog) or 'No answer!'" + "computer.prog" + ] + }, + { + "cell_type": "code", + "execution_count": 175, + "id": "36d89d9a-c8fc-41be-820b-cb13e40793c0", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[0]" + ] + }, + "execution_count": 175, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "run_with(A=0o7)" + ] + }, + { + "cell_type": "code", + "execution_count": 176, + "id": "892fef38-5f9b-4370-a242-f7a10df5487b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[3, 0]" + ] + }, + "execution_count": 176, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "run_with(A=0o72)" + ] + }, + { + "cell_type": "code", + "execution_count": 177, + "id": "b3d11d5e-30c2-419f-bc11-3f26fbdddfbb", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[5, 3, 0]" + ] + }, + "execution_count": 177, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "run_with(A=0o726)" + ] + }, + { + "cell_type": "markdown", + "id": "f40f775f-bb9d-459c-80c3-ac09b6b42904", + "metadata": {}, + "source": [ + "That is, my program ends in `5,3,0`, and an octal `7` outputs a `0`; an octal `0o72` outputs `3,0`, and octal `0o726` outputs `5,3,0`. So here's my approach for finding the Quine program:\n", + "- I'm going to keerp a set of candidate values for `A` as the set `As`.\n", + "- The set starts with just `{0}`.\n", + "- On each iteration I try appending each octal digit to each element of the set `As`.\n", + "- I keep the candidate `A` values whose output matches the tail of the program's output.\n", + "- Iterate this for each digit and return the set of `A` vcalues that produce the whole program.\n", + "- Take the minium of the set." + ] + }, + { + "cell_type": "code", + "execution_count": 179, + "id": "a49c6de9-4e6b-47e5-bcf0-2972a95c1af3", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Puzzle 17.2: .0232 seconds, answer 267265166222235 ok" + ] + }, + "execution_count": 179, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def quine(computer) -> Set[int]:\n", + " \"\"\"Find the values of `A` that cause the output to match the program.\"\"\"\n", + " As = {0}\n", + " for d in reversed(range(len(computer.prog))):\n", + " tail = computer.prog[d:]\n", + " candidates = {(A << 3) + i for A in As for i in range(8)}\n", + " As = {A for A in candidates if run_with(A=A) == tail}\n", + " return As\n", + "\n", + "answer(17.2, 267265166222235, \n", + " lambda: min(quine(computer)))" + ] + }, + { + "cell_type": "markdown", + "id": "c7f9e900-45be-401f-a6f2-a1d7b751fae6", + "metadata": {}, + "source": [ + "# [Day 18](https://adventofcode.com/2024/day/18): RAM Run\n", + "\n", + "In today's narrative, we're inside a computer, on a 2D memory board, and bytes are falling down, at specified (x, y) positions, as given in our input:" + ] + }, + { + "cell_type": "code", + "execution_count": 181, + "id": "d14e1966-2feb-4553-9a0a-12595ef4f7d7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "────────────────────────────────────────────────────────────────────────────────────────────────────\n", + "Puzzle input ➜ 3450 strs:\n", + "────────────────────────────────────────────────────────────────────────────────────────────────────\n", + "40,65\n", + "17,1\n", + "34,45\n", + "31,51\n", + "29,43\n", + "25,9\n", + "14,27\n", + "5,29\n", + "...\n", + "────────────────────────────────────────────────────────────────────────────────────────────────────\n", + "Parsed representation ➜ 3450 tuples:\n", + "────────────────────────────────────────────────────────────────────────────────────────────────────\n", + "(40, 65)\n", + "(17, 1)\n", + "(34, 45)\n", + "(31, 51)\n", + "(29, 43)\n", + "(25, 9)\n", + "(14, 27)\n", + "(5, 29)\n", + "...\n" + ] + } + ], + "source": [ + "falling_bytes = parse(18, ints)" + ] + }, + { + "cell_type": "markdown", + "id": "1229cec7-a456-4dd6-a668-8a00591c63f7", + "metadata": {}, + "source": [ + "### 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; this time we can take a step in any of the four cardinal directions at unit cost. The problem description says that we should first consider just the first kilobyte (1024 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": 183, + "id": "83af4751-38c9-4830-a2fa-78515b59bc97", + "metadata": {}, + "outputs": [], + "source": [ + "def memory_path(falling_bytes: Tuple[Point], width=71, height=71) -> Grid:\n", + " \"\"\"Make a Grid of the given size with the points as obstacles.\"\"\"\n", + " grid = Grid(['.' * width] * height)\n", + " grid.update({p: '#' for p in falling_bytes})\n", + " problem = GridProblem(grid=grid, initial=(0, 0), goal=sub(grid.size, (1, 1)))\n", + " return A_star_search(problem)" + ] + }, + { + "cell_type": "code", + "execution_count": 184, + "id": "29da25e2-f3c2-43e3-8769-1d4fcecb807b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Puzzle 18.1: .0138 seconds, answer 344 ok" + ] + }, + "execution_count": 184, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "answer(18.1, 344, lambda:\n", + " len(memory_path(falling_bytes[:1024])))" + ] + }, + { + "cell_type": "markdown", + "id": "792bc31e-964d-47fe-a09b-6a95724e641e", + "metadata": {}, + "source": [ + "### Part 2: What are the coordinates of the first byte that will prevent the exit from being reachable from your starting position?\n", + "\n", + "After 1024 bytes fall there is a path from start to exit, but as more bytes fall we might have to switch to a different path, and eventually there may be no path. We're asked for the first byte position that blocks the last remaining path. I can think of three ways to handle this:\n", + "1) Add falling bytes one at a time and repeat the A-star search each time. **Slow!**\n", + "2) Add falling bytes in binary search fashion: We know adding no bytes is good for getting a path and adding all of them is bad; try half way and then update good or bad depending on whether we found a path. **Pretty fast.**\n", + "3) Optimize (2) by checking which of the falling bytes intersects with the current path; if *n* bytes are ok, and byte *n*+1 is not on the path for *n*, then *n*+1 is ok. Might be a bit faster, but in my opinion not worth the code complexity. We could also incrementally add and remove " + ] + }, + { + "cell_type": "code", + "execution_count": 186, + "id": "4c0a8dcb-c3af-45e7-9273-8776e8c3ea1d", + "metadata": {}, + "outputs": [], + "source": [ + "def memory_blocker(falling_bytes) -> Point:\n", + " \"\"\"Which falling byte is the first to block a path to the exit? Do binary search.\"\"\"\n", + " good = 0\n", + " bad = len(falling_bytes) - 1\n", + " while bad - good > 1:\n", + " mid = (good + bad) // 2\n", + " path = memory_path(falling_bytes[:mid + 1])\n", + " if path == search_failure:\n", + " bad = mid\n", + " else:\n", + " good = mid\n", + " return falling_bytes[bad]" + ] + }, + { + "cell_type": "code", + "execution_count": 187, + "id": "22371144-5d51-440a-918f-a63de73b13ad", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Puzzle 18.2: .0348 seconds, answer 46,18 ok" + ] + }, + "execution_count": 187, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "answer(18.2, '46,18', lambda:\n", + " cat(memory_blocker(falling_bytes), ','))" + ] + }, + { + "cell_type": "markdown", + "id": "552f1c6e-052e-43d0-b13f-e8f66f274d63", + "metadata": {}, + "source": [ + "I admit I initially had an off-by-one **bug** here. I wanted `bad` to be an index into the falling bytes, but it is also natural to have it be the end of a range; I was confused about my choice." ] }, { @@ -7246,12 +7532,12 @@ "source": [ "# Summary\n", "\n", - "So far, I've solved all the puzzles except 17.2. Most of them run in well under a second (the median is less than a hundreth of a second), but four of them take over a second." + "So far, I've solved all the puzzles. Most of them run in well under a second (the median is less than a hundreth of a second), but four of them take over a second." ] }, { "cell_type": "code", - "execution_count": 176, + "execution_count": 190, "id": "34813fc9-a000-4cd8-88ae-692851b3242c", "metadata": {}, "outputs": [ @@ -7259,43 +7545,46 @@ "name": "stdout", "output_type": "stream", "text": [ - "Puzzle 1.1: .0002 seconds, answer 1830467 ok\n", - "Puzzle 1.2: .0001 seconds, answer 26674158 ok\n", - "Puzzle 2.1: .0004 seconds, answer 257 ok\n", - "Puzzle 2.2: .0021 seconds, answer 328 ok\n", - "Puzzle 3.1: .0006 seconds, answer 156388521 ok\n", - "Puzzle 3.2: .0004 seconds, answer 75920122 ok\n", - "Puzzle 4.1: .0328 seconds, answer 2401 ok\n", - "Puzzle 4.2: .0272 seconds, answer 1822 ok\n", - "Puzzle 5.1: .0007 seconds, answer 5762 ok\n", - "Puzzle 5.2: .0009 seconds, answer 4130 ok\n", - "Puzzle 6.1: .0016 seconds, answer 5329 ok\n", - "Puzzle 6.2: 1.9934 seconds, answer 2162 ok\n", - "Puzzle 7.1: .0214 seconds, answer 1985268524462 ok\n", - "Puzzle 7.2: 1.0915 seconds, answer 150077710195188 ok\n", - "Puzzle 8.1: .0029 seconds, answer 220 ok\n", - "Puzzle 8.2: .0031 seconds, answer 813 ok\n", - "Puzzle 9.1: .0193 seconds, answer 6332189866718 ok\n", - "Puzzle 9.2: 2.7416 seconds, answer 6353648390778 ok\n", - "Puzzle 10.1: .0047 seconds, answer 744 ok\n", - "Puzzle 10.2: .0059 seconds, answer 1651 ok\n", - "Puzzle 11.1: .0016 seconds, answer 194482 ok\n", - "Puzzle 11.2: .0595 seconds, answer 232454623677743 ok\n", - "Puzzle 12.1: .0307 seconds, answer 1402544 ok\n", - "Puzzle 12.2: .0434 seconds, answer 862486 ok\n", - "Puzzle 13.1: .0002 seconds, answer 29598 ok\n", - "Puzzle 13.2: .0004 seconds, answer 93217456941970 ok\n", - "Puzzle 14.1: .0001 seconds, answer 216027840 ok\n", - "Puzzle 14.2: 1.8776 seconds, answer 6876 ok\n", - "Puzzle 15.1: .0296 seconds, answer 1563092 ok\n", - "Puzzle 15.2: .0418 seconds, answer 1582688 ok\n", - "Puzzle 16.1: .1477 seconds, answer 103512 ok\n", - "Puzzle 16.2: .8603 seconds, answer 554 ok\n", + "Puzzle 1.1: .0002 seconds, answer 1830467 ok\n", + "Puzzle 1.2: .0001 seconds, answer 26674158 ok\n", + "Puzzle 2.1: .0004 seconds, answer 257 ok\n", + "Puzzle 2.2: .0023 seconds, answer 328 ok\n", + "Puzzle 3.1: .0006 seconds, answer 156388521 ok\n", + "Puzzle 3.2: .0005 seconds, answer 75920122 ok\n", + "Puzzle 4.1: .0328 seconds, answer 2401 ok\n", + "Puzzle 4.2: .0268 seconds, answer 1822 ok\n", + "Puzzle 5.1: .0007 seconds, answer 5762 ok\n", + "Puzzle 5.2: .0008 seconds, answer 4130 ok\n", + "Puzzle 6.1: .0014 seconds, answer 5329 ok\n", + "Puzzle 6.2: 1.9643 seconds, answer 2162 ok\n", + "Puzzle 7.1: .0213 seconds, answer 1985268524462 ok\n", + "Puzzle 7.2: 1.0804 seconds, answer 150077710195188 ok\n", + "Puzzle 8.1: .0026 seconds, answer 220 ok\n", + "Puzzle 8.2: .0030 seconds, answer 813 ok\n", + "Puzzle 9.1: .0197 seconds, answer 6332189866718 ok\n", + "Puzzle 9.2: 2.7519 seconds, answer 6353648390778 ok\n", + "Puzzle 10.1: .0045 seconds, answer 744 ok\n", + "Puzzle 10.2: .0062 seconds, answer 1651 ok\n", + "Puzzle 11.1: .0015 seconds, answer 194482 ok\n", + "Puzzle 11.2: .0599 seconds, answer 232454623677743 ok\n", + "Puzzle 12.1: .0307 seconds, answer 1402544 ok\n", + "Puzzle 12.2: .0430 seconds, answer 862486 ok\n", + "Puzzle 13.1: .0002 seconds, answer 29598 ok\n", + "Puzzle 13.2: .0004 seconds, answer 93217456941970 ok\n", + "Puzzle 14.1: .0001 seconds, answer 216027840 ok\n", + "Puzzle 14.2: 1.8645 seconds, answer 6876 ok\n", + "Puzzle 15.1: .0299 seconds, answer 1563092 ok\n", + "Puzzle 15.2: .0415 seconds, answer 1582688 ok\n", + "Puzzle 16.1: .1473 seconds, answer 103512 ok\n", + "Puzzle 16.2: .8496 seconds, answer 554 ok\n", "Puzzle 17.1: .0000 seconds, answer 2,1,0,1,7,2,5,0,3 ok\n", + "Puzzle 17.2: .0232 seconds, answer 267265166222235 ok\n", + "Puzzle 18.1: .0138 seconds, answer 344 ok\n", + "Puzzle 18.2: .0348 seconds, answer 46,18 ok\n", "\n", - "Correct: 33/33\n", + "Correct: 36/36\n", "\n", - "Time in seconds: 0.0047 median, 0.2740 mean, 9.0435 total.\n" + "Time in seconds: 0.0100 median, 0.2517 mean, 9.0607 total.\n" ] } ], diff --git a/ipynb/AdventUtils.ipynb b/ipynb/AdventUtils.ipynb index 936a0d1..6a18489 100644 --- a/ipynb/AdventUtils.ipynb +++ b/ipynb/AdventUtils.ipynb @@ -198,7 +198,7 @@ " comment = (f'' if self.got == unknown else\n", " f' ok' if self.ok else \n", " f' WRONG; expected answer is {self.solution}')\n", - " return f'Puzzle {self.puzzle:4.1f}: {secs} seconds, answer {self.got:<15}{comment}'\n", + " return f'Puzzle {self.puzzle:4.1f}: {secs} seconds, answer {self.got:<17}{comment}'\n", "\n", "def summary(answers):\n", " \"\"\"Print a report that summarizes the answers.\"\"\"\n", @@ -554,7 +554,8 @@ " def neighbors(self, point) -> List[Point]:\n", " \"\"\"Points on the grid that neighbor `point`.\"\"\"\n", " return [add2(point, Δ) for Δ in self.directions \n", - " if add2(point, Δ) in self or self.default not in (KeyError, None)]\n", + " if (add2(point, Δ) in self) \n", + " or (self.default not in (KeyError, None))]\n", " \n", " def neighbor_contents(self, point) -> Iterable:\n", " \"\"\"The contents of the neighboring points.\"\"\"\n", @@ -664,8 +665,7 @@ "class GridProblem(SearchProblem):\n", " \"\"\"Problem for searching a grid from a start to a goal location.\n", " A state is just an (x, y) location in the grid.\"\"\"\n", - " def actions(self, loc): return self.grid.neighbors(loc)\n", - " def result(self, loc1, loc2): return loc2\n", + " def actions(self, pos): return [p for p in self.grid.neighbors(pos) if self.grid[pos] != '#']\n", " def h(self, node): return taxi_distance(node.state, self.goal) \n", "\n", "class Node:\n",