From af672ed94a86ec3745b14be476c1e63fadf4d3e2 Mon Sep 17 00:00:00 2001 From: Peter Norvig Date: Sat, 28 Dec 2024 23:21:05 -0800 Subject: [PATCH] Add files via upload --- ipynb/Advent-2024.ipynb | 855 +++++++++++++++++++++++----------------- ipynb/AdventUtils.ipynb | 25 +- 2 files changed, 525 insertions(+), 355 deletions(-) diff --git a/ipynb/Advent-2024.ipynb b/ipynb/Advent-2024.ipynb index ef2fd9a..fc3e8a2 100644 --- a/ipynb/Advent-2024.ipynb +++ b/ipynb/Advent-2024.ipynb @@ -42,10 +42,11 @@ "id": "dfecffd7-6955-45ba-9dc2-1ec805baba85", "metadata": {}, "source": [ - "Each day's solution consists of three parts, making use of my `parse` and `answer` utilities:\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", + "Each day's solution consists of these parts, making use of my `parse` and `answer` utilities:\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", + "- *(Optional Part Three: On some days, I'll add some further exploration, visualization, or verification.)*\n", "\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", @@ -334,7 +335,7 @@ "id": "ee48bf63-8a67-407b-9a73-df097811eabc", "metadata": {}, "source": [ - "Note: I used my [AdventUtils](AdventUtils.ipynb) function `quantify`, where `quantify(reports, is_safe)` means the number of items in `reports` for which `is_safe` is true.\n", + "Note: I used my [AdventUtils](AdventUtils.ipynb) function `quantify` (adapted from the [itertools recipes](https://docs.python.org/3/library/itertools.html#itertools-recipes)). `quantify(reports, is_safe)` counts 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", @@ -474,24 +475,6 @@ " execute(program))" ] }, - { - "cell_type": "markdown", - "id": "d39477c7-5c81-41a9-83a3-5b1bfdef273a", - "metadata": {}, - "source": [ - "Here's an example of `all_multiplications`:" - ] - }, - { - "cell_type": "code", - "execution_count": 31, - "id": "85844f51-1396-4299-ba5b-c61064ee02b6", - "metadata": {}, - "outputs": [], - "source": [ - "assert all_multiplications(\"mul(1,2) + mul(34,5) - mul(67,89] / mul(x,15)\") == ['mul(1,2)', 'mul(34,5)']" - ] - }, { "cell_type": "markdown", "id": "622d7010-145e-422a-a592-d4b446afcc0f", @@ -504,7 +487,7 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 31, "id": "4525d01a-bac0-41c2-92b8-baf0fd395e88", "metadata": {}, "outputs": [], @@ -516,7 +499,7 @@ }, { "cell_type": "code", - "execution_count": 34, + "execution_count": 32, "id": "ce40f258-ca76-48c3-9965-27a6979a4243", "metadata": {}, "outputs": [ @@ -526,7 +509,7 @@ "Puzzle 3.2: .000 seconds, answer 75920122 ok" ] }, - "execution_count": 34, + "execution_count": 32, "metadata": {}, "output_type": "execute_result" } @@ -536,6 +519,40 @@ " execute(enabled_part(program)))" ] }, + { + "cell_type": "markdown", + "id": "81b1ed3a-5bdb-4ef9-957d-25bcb19e3a00", + "metadata": {}, + "source": [ + "### Part 3: Verification\n", + "\n", + "Regular expressions are [always tricky](https://blog.codinghorror.com/regular-expressions-now-you-have-two-problems/), so here are some assertions to give us more confidence that we got the components right (and to show some examples of their use)." + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "1cb1b8e5-1ad7-4364-8816-73669f9c0777", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 34, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "assert all_multiplications(\"mul(1,2) + mul(34,5) - mul(67,89] / mul(x,15)\") == ['mul(1,2)', 'mul(34,5)']\n", + "assert enabled_part(\"testing(1,2,3)\") == \"testing(1,2,3)\"\n", + "assert enabled_part(\"preamble don't() middle part do() final\") == 'preamble final'\n", + "enabled_part(\"preamble don't() middle part no do, final\") == 'preamble ' # Make sure it works without a closing \"do()\"" + ] + }, { "cell_type": "markdown", "id": "e1448343-6488-45ad-b03d-d7928feb75cd", @@ -601,8 +618,8 @@ "\n", "def grid_can_spell(grid, start, dir, word):\n", " \"\"\"Does `word` appear in grid starting at `start` and going in direction `dir`?\"\"\"\n", - " return all(grid[add(start, mul(dir, i))] == word[i] \n", - " for i in range(len(word)))" + " return all(grid[add(start, mul(dir, i))] == ch \n", + " for i, ch in enumerate(word))" ] }, { @@ -654,10 +671,10 @@ "\n", "def x_search(grid: Grid, word='MAS') -> int:\n", " \"\"\"How many times does an X-MAS appear in the grid?\"\"\"\n", - " A = word[1] # The letter in the middle of the cross\n", + " A_positions = grid.findall(word[1]) # All positions where an 'A' (the mid-letter 'MAS') appears\n", " return quantify((grid_can_spell(grid, sub(mid_pos, dir1), dir1, word) and\n", " grid_can_spell(grid, sub(mid_pos, dir2), dir2, word))\n", - " for mid_pos in grid.findall(A) # All positions where A appears\n", + " for mid_pos in A_positions\n", " for dir1, dir2 in diagonal_pairs)" ] }, @@ -694,7 +711,7 @@ "\n", "\n", "\n", - "I mostly like my `parse` function: it is easy to tell it how to break the input into sections and how to parse every section. But I admit my `parse` is not ideal when an input file has sections with two different formats. I'll parse the two sections as paragraphs, and then call `parse` again on each paragraph:" + "I mostly like my `parse` function: it is easy to tell it how to break the input into sections and how to parse every section, if all the sections are the same. But I admit my `parse` is not ideal when an input file has sections with two different formats. I'll parse the two sections as paragraphs, and then call `parse` again on each paragraph:" ] }, { @@ -908,7 +925,7 @@ "id": "53b1ccbc-01ae-43d0-a75f-3f9389fdd3c9", "metadata": {}, "source": [ - "I have to say, I'm pleased that this day I got both parts right with no errors (and in fact, the same for the previous days). I was worried I might have my `+1` and `-1` backwards in `cmp_to_key`, but so far, everything has gone smoothly. (However, even if I started solving the moment that the puzzles are released, I wouldn't show up on the leaderboard; I'm still *way* slower than the skilled contest programmers." + "I have to say, I'm pleased that so far I've had no bugs (other than simple typos like not closing a paren). Today I was worried I might have my `+1` and `-1` backwards in `cmp_to_key`, but so far, everything has gone smoothly. However, I have no chance of showing up on the leaderboard; I'm still *way* slower than skilled contest programmers, not to mention the automated large language models that some people are using." ] }, { @@ -918,7 +935,7 @@ "source": [ "# [Day 6](https://adventofcode.com/2024/day/6): Guard Gallivant\n", "\n", - "Today's input is a 2D map of the manufacturing lab, with \"`.`\" indicating an empty space, \"`#`\" indicating an obstruction, and \"`^`\" indicating the position of the security guard." + "Today's input is a 2D map of the manufacturing lab, with \"`.`\" indicating an empty space, \"`#`\" indicating an obstruction, and \"`^`\" indicating the position of the security guard, and the fact that the guard is initially facing North." ] }, { @@ -961,7 +978,7 @@ "- 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 puzzle statement specifically says that can't happen (the guard will always march off the grid), so I won't test for it." + "I'll define `follow_path` to return 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 specifically says that can't happen (the guard will always march off the grid), so I won't test for it." ] }, { @@ -974,7 +991,7 @@ "def follow_path(grid: Grid, guard='^', facing=North) -> List[Point]:\n", " \"\"\"A list of all points in the path followed by the guard.\n", " The guard turns right when there is an obstacle ahead, otherwise goes forward.\"\"\"\n", - " path = grid.findall(guard) # A one-element list of positions, e.g. [(3, 4)]\n", + " path = grid.findall(guard) # A list of exactly one position, e.g. [(3, 4)]\n", " while (ahead := add(path[-1], facing)) in grid:\n", " if grid[ahead] == '#':\n", " facing = make_turn(facing, 'R')\n", @@ -1010,18 +1027,20 @@ "id": "eaf72ac3-ade0-4479-a090-1d0f292ecc27", "metadata": {}, "source": [ - "I initially had a **bug**; I returned for the length of the **path**, not the length of the **set** of positions in the path. Since the path crosses itself these two numbers are different.\n", + "I initially had a **bug**; I took the **length** of the path, not the \"distinct positions\", which I get by taking the length of the **set** of positions. For a path that crosses itself these two numbers are different.\n", " \n", "### Part 2: How many different positions could you choose for an obstruction to put the guard in a loop?\n", "\n", "The historians would like to place a single obstacle so that the guard *will* get stuck in a loop, rather than exiting the grid. They want to know all possible positions for the obstacle. What do we know about such positions?\n", "- An obstacle position must be somewhere on the guard's path, otherwise it would have no effect.\n", "- The instructions say it can't be the guard's initial position.\n", - "- A loop is when the guard's path returns to the same position with the same facing. This means my Part 1 solution, which returns the list of visited positions, is not helpful: it is not a loop if the guard is in position *p* facing East and then later returns to position *p* facing South. \n", - "- Thus, I can detect a loop by keeping a set of previously visited position/facing pairs.\n", - "- I can make slightly less work by only storing the *corners* of the path: the places where the guard turns. \n", + "- A loop is when the guard's path returns to the same position with the same facing. This means my Part 1 solution, which returns the list of visited positions, without the facing directions, is not immediately helpful. \n", + "- I can detect a loop by keeping a set of previously visited (position, facing) pairs.\n", + "- I can save time and space by only storing the *corners* of the path: the places where the guard turns.\n", + " - If the guiard is in a loop, the guard must turn the same way at the same corner.\n", "- The simplest approach for finding obstacle positions is to temporarily place an obstacle on each point on the path, one at a time, and see if it leads to a loop.\n", - "- There are 5,329 positions on the path, so the runtime should be about 5,000 times longer than Part 1; on the order of 10 seconds or so. I'll try it, and if it seems too slow, I'll try to think of something better." + "- There are 5,329 positions on the path, so the runtime could be about 5,000 times longer than Part 1; on the order of 10 seconds or so.\n", + " - I'll try it, and if it seems too slow, I'll think of something else." ] }, { @@ -1033,13 +1052,13 @@ "source": [ "def is_loopy_path(grid: Grid, guard_pos, facing=North) -> bool:\n", " \"\"\"Does the path followed by the guard form a loop?\"\"\"\n", - " path = {(guard_pos, facing)}\n", + " previous_positions = {(guard_pos, facing)}\n", " while (ahead := add(guard_pos, facing)) in grid:\n", " if grid[ahead] == '#':\n", " facing = make_turn(facing, 'R')\n", - " if (guard_pos, facing) in path:\n", + " if (guard_pos, facing) in previous_positions:\n", " return True # Found a loop\n", - " path.add((guard_pos, facing))\n", + " previous_positions.add((guard_pos, facing))\n", " else:\n", " guard_pos = ahead\n", " return False\n", @@ -1063,7 +1082,7 @@ { "data": { "text/plain": [ - "Puzzle 6.2: 1.977 seconds, answer 2162 ok" + "Puzzle 6.2: 1.975 seconds, answer 2162 ok" ] }, "execution_count": 63, @@ -1235,7 +1254,7 @@ "source": [ "### Part 2: What is the total calibration result of possibly true equations, allowing concatenation?\n", "\n", - "In Part 2, a third operator is allowed: concatentation. The equation \"`192: 17 8 14`\" can be balanced by concatenated 17 and 8 to get 178, and then adding 14 to get 192: \"`192 = ((17 || 8) + 14)`\". With three operators, the equation with 11 operators now has 311 = 177,147 possibilities, almost 100 times more than Part 1, so this will take longer:" + "In Part 2, a third operator is allowed: **concatentation**. The equation \"`192: 17 8 14`\" can be balanced by concatenated 17 and 8 to get 178, and then adding 14 to get 192: \"`192 = ((17 || 8) + 14)`\". With three possible operators, the equation with 11 missing operator slots now has 311 = 177,147 possibilities, almost 100 times more than Part 1, so this will take longer:" ] }, { @@ -1257,7 +1276,7 @@ { "data": { "text/plain": [ - "Puzzle 7.2: .799 seconds, answer 150077710195188 ok" + "Puzzle 7.2: .802 seconds, answer 150077710195188 ok" ] }, "execution_count": 75, @@ -1308,7 +1327,7 @@ { "data": { "text/plain": [ - "Puzzle 7.2: .600 seconds, answer 150077710195188 ok" + "Puzzle 7.2: .602 seconds, answer 150077710195188 ok" ] }, "execution_count": 78, @@ -1380,7 +1399,7 @@ "outputs": [], "source": [ "def antinodes(antennas: Grid) -> Set[Point]:\n", - " \"\"\"The set of all antinodal points in the grid.\n", + " \"\"\"The set of all antinodal points for all antennas in the grid.\n", " (That is, points that are of distance d and 2d from same frequency antennas.)\"\"\"\n", " groups = [antennas.findall(f) for f in set(antennas.values()) if f != '.']\n", " return union(antinodes2(A, B, antennas)\n", @@ -1389,8 +1408,8 @@ "\n", "def antinodes2(A: Point, B: Point, antennas: Grid) -> Set[Point]:\n", " \"\"\"The set of antinodal points for two antenna points, A and B.\"\"\"\n", - " return {P for P in {sub(mul(A, 2), B), sub(mul(B, 2), A)}\n", - " if P in antennas}" + " candidates = [sub(mul(A, 2), B), sub(mul(B, 2), A)]\n", + " return {P for P in candidates if P in antennas}" ] }, { @@ -1420,9 +1439,9 @@ "id": "ff79d605-813a-46ac-8473-a1198be0e99f", "metadata": {}, "source": [ - "### Part 2: How many unique locations within the bounds of the map contain an updated antinode?\n", + "### Part 2: How many unique locations within the bounds of the map contain an resonant antinode?\n", "\n", - "For Part 2, an **updated antinode** can now occur at *any* point that is exactly on line with two antennas of the same frequency, regardless of distance. So if the two antennas are *A* and *B* then the antinodal points can be found by starting at *A* and going step by step in the direction of the vector *A* - *B* and also in the direction *B* - *A*, going as far as you can while staying on the grid. The `Grid.follow_line` method facilitates that.\n", + "For Part 2, a Historian tells us to consider **resonant antinodes**, which can occur at *any* point that is exactly on line with two antennas of the same frequency, regardless of distance. So if the two antennas are *A* and *B* then the antinodal points can be found by starting at *A* and going step by step in the direction of the vector *A* - *B* and also in the direction *B* - *A*, going as far as you can while staying on the grid. The `Grid.follow_line` method facilitates that.\n", "\n", "I'll refactor `antinodes` to take an `antinodes2` parameter so it can handle both parts:" ] @@ -1441,8 +1460,8 @@ " for points in groups\n", " for A, B in combinations(points, 2))\n", "\n", - "def updated_antinodes2(A: Point, B: Point, antennas: Grid) -> Set[Point]:\n", - " \"\"\"The set of updated antinodal points for two antenna points, A and B.\"\"\"\n", + "def resonant_antinodes2(A: Point, B: Point, antennas: Grid) -> Set[Point]:\n", + " \"\"\"The set of resonant antinodal points for two antenna points, A and B.\"\"\"\n", " return (set(antennas.follow_line(A, sub(A, B))) | \n", " set(antennas.follow_line(A, sub(B, A))))" ] @@ -1488,15 +1507,94 @@ ], "source": [ "answer(8.2, 813, lambda:\n", - " len(antinodes(antennas, updated_antinodes2)))" + " len(antinodes(antennas, resonant_antinodes2)))" ] }, { "cell_type": "markdown", - "id": "9696a986-b6a2-4530-b55b-9db959ef7485", + "id": "4ff92b7f-f9e9-4664-9fc5-0e9998aca47e", "metadata": {}, "source": [ - "I got both of these right the first time (except for some simple typos: a misplaced paren and typing `grid` when I meant the grid called `antennas`)." + "### Part 3: Visualization\n", + "\n", + "Here I show what the grid look like with all the resonant antinodes depicted with the same letter as their antenna." + ] + }, + { + "cell_type": "code", + "execution_count": 89, + "id": "4642cbba-16c1-4aa8-b39d-a104a475cbe6", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "..........E7...xH..s....B.C........4..W..........5\n", + ".........4O.5..H3...t..h....3.s..C.3..zo..35b.....\n", + ".e.....x..x.7OH.x....k..8....CT...z.o5............\n", + "5...........XH...1...t.....s...5j...8z.T.3........\n", + "...........EH...ex.Tj3t.E5....3.C....E..k.O....c.w\n", + ".30........Hh...Kb.5.t.cs......O.S..z..k3....9..Eo\n", + ".......3..H..5.7..x.O..B..........9C3..zcb....o.wX\n", + ".......5.O.x.3.......st9..3...4C3j..W..3...o.W....\n", + ".5.0....H...9......x.........o...c....C.o......w..\n", + ".9H....H.c...sex.71...kj.3.....b...E.o3..K.....w..\n", + "......H..........67.x.....cO..C3.T3.5..8.C..E.K..S\n", + "sW..0H....H...5s.........B6....o..j..Cb.z..c......\n", + ".s..6O.....s.EHx...7.bBt....o.......8.....e3Cw..S.\n", + "..sH.k..O...s.....H..H.E.o.e.C......3..zT.......X3\n", + "T.Hs.0.....ke.......H7x..4j..H...EO..9..K.z.w..C..\n", + ".H..s....T.H.....k.o.5..H.H........3.H..E.G..0....\n", + "H....s.......1E5o.T.x.7x...HC.j.4..b..3C.3.w.HST..\n", + ".O....4......oH....x....t..T.k....3S....e08.z.....\n", + ".......s..o..........B.7x.........HHT.H0zOw..S....\n", + "...s...Os.e......H.......b.C5j.E.3C.j0.K3kHz.T31.J\n", + "..g.o..0.s.....E........tx.........0E....w..S.HX..\n", + "so...s....s.....5...H....1....K.b0w....3.Cz...H.J.\n", + "....KS...o.s..........Ex.tx7X..0........9K3S.3..O.\n", + "........0...s6.........Hkx.7.9.3...5Sj.wez.....Jz.\n", + ".4.O....e....s..E...BT....wx1E..j.....Kw..S.z8....\n", + ".E....O.....K.s..........CH...38E.j......W..3.J...\n", + "....h...o0.g...s.5.....e.w..xb7.......wT3S8C.....j\n", + "02.8........S...s.wW.0....txT3.......wj...5.cJX..Z\n", + "..J...k.......84.E.0K...Ct...xW......w.8S.z...3..G\n", + "...0E.e...0......0sk.......E3...H..j.K......JzT...\n", + "..........w....0.gks.....kttE.x777.ww..S...9..GG..\n", + ".....O0......0.Tw.5.sE.C..b3K......HG..j.3.JzC..35\n", + "So..2......0......ESg9.....t...x.C.w..S....1G..z..\n", + "B.w4...E.0..........8ws...3.t...1b....H...Jz...X..\n", + "....e.B0.....h.......1Cs.E....8.x.w.CSjWG.T..XZ...\n", + "6....0..S.1.0.k.........E3..W.........S.jJ.......z\n", + "...0...w.6.......WB5...b.sg8....wx.7SG....3..z.C..\n", + ".0..O.W2..E....0..6..C..3.S.tT.w...x.7..J...K.....\n", + ".........T...0..S.......8..6tgB.w.xS...7....z.9...\n", + "..e...............0....E....st...G.K67.J.j....EH..\n", + "...2W...............C8.......s.wg.Sx.b...EB..6....\n", + "........28..j90W....b03.S.....s.....E.JS...3X.z.BC\n", + ".....4..k.2..2...B8...h...WjGtwE.S.gx..x..7..T...z\n", + "..oO...h..........2C.3..0.ESt...s....J....j..zg...\n", + "e.....G........0.....E.2.G.w.w..SsK..xg.x.7Jj...W.\n", + "...T....w....jGSE...3......w2..6C.b.J..1.7.......X\n", + ".........S.E8....bC..5G.....w..K.2.s..x..G..37.X.9\n", + "...Sk.E...B..2..0..3.....S....T....Js.2.Sb.x.X....\n", + ".E.......8......B..G..E....w..St.....sGxZ..Xg.z...\n", + "4.O..6..G........C3E....W....2..tKJ.g.C..X....Gj2.\n" + ] + } + ], + "source": [ + "def show_antinodes(antennas: Grid, antinodes2=resonant_antinodes2):\n", + " \"\"\"Put antinodes on the grid and print.\"\"\"\n", + " antennas = antennas.copy() # Don't alter the original\n", + " groups = {f: antennas.findall(f) for f in set(antennas.values()) if f != '.'}\n", + " for f, points in groups.items():\n", + " for A, B in combinations(points, 2):\n", + " for p in antinodes2(A, B, antennas):\n", + " antennas[p] = f\n", + " antennas.print()\n", + " \n", + "show_antinodes(antennas.copy())" ] }, { @@ -1511,7 +1609,7 @@ }, { "cell_type": "code", - "execution_count": 90, + "execution_count": 91, "id": "0e944f9e-5c16-440c-b12e-178058a87048", "metadata": {}, "outputs": [ @@ -1564,7 +1662,7 @@ }, { "cell_type": "code", - "execution_count": 92, + "execution_count": 93, "id": "76e8454d-a2f3-4b6b-92df-182116cf46e0", "metadata": {}, "outputs": [], @@ -1596,17 +1694,17 @@ }, { "cell_type": "code", - "execution_count": 93, + "execution_count": 94, "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": 93, + "execution_count": 94, "metadata": {}, "output_type": "execute_result" } @@ -1629,12 +1727,12 @@ "\n", "To find all the slices that indicate files, it is easier to run through the disk map than the disk layout. The function `file_slices` quickly finds all such slices.\n", "\n", - "Finding a free space for a file is more difficult, because we need to find one that is big enough. At first I coded a solution that ran through the whole layout from left-to-right each time. That made it *O*(*n*2). So I added a table: `starts[length]` tells me where to start looking for an empty space of size `length`. That speeds things up." + "Finding a free space for a file is more difficult than finding a single free block, because we need to find free space that is big enough. At first I coded a solution that ran through the whole layout from left-to-right each time, looking for the first sufficiently-large free space. That made it *O*(*n*2), but it should be *O*(*n*) So I added a table: `starts[length]` that tells me where to start looking for an empty space of size `length`, and updated the table every time I move a file. That speeds things up by a factor of 100." ] }, { "cell_type": "code", - "execution_count": 95, + "execution_count": 96, "id": "8d34e96a-8ea9-4f92-b0aa-839b216ea14b", "metadata": {}, "outputs": [], @@ -1662,12 +1760,12 @@ "def find_freespace(layout, file_slice, starts) -> Optional[slice]:\n", " \"\"\"Find a slice position big enough to fit the given file slice, or return None if there is no position.\"\"\"\n", " length = file_slice.stop - file_slice.start\n", - " run = 0\n", + " run = 0 # The number of consecutive empty spaces we have found so far\n", " for i in range(layout.index(empty, starts[length]), len(layout)):\n", " starts[length] = i\n", " if i >= file_slice.start:\n", " return None # We only want to move a file left, not right\n", - " elif layout[i] is empty:\n", + " elif layout[i] is empty: # Look for empty spaces one by one\n", " run += 1\n", " if run == length:\n", " return slice(i + 1 - length, i + 1)\n", @@ -1678,7 +1776,7 @@ }, { "cell_type": "code", - "execution_count": 96, + "execution_count": 97, "id": "e3036875-88d0-496e-9d2f-facd0e80a5b2", "metadata": {}, "outputs": [ @@ -1688,7 +1786,7 @@ "Puzzle 9.2: .021 seconds, answer 6353648390778 ok" ] }, - "execution_count": 96, + "execution_count": 97, "metadata": {}, "output_type": "execute_result" } @@ -1703,7 +1801,7 @@ "id": "24c0e7d7-6ac7-4e4b-9557-bd4e215ad0a9", "metadata": {}, "source": [ - "I got the right answer, but I confess I had an off-by-one **bug** in `find_freespace` on the first try, and another bug where I was moving a large file to the right (thereby uncompressing the disk) rather than leaving it in place." + "I got the right answer, but I confess I had an off-by-one **bug** in `find_freespace` on the first try, and another bug where I didn't check if `if i >= file_slice.start`, and was sometimes moving a large file to the right (thereby *uncompressing* the disk) rather than leaving it in place." ] }, { @@ -1718,7 +1816,7 @@ }, { "cell_type": "code", - "execution_count": 99, + "execution_count": 100, "id": "5804fb03-05f3-402f-b6cc-6804c5d22512", "metadata": {}, "outputs": [ @@ -1771,7 +1869,7 @@ }, { "cell_type": "code", - "execution_count": 101, + "execution_count": 102, "id": "76b5379e-ee19-4607-91b8-88ec7b38023f", "metadata": {}, "outputs": [], @@ -1787,17 +1885,17 @@ }, { "cell_type": "code", - "execution_count": 102, + "execution_count": 103, "id": "97cf05f7-fa56-4a90-b2d8-2cd4d9b81f95", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "Puzzle 10.1: .004 seconds, answer 744 ok" + "Puzzle 10.1: .005 seconds, answer 744 ok" ] }, - "execution_count": 102, + "execution_count": 103, "metadata": {}, "output_type": "execute_result" } @@ -1821,7 +1919,7 @@ }, { "cell_type": "code", - "execution_count": 104, + "execution_count": 105, "id": "b763450f-a565-4936-bee4-e531c2eeebdb", "metadata": {}, "outputs": [], @@ -1847,7 +1945,7 @@ }, { "cell_type": "code", - "execution_count": 106, + "execution_count": 107, "id": "f8a87032-6556-4fc9-9bb8-573611aee8dc", "metadata": {}, "outputs": [ @@ -1857,7 +1955,7 @@ "Puzzle 10.2: .006 seconds, answer 1651 ok" ] }, - "execution_count": 106, + "execution_count": 107, "metadata": {}, "output_type": "execute_result" } @@ -1872,14 +1970,14 @@ "id": "af410d30-7096-4be6-bb20-904b3c8e2f59", "metadata": {}, "source": [ - "Today I went pretty fast (for me); I started a few minutes late and finished in 15 minutes. From the point of view of a competitive coder I did foolish things like write docstrings and use variables of more than one letter, so while this time was fast for me, it placed well out of the top 1000.\n", + "### Part 3: Visualization\n", "\n", "Here's a visualization of the map:" ] }, { "cell_type": "code", - "execution_count": 108, + "execution_count": 109, "id": "eea7e277-4687-48a9-85b5-a031a9d4b6e1", "metadata": {}, "outputs": [ @@ -1919,7 +2017,7 @@ }, { "cell_type": "code", - "execution_count": 110, + "execution_count": 111, "id": "76b68cef-d8de-4145-b65c-b254fedf1671", "metadata": {}, "outputs": [ @@ -1963,7 +2061,7 @@ }, { "cell_type": "code", - "execution_count": 112, + "execution_count": 113, "id": "1513df56-3d6f-42cf-8aec-1bdbeb991d90", "metadata": {}, "outputs": [], @@ -1989,17 +2087,17 @@ }, { "cell_type": "code", - "execution_count": 113, + "execution_count": 114, "id": "eff17cd0-a2c7-4d69-bc55-c0ef97917915", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "Puzzle 11.1: .068 seconds, answer 194482 ok" + "Puzzle 11.1: .067 seconds, answer 194482 ok" ] }, - "execution_count": 113, + "execution_count": 114, "metadata": {}, "output_type": "execute_result" } @@ -2025,7 +2123,7 @@ }, { "cell_type": "code", - "execution_count": 115, + "execution_count": 116, "id": "707b5a97-0296-48df-bdab-e34064cc67c2", "metadata": {}, "outputs": [], @@ -2050,7 +2148,7 @@ }, { "cell_type": "code", - "execution_count": 117, + "execution_count": 118, "id": "efdcdbf8-e8ec-4a85-9d09-90a20e08c66a", "metadata": {}, "outputs": [ @@ -2060,7 +2158,7 @@ "Puzzle 11.1: .002 seconds, answer 194482 ok" ] }, - "execution_count": 117, + "execution_count": 118, "metadata": {}, "output_type": "execute_result" } @@ -2072,17 +2170,17 @@ }, { "cell_type": "code", - "execution_count": 118, + "execution_count": 119, "id": "657b1f13-ffcc-44c6-84f1-398fa2fcdac7", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "Puzzle 11.2: .058 seconds, answer 232454623677743 ok" + "Puzzle 11.2: .062 seconds, answer 232454623677743 ok" ] }, - "execution_count": 118, + "execution_count": 119, "metadata": {}, "output_type": "execute_result" } @@ -2099,7 +2197,62 @@ "source": [ "I'm glad I used the `Counter`; it would have taken a petabyte of storage (and a long, long time) to represent that many stones as a list.\n", "\n", - "Again, I did pretty well, with no errors, and moving at what I thought was a good pace, but I didn't even crack the top 2000 on the leaderboard. " + "Again, I did pretty well, with no errors, and moving at what I thought was a good pace, but I didn't even crack the top 2000 on the leaderboard. \n", + "\n", + "### Part 3: Exploration\n", + "\n", + "Here I show how the stones grow, starting from a single stone marked \"0\" for 10 blinks, and how the total number of stones grows over 30 blinks." + ] + }, + { + "cell_type": "code", + "execution_count": 121, + "id": "8609fda3-c158-4fbb-bec5-4270c3a2dbe7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[1]\n", + "[2024]\n", + "[20, 24]\n", + "[2, 0, 2, 4]\n", + "[4048, 1, 4048, 8096]\n", + "[40, 48, 2024, 40, 48, 80, 96]\n", + "[4, 0, 4, 8, 20, 24, 4, 0, 4, 8, 8, 0, 9, 6]\n", + "[8096, 1, 8096, 16192, 2, 0, 2, 4, 8096, 1, 8096, 16192, 16192, 1, 18216, 12144]\n", + "[80, 96, 2024, 80, 96, 32772608, 4048, 1, 4048, 8096, 80, 96, 2024, 80, 96, 32772608, 32772608, 2024, 36869184, 24579456]\n", + "[8, 0, 9, 6, 20, 24, 8, 0, 9, 6, 3277, 2608, 40, 48, 2024, 40, 48, 80, 96, 8, 0, 9, 6, 20, 24, 8, 0, 9, 6, 3277, 2608, 3277, 2608, 20, 24, 3686, 9184, 2457, 9456]\n" + ] + } + ], + "source": [ + "S = [0]\n", + "for _ in range(10):\n", + " S = blink(S, 1)\n", + " print(S)" + ] + }, + { + "cell_type": "code", + "execution_count": 122, + "id": "100febf3-8397-4bce-a290-12de1f281f82", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1, 1, 2, 4, 4, 7, 14, 16, 20, 39, 62, 81, 110, 200, 328, 418, 667, 1059, 1546, 2377, 3572, 5602, 8268, 12343, 19778, 29165, 43726, 67724, 102131, 156451, " + ] + } + ], + "source": [ + "S = [0]\n", + "for _ in range(30):\n", + " S = blink(S, 1)\n", + " print(len(S), end=', ')" ] }, { @@ -2109,12 +2262,12 @@ "source": [ "# [Day 12](https://adventofcode.com/2024/day/12): Garden Groups\n", "\n", - "Today's input is yet another 2D map. This one depicts different garden plots on a farm, each plot planted with a crop, indicated by a letter. Perhaps \"I\" is iceberg lettuce and \"O\" is okra, and so on." + "Today's input is yet another 2D map. This one depicts different garden **plots** on a farm, each plot planted with a **crop**, indicated by a letter. Perhaps \"I\" is iceberg lettuce, \"O\" is okra, \"A\" is avocado, and so on." ] }, { "cell_type": "code", - "execution_count": 121, + "execution_count": 124, "id": "8161ee7e-76e3-499a-abf8-a607991c9602", "metadata": {}, "outputs": [ @@ -2148,12 +2301,12 @@ "source": [ "### Part 1: What is the total price of fencing all regions on your map?\n", "\n", - "We are asked to calculate the cost of putting fences around each **region** (a region is a set of garden plots with the same crop that abut each other horizontally or vertically). The price of the fence for a region is defined as the product of the region's area and its perimeter. If we represent a region as a set of (x, y) points, then the area is easy: it is just the number of points. The perimeter length can be computed by, for each plot point in the region, looking at each of the four directions and counting cases where the adjacent plot in that direction is *not* in the region. (Initially I had a **bug** in that I looked at the `farm.neighbors` of each plot. That doesn't work because a plot on the edge of the grid should count as part of the perimeter, so we have to explicitly check all four directions.)" + "We are asked to calculate the cost of putting fences around each region. A **region** is a set of garden plots with the same crop that abut each other horizontally or vertically. The **price** of the fence for a region is defined as the product of the region's area and its perimeter. (That seems like a strange way to do pricing, but what do I know.) If we represent a region as a **set** of (x, y) points, then the area is easy: it is just the number of points. The perimeter can be computed by, for each plot point in the region, counting the neighbors (in the 4 directions) that are *not* in the region." ] }, { "cell_type": "code", - "execution_count": 123, + "execution_count": 126, "id": "79f91f38-e325-44f2-9e53-b64ce12d9d35", "metadata": {}, "outputs": [], @@ -2168,10 +2321,10 @@ "def region_price(region) -> int: return region_area(region) * perimeter_length(region)\n", "\n", "def perimeter_length(region: Region) -> int:\n", - " \"\"\"The number of sides on the perimeter of the region: \n", - " ones where the adjacent position in some direction is not in the region.\"\"\"\n", - " return quantify(add(plot, d) not in region \n", - " for plot in region for d in directions4)" + " \"\"\"The number of sides on the perimeter of the region:\n", + " sides where the neighboring position in the given direction is not in the region.\"\"\"\n", + " return quantify(neighbor not in region \n", + " for plot in region for neighbor in neighbors(plot))" ] }, { @@ -2179,12 +2332,12 @@ "id": "9524ee15-c378-4cd4-a79b-14fb99b17cb3", "metadata": {}, "source": [ - "To find all the regions I'll start at a point and do a [flood fill](https://en.wikipedia.org/wiki/Flood_fill) to neighboring points with the same region letter, keeping track of points that have already been found so as to not duplicate them. The function `all_regions` iterates over all points to make sure it finds every region, and `flood_fill` recursively expands to all points that neighbor `p` and have the same crop (letter). `flood_fill` mutates the set `already_found` as it goes (and also mutates the `region` it is building up)." + "To find all the regions I'll start at a point and do a [flood fill](https://en.wikipedia.org/wiki/Flood_fill) to neighboring points with the same region letter, keeping track of points that have already been found. The function `all_regions` iterates over all points to make sure it finds every region, and `flood_fill` recursively expands to all points that neighbor `p` and have the same crop (letter). `flood_fill` mutates the set `already_found` as it goes (and also mutates the `region` it is building up)." ] }, { "cell_type": "code", - "execution_count": 125, + "execution_count": 128, "id": "1fbabbfb-50c8-4197-8517-e7cee9582765", "metadata": {}, "outputs": [], @@ -2208,7 +2361,7 @@ }, { "cell_type": "code", - "execution_count": 126, + "execution_count": 129, "id": "cdaf655b-d12c-4973-b19b-3132e5e691c6", "metadata": {}, "outputs": [ @@ -2218,7 +2371,7 @@ "Puzzle 12.1: .032 seconds, answer 1402544 ok" ] }, - "execution_count": 126, + "execution_count": 129, "metadata": {}, "output_type": "execute_result" } @@ -2233,13 +2386,13 @@ "id": "b3383560-2bbc-4dfc-b643-feb103876823", "metadata": {}, "source": [ - "### Part 2: What is the new total price of fencing all regions on your map, with the bulk discount?\n", + "### Part 2: What is the total price of fencing all regions on your map, with the bulk discount?\n", "\n", - "In Part 2 we get a **bulk discount** on the fencing; we only need to pay for the number of straight line sides on the perimeter, not the total length of the perimeter. For example, a 10 x 10 square has perimeter 40, but has only 4 sides. That's a 90% discount!\n", + "In Part 2 we get a **bulk discount** on the fencing; we only need to pay for the number of **straight line sides** on the perimeter, not the total length of the perimeter. For example, a 10 x 10 square has perimeter 40, but has only 4 sides. That's a 90% discount!\n", "\n", - "It took me a while to figure out a good approach for this. At first I was reminded of the Convex Hull problem, for which I have [a notebook](https://github.com/norvig/pytudes/blob/main/ipynb/Convex%20Hull.ipynb). But that's not really appropriate here; our regions could be non-convex, and the set of sides in a region are not the same as the vertexes of a polygon (e.g., a region with one point has 4 sides, not 0).\n", + "It took me a while to figure out a good approach for this. At first I was reminded of the Convex Hull problem, for which I have [a notebook](https://github.com/norvig/pytudes/blob/main/ipynb/Convex%20Hull.ipynb). But that's not really appropriate here; our regions could be non-convex, and the set of sides in a region are not the same as the vertexes of a polygon (e.g., a region with one point has 4 sides, not 0). (*Funny story:* I wondered if an AI large language model (LLM) for programming would get confused like I did on this. So I described the problem in a way that hinted at convex hull, and the LLM suggested sorting the points by their angle to the centroid (a good idea for the convex hull problem), and then returning the length of the list of points. So, (1) that's not the right answer, and (2) if it was the right answer, it would have been right before sorting.)\n", "\n", - "A better idea is to start with the perimeter length and subtract one for every case in which a points has an edge in one direction (e.g., an edge to the North) and also has a neighbor with the same edge. To be precise, I'll look for four cases:\n", + "A better idea is to start with the perimeter length and subtract one for every case in which a point has an edge in one direction (e.g., an edge to the North) and also has a neighbor with the same edge. To be precise, I'll look for four cases:\n", "- A point with an edge to the North whose neighbor to the East also has an edge to the North\n", "- A point with an edge to the East whose neighbor to the South also has an edge to the East\n", "- A point with an edge to the South whose neighbor to the West also has an edge to the South\n", @@ -2258,19 +2411,20 @@ }, { "cell_type": "code", - "execution_count": 128, + "execution_count": 131, "id": "38c30e15-3a33-40c2-b734-163a15af7a8a", "metadata": {}, "outputs": [], "source": [ - "def fence_price(farm: Grid, region_price=region_price) -> int:\n", + "def fence_price(farm: Grid, region_price:Callable=region_price) -> int:\n", " \"\"\"Total price of fences for all the regions in the farm, given the price function for a region.\"\"\"\n", " return sum(map(region_price, all_regions(farm)))\n", "\n", "def discount_region_price(region) -> int: return region_area(region) * region_sides(region)\n", " \n", "def region_sides(region):\n", - " \"\"\"How many straight-line sides does this region have?\"\"\"\n", + " \"\"\"How many straight-line sides does this region have?\n", + " The perimeter minus all sides that are the same as their clockwise neighbor.\"\"\"\n", " def has_edge(p: Point, d: Vector): return p in region and add(p, d) not in region\n", " def neighbor(p: Point, d: Vector): return add(p, make_turn(d, 'R'))\n", " subtract = quantify(has_edge(p, d) and has_edge(neighbor(p, d), d)\n", @@ -2281,7 +2435,7 @@ }, { "cell_type": "code", - "execution_count": 129, + "execution_count": 132, "id": "72175812-dcd0-4f1b-9efa-0dceeeafa609", "metadata": {}, "outputs": [ @@ -2291,7 +2445,7 @@ "Puzzle 12.1: .052 seconds, answer 1402544 ok" ] }, - "execution_count": 129, + "execution_count": 132, "metadata": {}, "output_type": "execute_result" } @@ -2303,7 +2457,7 @@ }, { "cell_type": "code", - "execution_count": 130, + "execution_count": 133, "id": "9defcd35-91bc-41d4-a16f-bb7a4ede75e7", "metadata": {}, "outputs": [ @@ -2313,7 +2467,7 @@ "Puzzle 12.2: .043 seconds, answer 862486 ok" ] }, - "execution_count": 130, + "execution_count": 133, "metadata": {}, "output_type": "execute_result" } @@ -2330,14 +2484,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.\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 (which is different for each machine). The prize is at a specified location (also different for each machine). We can parse the input by defining a `parse_claw` function and a `Claw` datatype to represent the machine.\n", "\n", "" ] }, { "cell_type": "code", - "execution_count": 132, + "execution_count": 135, "id": "e78f45c0-c420-4661-aad2-14e122b4473b", "metadata": {}, "outputs": [ @@ -2376,7 +2530,7 @@ "Claw = namedtuple('Claw', 'A, B, Prize') # A claw machine\n", "\n", "def parse_claw(text: str) -> Claw:\n", - " \"\"\"Parse a description into a Claw object.\"\"\"\n", + " \"\"\"Parse a paragraph of text into a Claw object.\"\"\"\n", " ax, ay, bx, by, px, py = ints(text)\n", " return Claw((ax, ay), (bx, by), (px, py))\n", " \n", @@ -2390,18 +2544,18 @@ "source": [ "### Part 1: What is the fewest tokens you would have to spend to win all possible prizes?\n", "\n", - "We are told that for some machines it is possible to reach the prize and others it is impossible. We are also told that you need to spend 3 tokens to press the **A** button and 1 token to press the **B** button. There is a hint that no button will need to be pushed more than 100 times. It is usually a good idea to accept the hint, so I will do the following:\n", + "We are told that for some machines it is possible to reach the prize and others it is impossible. We are also told that you need to spend 3 **tokens** to press the **A** button and 1 token to press the **B** button. There is a hint that no button will need to be pushed more than **100 times**. It is usually a good idea to accept the hint, so I will do the following:\n", "- Exhaustively try every number from 0 to 100 presses of the **A** button.\n", "- For each number, figure out what the resulting position of the claw would be after those presses.\n", "- Calculate how many presses of the **B** button would be required to get to the prize's X location.\n", - "- If the number of presses is an integer, and would also arrive at the prize's Y location, then yield that solution.\n", + "- If the number of presses is an integer, and would also arrive at the prize's Y location, then yield that (A-presses, B-presses) solution.\n", "- Out of these solutions, the cheapest solution is the one that needs the fewest tokens.\n", "- (If there are no solutions, `cheapest_solution` will return 0, whcih is convenient for adding the results up, but is worrisome because it doesn't distinguish between an impossible-to-reach prize and a prize at the origin. On this puzzle, that's ok, but it is still worrisome.)" ] }, { "cell_type": "code", - "execution_count": 134, + "execution_count": 137, "id": "c2c4bbc9-42cd-483d-8da2-97cf051e93fe", "metadata": {}, "outputs": [], @@ -2425,7 +2579,7 @@ }, { "cell_type": "code", - "execution_count": 135, + "execution_count": 138, "id": "f5638ed4-1e59-4b9f-b1fc-427d2eb0d036", "metadata": {}, "outputs": [ @@ -2435,7 +2589,7 @@ "Puzzle 13.1: .006 seconds, answer 29598 ok" ] }, - "execution_count": 135, + "execution_count": 138, "metadata": {}, "output_type": "execute_result" } @@ -2452,8 +2606,7 @@ "source": [ "### Part 2: Using the corrected prize coordinates, what is the fewest tokens you would have to spend to win all possible prizes?\n", "\n", - "In Part 2, we discover that \"due to a unit conversion error\" the prize is actually much farther away, by 10,000,000,000,000 (ten trillion) steps in both the X and Y coordinates. Theoretically, we could still use `solve_claw` with a bigger `limit`, but that would take a *long* time.\n", - "w\n", + "In Part 2, we discover that \"due to a unit conversion error\" the prize is actually **much** farther away, by 10,000,000,000,000 (ten trillion) steps in both the X and Y coordinates. Theoretically, we could still use `solve_claw` with a bigger `limit`, but that would take a *long* time.\n", "Instead I can solve each claw machine *mathematically* rather than by trial and error. It looks like each claw machine defines two equations; what exactly are the equations? For my first claw machine, \n", "\n", " Button A: X+24, Y+19\n", @@ -2465,14 +2618,14 @@ " 24 A + 32 B = 1072\n", " 19 A + 86 B = 2062\n", "\n", - "I know that an equation over the integers is called a [Diophantine equation](https://en.wikipedia.org/wiki/Diophantine_equation), but I don't remember much about them, and furthermore it seems like it is more important that we are dealing with lines than that they need integer solutions. In general, two linear equations in two unknowns can have zero solutions (if the lines are parallel), an infinite number of solutions (if the lines are the same), or exactly one solution (in the \"normal\" case). We can solve the linear equations, and then discard the answer if *A* and *B* are not integers. \n", + "I know that an equation over the integers is called a [Diophantine equation](https://en.wikipedia.org/wiki/Diophantine_equation), but I don't remember much about them, and furthermore it seems like it is more important that these are linear equations than that they are Diophantine equations. In general, two linear equations in two unknowns can have zero solutions (if the lines are parallel), an infinite number of solutions (if the lines are the same), or exactly one solution (the intersection of the lines; the \"normal\" case). We can solve the linear equations, and then discard the answer if *A* and *B* are not integers. \n", "\n", "I defined `solve_claw` to yield multiple solutions, but let's see how many solutions each claw machine yields:" ] }, { "cell_type": "code", - "execution_count": 137, + "execution_count": 140, "id": "df8da2ae-52f9-409b-a54f-ad7d21b32e45", "metadata": {}, "outputs": [ @@ -2482,7 +2635,7 @@ "Counter({0: 168, 1: 152})" ] }, - "execution_count": 137, + "execution_count": 140, "metadata": {}, "output_type": "execute_result" } @@ -2496,7 +2649,7 @@ "id": "f0f97d47-5741-4c6e-ae96-5b3161ee2e56", "metadata": {}, "source": [ - "This says that 168 claw machines have no solution and 152 have exactly one. None of them have more than one solution.\n", + "This says that 168 claw machines have no solution and 152 have exactly one solution. None of them have more than one solution.\n", "\n", "So, what is the intersection point of two lines? You could [look it up](https://www.wolframalpha.com/input?i=solve+for+a%2C+b%3A+p+*+a+%2B+q+*+b+%3D+r++and+++s+*+a+%2B+t+*+b+%3D+u) or work it out by hand, or [call a library function](https://numpy.org/doc/2.1/reference/generated/numpy.linalg.solve.html). Given the two equations over the variables *a* and *b*:\n", "\n", @@ -2513,7 +2666,7 @@ }, { "cell_type": "code", - "execution_count": 139, + "execution_count": 142, "id": "6bbd0934-d962-4c93-940b-810651e9e568", "metadata": {}, "outputs": [], @@ -2532,14 +2685,14 @@ "id": "af52d98e-50e9-4c4d-875f-1ed2b3dc8111", "metadata": {}, "source": [ - "This raises an interesting design point: If you have a function that might return zero or one result of type `T`, should you define the return type as `Optional[T]` or as `Iterable[T]`, where it can yield zero or one result? I guess it depends on the expected use cases of the function.\n", + "This raises an interesting style question: If you have a function that might return zero or one result of type `T`, should you define the return type as `Optional[T]` or as `Iterable[T]`? I guess it depends on the expected use cases of the function.\n", "\n", - "We'll need a way to move the prizes further away, then we're ready to answer Part 2:" + "We'll need a way to move the prizes further away:" ] }, { "cell_type": "code", - "execution_count": 141, + "execution_count": 144, "id": "dd38ba4c-44ba-426b-b1c8-0e10adbdd642", "metadata": {}, "outputs": [], @@ -2549,9 +2702,17 @@ " return [claw._replace(Prize=(add(claw.Prize, delta))) for claw in claws]" ] }, + { + "cell_type": "markdown", + "id": "285bfa66-a33e-4d97-b7a7-cfd821953091", + "metadata": {}, + "source": [ + "Now we can answer Part 2:" + ] + }, { "cell_type": "code", - "execution_count": 142, + "execution_count": 146, "id": "9f578b3e-6b6d-4eb0-9228-c98122a84747", "metadata": {}, "outputs": [ @@ -2561,7 +2722,7 @@ "Puzzle 13.2: .000 seconds, answer 93217456941970 ok" ] }, - "execution_count": 142, + "execution_count": 146, "metadata": {}, "output_type": "execute_result" } @@ -2581,7 +2742,7 @@ }, { "cell_type": "code", - "execution_count": 144, + "execution_count": 148, "id": "609ed4ce-548c-4af4-8e09-c621aca0124e", "metadata": {}, "outputs": [ @@ -2591,7 +2752,7 @@ "Puzzle 13.1: .000 seconds, answer 29598 ok" ] }, - "execution_count": 144, + "execution_count": 148, "metadata": {}, "output_type": "execute_result" } @@ -2621,7 +2782,7 @@ }, { "cell_type": "code", - "execution_count": 147, + "execution_count": 151, "id": "1a5f5875-426d-47ea-a35a-405c39ced5dd", "metadata": {}, "outputs": [ @@ -2674,7 +2835,7 @@ }, { "cell_type": "code", - "execution_count": 149, + "execution_count": 153, "id": "be22ac94-7401-4cf6-ab83-e43775536af7", "metadata": {}, "outputs": [], @@ -2696,7 +2857,7 @@ }, { "cell_type": "code", - "execution_count": 150, + "execution_count": 154, "id": "69093001-79aa-463a-b801-51cd5b4de4eb", "metadata": {}, "outputs": [ @@ -2706,7 +2867,7 @@ "Puzzle 14.1: .000 seconds, answer 216027840 ok" ] }, - "execution_count": 150, + "execution_count": 154, "metadata": {}, "output_type": "execute_result" } @@ -2726,7 +2887,7 @@ }, { "cell_type": "code", - "execution_count": 152, + "execution_count": 156, "id": "08661dc5-445c-4908-b0b4-57f3a53e5179", "metadata": {}, "outputs": [ @@ -2736,7 +2897,7 @@ "Counter({(1, 1): 124, (-1, 1): 122, (-1, -1): 120, (1, -1): 119})" ] }, - "execution_count": 152, + "execution_count": 156, "metadata": {}, "output_type": "execute_result" } @@ -2764,7 +2925,7 @@ }, { "cell_type": "code", - "execution_count": 154, + "execution_count": 158, "id": "664c686e-0c3d-43b8-970f-88c0bf47dbf6", "metadata": {}, "outputs": [], @@ -2796,7 +2957,7 @@ }, { "cell_type": "code", - "execution_count": 156, + "execution_count": 160, "id": "87843969-cb37-4fa5-9788-6a1b71c43521", "metadata": {}, "outputs": [ @@ -2988,42 +3149,42 @@ "\n", "\n", "
\n", - " \n", + " \n", "
\n", - " \n", + " oninput=\"anim523949980df7477489860b6ec8742e38.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", @@ -3033,9 +3194,9 @@ " /* Instantiate the Animation class. */\n", " /* The IDs given should match those used in the template above. */\n", " (function() {\n", - " var img_id = \"_anim_imge5889f6e232a4cbba19719532f4cbcc2\";\n", - " var slider_id = \"_anim_slidere5889f6e232a4cbba19719532f4cbcc2\";\n", - " var loop_select_id = \"_anim_loop_selecte5889f6e232a4cbba19719532f4cbcc2\";\n", + " var img_id = \"_anim_img523949980df7477489860b6ec8742e38\";\n", + " var slider_id = \"_anim_slider523949980df7477489860b6ec8742e38\";\n", + " var loop_select_id = \"_anim_loop_select523949980df7477489860b6ec8742e38\";\n", " var frames = new Array(3);\n", " \n", " frames[0] = \"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAoAAAAHgCAYAAAA10dzkAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90\\\n", @@ -4772,17 +4933,17 @@ " /* set a timeout to make sure all the above elements are created before\n", " the object is initialized. */\n", " setTimeout(function() {\n", - " anime5889f6e232a4cbba19719532f4cbcc2 = new Animation(frames, img_id, slider_id, 200.0,\n", + " anim523949980df7477489860b6ec8742e38 = new Animation(frames, img_id, slider_id, 200.0,\n", " loop_select_id);\n", " }, 0);\n", " })()\n", "\n" ], "text/plain": [ - "" + "" ] }, - "execution_count": 156, + "execution_count": 160, "metadata": {}, "output_type": "execute_result" } @@ -4803,7 +4964,7 @@ }, { "cell_type": "code", - "execution_count": 158, + "execution_count": 162, "id": "9563d49c-54a3-439f-a833-f48c2a070609", "metadata": {}, "outputs": [], @@ -4820,7 +4981,7 @@ }, { "cell_type": "code", - "execution_count": 159, + "execution_count": 163, "id": "ab8c7e3b-f400-4976-ad0d-5f92cbadec02", "metadata": {}, "outputs": [ @@ -5012,42 +5173,42 @@ "\n", "\n", "
\n", - " \n", + " \n", "
\n", - " \n", + " oninput=\"anim291d19a4dcbd4b0bab44c9de928d3b96.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", @@ -5057,9 +5218,9 @@ " /* Instantiate the Animation class. */\n", " /* The IDs given should match those used in the template above. */\n", " (function() {\n", - " var img_id = \"_anim_img7295292aa9074d0193a9f80b1d46cca1\";\n", - " var slider_id = \"_anim_slider7295292aa9074d0193a9f80b1d46cca1\";\n", - " var loop_select_id = \"_anim_loop_select7295292aa9074d0193a9f80b1d46cca1\";\n", + " var img_id = \"_anim_img291d19a4dcbd4b0bab44c9de928d3b96\";\n", + " var slider_id = \"_anim_slider291d19a4dcbd4b0bab44c9de928d3b96\";\n", + " var loop_select_id = \"_anim_loop_select291d19a4dcbd4b0bab44c9de928d3b96\";\n", " var frames = new Array(1);\n", " \n", " frames[0] = \"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAoAAAAHgCAYAAAA10dzkAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90\\\n", @@ -5448,17 +5609,17 @@ " /* set a timeout to make sure all the above elements are created before\n", " the object is initialized. */\n", " setTimeout(function() {\n", - " anim7295292aa9074d0193a9f80b1d46cca1 = new Animation(frames, img_id, slider_id, 200.0,\n", + " anim291d19a4dcbd4b0bab44c9de928d3b96 = new Animation(frames, img_id, slider_id, 200.0,\n", " loop_select_id);\n", " }, 0);\n", " })()\n", "\n" ], "text/plain": [ - "" + "" ] }, - "execution_count": 159, + "execution_count": 163, "metadata": {}, "output_type": "execute_result" } @@ -5482,17 +5643,17 @@ }, { "cell_type": "code", - "execution_count": 161, + "execution_count": 165, "id": "75434bc8-35ae-4d8b-b747-01d773472541", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "Puzzle 14.2: 1.866 seconds, answer 6876 ok" + "Puzzle 14.2: 1.884 seconds, answer 6876 ok" ] }, - "execution_count": 161, + "execution_count": 165, "metadata": {}, "output_type": "execute_result" } @@ -5514,7 +5675,7 @@ }, { "cell_type": "code", - "execution_count": 163, + "execution_count": 167, "id": "20be45ec-f6fc-472c-9b49-872b7334528c", "metadata": {}, "outputs": [ @@ -5565,7 +5726,7 @@ }, { "cell_type": "code", - "execution_count": 165, + "execution_count": 169, "id": "248d51c0-424e-4bbb-bbf4-37cdc18313e1", "metadata": {}, "outputs": [], @@ -5604,7 +5765,7 @@ }, { "cell_type": "code", - "execution_count": 166, + "execution_count": 170, "id": "52e93218-6ac2-4cf7-aced-f4f3a3df77b5", "metadata": {}, "outputs": [ @@ -5614,7 +5775,7 @@ "Puzzle 15.1: .027 seconds, answer 1563092 ok" ] }, - "execution_count": 166, + "execution_count": 170, "metadata": {}, "output_type": "execute_result" } @@ -5651,7 +5812,7 @@ }, { "cell_type": "code", - "execution_count": 168, + "execution_count": 172, "id": "2d9afbc1-75b2-479d-81fc-fbab69d25753", "metadata": {}, "outputs": [], @@ -5689,17 +5850,17 @@ }, { "cell_type": "code", - "execution_count": 169, + "execution_count": 173, "id": "7af49bbf-dd10-4221-9096-6f548dec44c0", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "Puzzle 15.1: .029 seconds, answer 1563092 ok" + "Puzzle 15.1: .030 seconds, answer 1563092 ok" ] }, - "execution_count": 169, + "execution_count": 173, "metadata": {}, "output_type": "execute_result" } @@ -5711,17 +5872,17 @@ }, { "cell_type": "code", - "execution_count": 170, + "execution_count": 174, "id": "99246602-a51e-41aa-a7e9-7cdbc8d449ca", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "Puzzle 15.2: .042 seconds, answer 1582688 ok" + "Puzzle 15.2: .043 seconds, answer 1582688 ok" ] }, - "execution_count": 170, + "execution_count": 174, "metadata": {}, "output_type": "execute_result" } @@ -5741,7 +5902,7 @@ }, { "cell_type": "code", - "execution_count": 172, + "execution_count": 176, "id": "47c99f0f-ab65-4ae1-a308-42668feacdd5", "metadata": {}, "outputs": [ @@ -5794,7 +5955,7 @@ }, { "cell_type": "code", - "execution_count": 175, + "execution_count": 179, "id": "03255fd5-95d9-4a90-b1bb-abdf6bbf1d85", "metadata": {}, "outputs": [ @@ -5839,7 +6000,7 @@ }, { "cell_type": "code", - "execution_count": 177, + "execution_count": 181, "id": "e6e4bfba-0170-436a-961b-d4749f2cd66e", "metadata": {}, "outputs": [], @@ -5872,7 +6033,7 @@ }, { "cell_type": "code", - "execution_count": 178, + "execution_count": 182, "id": "4974c14c-86da-4b86-a55c-6ae8781df207", "metadata": {}, "outputs": [ @@ -5882,7 +6043,7 @@ "Puzzle 16.1: .147 seconds, answer 103512 ok" ] }, - "execution_count": 178, + "execution_count": 182, "metadata": {}, "output_type": "execute_result" } @@ -5904,7 +6065,7 @@ }, { "cell_type": "code", - "execution_count": 180, + "execution_count": 184, "id": "692abfea-f9c8-477c-8369-386cdd2a7606", "metadata": {}, "outputs": [], @@ -5936,17 +6097,17 @@ }, { "cell_type": "code", - "execution_count": 181, + "execution_count": 185, "id": "ea9bf9f3-0e6b-4949-a641-6b3db2fd9d32", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "Puzzle 16.2: .860 seconds, answer 554 ok" + "Puzzle 16.2: .866 seconds, answer 554 ok" ] }, - "execution_count": 181, + "execution_count": 185, "metadata": {}, "output_type": "execute_result" } @@ -5963,12 +6124,12 @@ "source": [ "# [Day 17](https://adventofcode.com/2024/day/17): Chronospatial Computer\n", "\n", - "Today we have to help debug a faulty computer. This particular computer has three registers, A, B, and C, and a program consisting of a sequence of octal digits. The input is a description of the state of the computer; the values in the register and the stored program." + "Today we have to help debug a faulty computer. This particular computer has three registers, A, B, and C, and a program consisting of a sequence of octal digits. The input is a description of the state of the computer; the values in the register and the stored program. " ] }, { "cell_type": "code", - "execution_count": 183, + "execution_count": 187, "id": "50fd3cb8-0a6f-4edd-b5cb-f7b1e6f9d987", "metadata": {}, "outputs": [ @@ -6012,7 +6173,7 @@ }, { "cell_type": "code", - "execution_count": 184, + "execution_count": 188, "id": "c23b8a44-e352-4a30-abe5-3c939ffc743c", "metadata": {}, "outputs": [ @@ -6022,7 +6183,7 @@ "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": 184, + "execution_count": 188, "metadata": {}, "output_type": "execute_result" } @@ -6043,7 +6204,7 @@ }, { "cell_type": "code", - "execution_count": 186, + "execution_count": 190, "id": "d98f88cc-c435-43fc-bcb5-52e6dd70fdb1", "metadata": {}, "outputs": [], @@ -6078,7 +6239,7 @@ }, { "cell_type": "code", - "execution_count": 188, + "execution_count": 192, "id": "860f24e5-92ad-4361-8920-102ebc573598", "metadata": {}, "outputs": [ @@ -6088,7 +6249,7 @@ "Puzzle 17.1: .000 seconds, answer 2,1,0,1,7,2,5,0,3 ok" ] }, - "execution_count": 188, + "execution_count": 192, "metadata": {}, "output_type": "execute_result" } @@ -6132,7 +6293,7 @@ }, { "cell_type": "code", - "execution_count": 190, + "execution_count": 194, "id": "bb745303-dd20-486f-bbdd-7ae77f995c2c", "metadata": {}, "outputs": [], @@ -6144,7 +6305,7 @@ }, { "cell_type": "code", - "execution_count": 191, + "execution_count": 195, "id": "54ac9b9c-70d9-4356-91c5-40fb285634b4", "metadata": {}, "outputs": [ @@ -6154,7 +6315,7 @@ "[2, 4, 1, 7, 7, 5, 0, 3, 4, 4, 1, 7, 5, 5, 3, 0]" ] }, - "execution_count": 191, + "execution_count": 195, "metadata": {}, "output_type": "execute_result" } @@ -6165,7 +6326,7 @@ }, { "cell_type": "code", - "execution_count": 192, + "execution_count": 196, "id": "36d89d9a-c8fc-41be-820b-cb13e40793c0", "metadata": {}, "outputs": [ @@ -6175,7 +6336,7 @@ "[0]" ] }, - "execution_count": 192, + "execution_count": 196, "metadata": {}, "output_type": "execute_result" } @@ -6186,7 +6347,7 @@ }, { "cell_type": "code", - "execution_count": 193, + "execution_count": 197, "id": "892fef38-5f9b-4370-a242-f7a10df5487b", "metadata": {}, "outputs": [ @@ -6196,7 +6357,7 @@ "[3, 0]" ] }, - "execution_count": 193, + "execution_count": 197, "metadata": {}, "output_type": "execute_result" } @@ -6207,7 +6368,7 @@ }, { "cell_type": "code", - "execution_count": 194, + "execution_count": 198, "id": "b3d11d5e-30c2-419f-bc11-3f26fbdddfbb", "metadata": {}, "outputs": [ @@ -6217,7 +6378,7 @@ "[5, 3, 0]" ] }, - "execution_count": 194, + "execution_count": 198, "metadata": {}, "output_type": "execute_result" } @@ -6228,7 +6389,7 @@ }, { "cell_type": "code", - "execution_count": 195, + "execution_count": 199, "id": "9ffcefa7-714f-416f-b6c7-8a9ad6d65380", "metadata": {}, "outputs": [ @@ -6238,7 +6399,7 @@ "[5, 5, 3, 0]" ] }, - "execution_count": 195, + "execution_count": 199, "metadata": {}, "output_type": "execute_result" } @@ -6266,7 +6427,7 @@ }, { "cell_type": "code", - "execution_count": 197, + "execution_count": 201, "id": "a49c6de9-4e6b-47e5-bcf0-2972a95c1af3", "metadata": {}, "outputs": [ @@ -6276,7 +6437,7 @@ "Puzzle 17.2: .004 seconds, answer 267265166222235 ok" ] }, - "execution_count": 197, + "execution_count": 201, "metadata": {}, "output_type": "execute_result" } @@ -6307,7 +6468,7 @@ }, { "cell_type": "code", - "execution_count": 199, + "execution_count": 203, "id": "d14e1966-2feb-4553-9a0a-12595ef4f7d7", "metadata": {}, "outputs": [ @@ -6362,7 +6523,7 @@ }, { "cell_type": "code", - "execution_count": 201, + "execution_count": 205, "id": "83af4751-38c9-4830-a2fa-78515b59bc97", "metadata": {}, "outputs": [], @@ -6377,7 +6538,7 @@ }, { "cell_type": "code", - "execution_count": 202, + "execution_count": 206, "id": "29da25e2-f3c2-43e3-8769-1d4fcecb807b", "metadata": {}, "outputs": [ @@ -6387,7 +6548,7 @@ "Puzzle 18.1: .014 seconds, answer 344 ok" ] }, - "execution_count": 202, + "execution_count": 206, "metadata": {}, "output_type": "execute_result" } @@ -6411,7 +6572,7 @@ }, { "cell_type": "code", - "execution_count": 204, + "execution_count": 208, "id": "4c0a8dcb-c3af-45e7-9273-8776e8c3ea1d", "metadata": {}, "outputs": [], @@ -6432,7 +6593,7 @@ }, { "cell_type": "code", - "execution_count": 205, + "execution_count": 209, "id": "22371144-5d51-440a-918f-a63de73b13ad", "metadata": {}, "outputs": [ @@ -6442,7 +6603,7 @@ "Puzzle 18.2: .032 seconds, answer 46,18 ok" ] }, - "execution_count": 205, + "execution_count": 209, "metadata": {}, "output_type": "execute_result" } @@ -6484,7 +6645,7 @@ }, { "cell_type": "code", - "execution_count": 208, + "execution_count": 212, "id": "689df92b-92d7-44e6-8b4e-d67bf3f153df", "metadata": {}, "outputs": [ @@ -6528,7 +6689,7 @@ }, { "cell_type": "code", - "execution_count": 210, + "execution_count": 214, "id": "e0b96545-5f60-4c8e-9a0f-c77608c19128", "metadata": {}, "outputs": [], @@ -6542,17 +6703,17 @@ }, { "cell_type": "code", - "execution_count": 211, + "execution_count": 215, "id": "3c3fba1b-d3e5-494e-aad0-42ca0566ae1f", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "Puzzle 19.1: .039 seconds, answer 242 ok" + "Puzzle 19.1: .040 seconds, answer 242 ok" ] }, - "execution_count": 211, + "execution_count": 215, "metadata": {}, "output_type": "execute_result" } @@ -6574,7 +6735,7 @@ }, { "cell_type": "code", - "execution_count": 213, + "execution_count": 217, "id": "77d4fae1-d506-4733-9bb2-467619012f97", "metadata": {}, "outputs": [], @@ -6589,17 +6750,17 @@ }, { "cell_type": "code", - "execution_count": 214, + "execution_count": 218, "id": "14116eca-0b0f-484a-a169-9726e4ac7fbf", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "Puzzle 19.2: .185 seconds, answer 595975512785325 ok" + "Puzzle 19.2: .187 seconds, answer 595975512785325 ok" ] }, - "execution_count": 214, + "execution_count": 218, "metadata": {}, "output_type": "execute_result" } @@ -6619,7 +6780,7 @@ }, { "cell_type": "code", - "execution_count": 216, + "execution_count": 220, "id": "f992b197-be14-47dc-8678-10cc63a1afd7", "metadata": {}, "outputs": [], @@ -6638,7 +6799,7 @@ }, { "cell_type": "code", - "execution_count": 218, + "execution_count": 222, "id": "2437ae06-27e4-4840-ad51-65454ef0bf60", "metadata": {}, "outputs": [], @@ -6651,7 +6812,7 @@ }, { "cell_type": "code", - "execution_count": 219, + "execution_count": 223, "id": "88e45604-ce45-478e-a1b8-66ead1d0e72e", "metadata": {}, "outputs": [ @@ -6661,7 +6822,7 @@ "Puzzle 19.1: .004 seconds, answer 242 ok" ] }, - "execution_count": 219, + "execution_count": 223, "metadata": {}, "output_type": "execute_result" } @@ -6683,7 +6844,7 @@ }, { "cell_type": "code", - "execution_count": 221, + "execution_count": 225, "id": "156dcbf7-79ec-41a7-a9f2-397a827b9856", "metadata": {}, "outputs": [ @@ -6726,7 +6887,7 @@ }, { "cell_type": "code", - "execution_count": 223, + "execution_count": 227, "id": "4c4ef05c-b548-49f9-b092-847e9752e745", "metadata": {}, "outputs": [], @@ -6757,17 +6918,17 @@ }, { "cell_type": "code", - "execution_count": 224, + "execution_count": 228, "id": "1bbd8b72-c503-4384-aaea-a5bed45a4491", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "Puzzle 20.1: .028 seconds, answer 1343 ok" + "Puzzle 20.1: .029 seconds, answer 1343 ok" ] }, - "execution_count": 224, + "execution_count": 228, "metadata": {}, "output_type": "execute_result" } @@ -6794,17 +6955,17 @@ }, { "cell_type": "code", - "execution_count": 226, + "execution_count": 230, "id": "d370e24c-9b82-4415-82a5-7afe2be17654", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "Puzzle 20.2: .768 seconds, answer 982891 ok" + "Puzzle 20.2: .769 seconds, answer 982891 ok" ] }, - "execution_count": 226, + "execution_count": 230, "metadata": {}, "output_type": "execute_result" } @@ -6840,7 +7001,7 @@ }, { "cell_type": "code", - "execution_count": 228, + "execution_count": 232, "id": "332e9c72-db9a-4b90-a649-45f7bf955e84", "metadata": {}, "outputs": [ @@ -6850,7 +7011,7 @@ "Puzzle 20.1: .023 seconds, answer 1343 ok" ] }, - "execution_count": 228, + "execution_count": 232, "metadata": {}, "output_type": "execute_result" } @@ -6872,7 +7033,7 @@ }, { "cell_type": "code", - "execution_count": 230, + "execution_count": 234, "id": "9d0eea9f-9d8f-4410-97ec-13d8b136925b", "metadata": {}, "outputs": [ @@ -6946,7 +7107,7 @@ }, { "cell_type": "code", - "execution_count": 232, + "execution_count": 236, "id": "779c6b4c-ceb0-49ac-96f2-4700835c70ea", "metadata": {}, "outputs": [], @@ -6972,7 +7133,7 @@ }, { "cell_type": "code", - "execution_count": 234, + "execution_count": 238, "id": "c915ada2-9dfe-42fe-bd08-855ff5d0e837", "metadata": {}, "outputs": [], @@ -7001,7 +7162,7 @@ }, { "cell_type": "code", - "execution_count": 236, + "execution_count": 240, "id": "391f6120-6373-43f2-b697-585c90e45e54", "metadata": {}, "outputs": [], @@ -7021,7 +7182,7 @@ }, { "cell_type": "code", - "execution_count": 238, + "execution_count": 242, "id": "1e2e8c59-744f-4f36-9251-c8ea5fd60aed", "metadata": {}, "outputs": [], @@ -7041,7 +7202,7 @@ }, { "cell_type": "code", - "execution_count": 240, + "execution_count": 244, "id": "980572de-2589-4ada-a2a1-6c17cc627353", "metadata": {}, "outputs": [], @@ -7078,7 +7239,7 @@ }, { "cell_type": "code", - "execution_count": 242, + "execution_count": 246, "id": "393a8c6b-6a1c-4495-aad5-92f48711fbf1", "metadata": {}, "outputs": [ @@ -7088,7 +7249,7 @@ "Puzzle 21.1: .000 seconds, answer 205160 ok" ] }, - "execution_count": 242, + "execution_count": 246, "metadata": {}, "output_type": "execute_result" } @@ -7108,7 +7269,7 @@ }, { "cell_type": "code", - "execution_count": 244, + "execution_count": 248, "id": "4d8c8659-52d3-4349-8465-7003e880502f", "metadata": {}, "outputs": [], @@ -7140,7 +7301,7 @@ }, { "cell_type": "code", - "execution_count": 247, + "execution_count": 251, "id": "563c5959-692d-4368-8c0b-469fa0142678", "metadata": {}, "outputs": [ @@ -7150,7 +7311,7 @@ "Puzzle 21.2: .004 seconds, answer 252473394928452 ok" ] }, - "execution_count": 247, + "execution_count": 251, "metadata": {}, "output_type": "execute_result" } @@ -7170,7 +7331,7 @@ }, { "cell_type": "code", - "execution_count": 249, + "execution_count": 253, "id": "f2d29a66-5420-4fc1-8586-1570ed5ac0a8", "metadata": {}, "outputs": [ @@ -7180,7 +7341,7 @@ "439646398242" ] }, - "execution_count": 249, + "execution_count": 253, "metadata": {}, "output_type": "execute_result" } @@ -7209,7 +7370,7 @@ }, { "cell_type": "code", - "execution_count": 252, + "execution_count": 256, "id": "2c1e7612-4ec5-4ce1-b591-5c3a14f8ea61", "metadata": {}, "outputs": [ @@ -7260,7 +7421,7 @@ }, { "cell_type": "code", - "execution_count": 254, + "execution_count": 258, "id": "3807adbc-261a-4108-88ba-6e2b061526b5", "metadata": {}, "outputs": [], @@ -7276,17 +7437,17 @@ }, { "cell_type": "code", - "execution_count": 255, + "execution_count": 259, "id": "fce754cd-f6da-4e7d-b406-3314c9c28ca8", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "Puzzle 22.1: .331 seconds, answer 14273043166 ok" + "Puzzle 22.1: .300 seconds, answer 14273043166 ok" ] }, - "execution_count": 255, + "execution_count": 259, "metadata": {}, "output_type": "execute_result" } @@ -7332,7 +7493,7 @@ }, { "cell_type": "code", - "execution_count": 257, + "execution_count": 261, "id": "cbbb4793-b13d-4a95-ba03-673b3ebaa229", "metadata": {}, "outputs": [], @@ -7372,17 +7533,17 @@ }, { "cell_type": "code", - "execution_count": 258, + "execution_count": 262, "id": "34eaf7fe-fe9b-4858-b1e8-e4c3713ea093", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "Puzzle 22.2: 1.157 seconds, answer 1667 ok" + "Puzzle 22.2: 1.158 seconds, answer 1667 ok" ] }, - "execution_count": 258, + "execution_count": 262, "metadata": {}, "output_type": "execute_result" } @@ -7402,7 +7563,7 @@ }, { "cell_type": "code", - "execution_count": 260, + "execution_count": 264, "id": "a2da8c78-d240-4c52-a83a-f3599f4f69a4", "metadata": {}, "outputs": [ @@ -7412,7 +7573,7 @@ "(-3, 2, -1, 2)" ] }, - "execution_count": 260, + "execution_count": 264, "metadata": {}, "output_type": "execute_result" } @@ -7433,7 +7594,7 @@ }, { "cell_type": "code", - "execution_count": 262, + "execution_count": 266, "id": "9de3f365-c546-494a-a18c-2ae58d88792b", "metadata": {}, "outputs": [ @@ -7465,7 +7626,7 @@ }, { "cell_type": "code", - "execution_count": 264, + "execution_count": 268, "id": "4fc191f9-62d8-47ef-a571-8c0541e25f4d", "metadata": {}, "outputs": [ @@ -7484,7 +7645,7 @@ " ((1, -1, 0, 0), 292)]" ] }, - "execution_count": 264, + "execution_count": 268, "metadata": {}, "output_type": "execute_result" } @@ -7503,7 +7664,7 @@ }, { "cell_type": "code", - "execution_count": 266, + "execution_count": 270, "id": "bbd329a0-309c-4c81-a780-6bd97a95f652", "metadata": {}, "outputs": [ @@ -7513,7 +7674,7 @@ "1341" ] }, - "execution_count": 266, + "execution_count": 270, "metadata": {}, "output_type": "execute_result" } @@ -7532,7 +7693,7 @@ }, { "cell_type": "code", - "execution_count": 268, + "execution_count": 272, "id": "a2945c44-00f7-4c88-9b6e-747d9563034e", "metadata": {}, "outputs": [], @@ -7555,7 +7716,7 @@ }, { "cell_type": "code", - "execution_count": 270, + "execution_count": 274, "id": "936bfa40-726a-4ddd-8033-8a50fa09b9ea", "metadata": {}, "outputs": [ @@ -7579,7 +7740,7 @@ " (0, 0, -1): 1})" ] }, - "execution_count": 270, + "execution_count": 274, "metadata": {}, "output_type": "execute_result" } @@ -7600,7 +7761,7 @@ }, { "cell_type": "code", - "execution_count": 272, + "execution_count": 276, "id": "2bb368b8-d5e7-431c-bd2c-19aa41983b8d", "metadata": {}, "outputs": [ @@ -7610,7 +7771,7 @@ "{1: 19, 2: 271, 3: 3439, 4: 40951}" ] }, - "execution_count": 272, + "execution_count": 276, "metadata": {}, "output_type": "execute_result" } @@ -7639,7 +7800,7 @@ }, { "cell_type": "code", - "execution_count": 275, + "execution_count": 279, "id": "85f5b145-8c5e-448c-8b45-ad5750252ff2", "metadata": {}, "outputs": [ @@ -7690,7 +7851,7 @@ }, { "cell_type": "code", - "execution_count": 277, + "execution_count": 281, "id": "289d2325-1e58-41f5-b4b2-b90ae26e7887", "metadata": {}, "outputs": [], @@ -7715,7 +7876,7 @@ }, { "cell_type": "code", - "execution_count": 278, + "execution_count": 282, "id": "6425577d-4ca9-45de-9698-cd9b026f7ce6", "metadata": {}, "outputs": [ @@ -7725,7 +7886,7 @@ "Puzzle 23.1: .001 seconds, answer 1170 ok" ] }, - "execution_count": 278, + "execution_count": 282, "metadata": {}, "output_type": "execute_result" } @@ -7747,7 +7908,7 @@ }, { "cell_type": "code", - "execution_count": 280, + "execution_count": 284, "id": "88811073-22b2-4378-afa5-9d38462c63b5", "metadata": {}, "outputs": [], @@ -7769,7 +7930,7 @@ }, { "cell_type": "code", - "execution_count": 281, + "execution_count": 285, "id": "0b5f08ac-18e2-4933-9737-cdbc842c5809", "metadata": {}, "outputs": [ @@ -7779,7 +7940,7 @@ "Puzzle 23.2: .003 seconds, answer bo,dd,eq,ik,lo,lu,ph,ro,rr,rw,uo,wx,yg ok" ] }, - "execution_count": 281, + "execution_count": 285, "metadata": {}, "output_type": "execute_result" } @@ -7819,7 +7980,7 @@ }, { "cell_type": "code", - "execution_count": 284, + "execution_count": 288, "id": "47421581-71df-4c72-a62e-c40d0596fdbb", "metadata": {}, "outputs": [ @@ -7870,7 +8031,7 @@ }, { "cell_type": "code", - "execution_count": 286, + "execution_count": 290, "id": "43b29260-d912-4cfc-91ce-d303ec1c86df", "metadata": {}, "outputs": [], @@ -7896,7 +8057,7 @@ }, { "cell_type": "code", - "execution_count": 288, + "execution_count": 292, "id": "a9a4780f-6033-452f-b49c-74b97c9e2440", "metadata": {}, "outputs": [], @@ -7918,7 +8079,7 @@ }, { "cell_type": "code", - "execution_count": 289, + "execution_count": 293, "id": "72437439-dddf-4202-9944-36e796800304", "metadata": {}, "outputs": [ @@ -7928,7 +8089,7 @@ "Puzzle 24.1: .001 seconds, answer 36035961805936 ok" ] }, - "execution_count": 289, + "execution_count": 293, "metadata": {}, "output_type": "execute_result" } @@ -7984,7 +8145,7 @@ }, { "cell_type": "code", - "execution_count": 293, + "execution_count": 297, "id": "9021ad30-f505-4164-9feb-5af740632182", "metadata": {}, "outputs": [ @@ -8039,7 +8200,7 @@ " 'z00': ('x00', 'XOR', 'y00')}" ] }, - "execution_count": 293, + "execution_count": 297, "metadata": {}, "output_type": "execute_result" } @@ -8070,7 +8231,7 @@ }, { "cell_type": "code", - "execution_count": 295, + "execution_count": 299, "id": "3b41a166-d33b-402b-971b-1dc1d33135ec", "metadata": {}, "outputs": [ @@ -8080,7 +8241,7 @@ "Counter({'AND': 89, 'XOR': 89, 'OR': 44})" ] }, - "execution_count": 295, + "execution_count": 299, "metadata": {}, "output_type": "execute_result" } @@ -8101,7 +8262,7 @@ }, { "cell_type": "code", - "execution_count": 297, + "execution_count": 301, "id": "ec8d86d4-8caf-40f2-b516-c7da4acbda19", "metadata": {}, "outputs": [], @@ -8137,7 +8298,7 @@ }, { "cell_type": "code", - "execution_count": 299, + "execution_count": 303, "id": "7ed8f8e6-c3e1-4c22-b812-e1a201afec3d", "metadata": {}, "outputs": [], @@ -8175,7 +8336,7 @@ }, { "cell_type": "code", - "execution_count": 300, + "execution_count": 304, "id": "e9b87c38-f67c-4948-9af6-251021747e9d", "metadata": {}, "outputs": [ @@ -8185,7 +8346,7 @@ "Puzzle 24.2: .000 seconds, answer jqf,mdd,skh,wpd,wts,z11,z19,z37 ok" ] }, - "execution_count": 300, + "execution_count": 304, "metadata": {}, "output_type": "execute_result" } @@ -8209,7 +8370,7 @@ }, { "cell_type": "code", - "execution_count": 302, + "execution_count": 306, "id": "f5971853-7139-4f17-bdc5-6c51e12a928d", "metadata": {}, "outputs": [ @@ -8277,7 +8438,7 @@ }, { "cell_type": "code", - "execution_count": 304, + "execution_count": 308, "id": "5302ac58-91fc-475a-83d9-cea91457df3b", "metadata": {}, "outputs": [], @@ -8299,7 +8460,7 @@ }, { "cell_type": "code", - "execution_count": 305, + "execution_count": 309, "id": "89c28b74-ed31-4bb5-b463-7177952a95ae", "metadata": {}, "outputs": [ @@ -8309,7 +8470,7 @@ "Puzzle 25.1: .022 seconds, answer 3196 ok" ] }, - "execution_count": 305, + "execution_count": 309, "metadata": {}, "output_type": "execute_result" } @@ -8340,12 +8501,12 @@ "\n", "Here are all the puzzle answers and timings. I got all the puzzles correct! And I did it before midnight (my time) on December 25th, a rarity for me. \n", "\n", - "The median run time is about 5 milliseconds, with 3 puzzles taking over a second. I didn't count the time that `parse` takes, but that is less than a millisecond per day." + "The median run time is about 5 milliseconds, with 3 puzzles taking over a second. I didn't count the time that `parse` takes, but that is less than a millisecond per day. Some people in the Rust subreddit were talking about completing the puzzles in less than 10 seconds of run time. I managed to beat that, using Python's slow interpreter rather than [Rust](https://www.rust-lang.org/)'s fast compiler. " ] }, { "cell_type": "code", - "execution_count": 308, + "execution_count": 312, "id": "34813fc9-a000-4cd8-88ae-692851b3242c", "metadata": {}, "outputs": [ @@ -8364,39 +8525,39 @@ "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.977 seconds, answer 2162 ok\n", + "Puzzle 6.2: 1.975 seconds, answer 2162 ok\n", "Puzzle 7.1: .014 seconds, answer 1985268524462 ok\n", - "Puzzle 7.2: .600 seconds, answer 150077710195188 ok\n", + "Puzzle 7.2: .602 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.1: .019 seconds, answer 6332189866718 ok\n", "Puzzle 9.2: .021 seconds, answer 6353648390778 ok\n", - "Puzzle 10.1: .004 seconds, answer 744 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: .058 seconds, answer 232454623677743 ok\n", + "Puzzle 11.2: .062 seconds, answer 232454623677743 ok\n", "Puzzle 12.1: .052 seconds, answer 1402544 ok\n", "Puzzle 12.2: .043 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.866 seconds, answer 6876 ok\n", - "Puzzle 15.1: .029 seconds, answer 1563092 ok\n", - "Puzzle 15.2: .042 seconds, answer 1582688 ok\n", + "Puzzle 14.2: 1.884 seconds, answer 6876 ok\n", + "Puzzle 15.1: .030 seconds, answer 1563092 ok\n", + "Puzzle 15.2: .043 seconds, answer 1582688 ok\n", "Puzzle 16.1: .147 seconds, answer 103512 ok\n", - "Puzzle 16.2: .860 seconds, answer 554 ok\n", + "Puzzle 16.2: .866 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: .004 seconds, answer 267265166222235 ok\n", "Puzzle 18.1: .014 seconds, answer 344 ok\n", "Puzzle 18.2: .032 seconds, answer 46,18 ok\n", "Puzzle 19.1: .004 seconds, answer 242 ok\n", - "Puzzle 19.2: .185 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: .768 seconds, answer 982891 ok\n", + "Puzzle 20.2: .769 seconds, answer 982891 ok\n", "Puzzle 21.1: .000 seconds, answer 205160 ok\n", "Puzzle 21.2: .004 seconds, answer 252473394928452 ok\n", - "Puzzle 22.1: .331 seconds, answer 14273043166 ok\n", - "Puzzle 22.2: 1.157 seconds, answer 1667 ok\n", + "Puzzle 22.1: .300 seconds, answer 14273043166 ok\n", + "Puzzle 22.2: 1.158 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", @@ -8405,21 +8566,13 @@ "\n", "Correct: 49/49\n", "\n", - "Time in seconds: 0.004 median, 0.170 mean, 8.342 total.\n" + "Time in seconds: 0.005 median, 0.170 mean, 8.342 total.\n" ] } ], "source": [ "summary(answers)" ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "4f23c694-7302-4d45-8bef-751b9a7356f2", - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { diff --git a/ipynb/AdventUtils.ipynb b/ipynb/AdventUtils.ipynb index a00cfd1..438b8a8 100644 --- a/ipynb/AdventUtils.ipynb +++ b/ipynb/AdventUtils.ipynb @@ -67,15 +67,28 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 7, "metadata": {}, - "outputs": [], + "outputs": [ + { + "ename": "NameError", + "evalue": "name 'whole' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[7], line 6\u001b[0m\n\u001b[1;32m 3\u001b[0m lines \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mstr\u001b[39m\u001b[38;5;241m.\u001b[39msplitlines \u001b[38;5;66;03m# By default, split input text into lines\u001b[39;00m\n\u001b[1;32m 5\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mparagraphs\u001b[39m(text): \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mSplit text into paragraphs\u001b[39m\u001b[38;5;124m\"\u001b[39m; \u001b[38;5;28;01mreturn\u001b[39;00m text\u001b[38;5;241m.\u001b[39msplit(\u001b[38;5;124m'\u001b[39m\u001b[38;5;130;01m\\n\u001b[39;00m\u001b[38;5;130;01m\\n\u001b[39;00m\u001b[38;5;124m'\u001b[39m)\n\u001b[0;32m----> 6\u001b[0m whole\n\u001b[1;32m 8\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mparse\u001b[39m(day_or_text:Union[\u001b[38;5;28mint\u001b[39m, \u001b[38;5;28mstr\u001b[39m], parser\u001b[38;5;241m=\u001b[39m\u001b[38;5;28mstr\u001b[39m, sections\u001b[38;5;241m=\u001b[39mlines, show\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m8\u001b[39m) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m \u001b[38;5;28mtuple\u001b[39m:\n\u001b[1;32m 9\u001b[0m \u001b[38;5;250m \u001b[39m\u001b[38;5;124;03m\"\"\"Split the input text into `sections`, and apply `parser` to each.\u001b[39;00m\n\u001b[1;32m 10\u001b[0m \u001b[38;5;124;03m The first argument is either the text itself, or the day number of a text file.\"\"\"\u001b[39;00m\n", + "\u001b[0;31mNameError\u001b[0m: name 'whole' is not defined" + ] + } + ], "source": [ "current_year = 2023 # Subdirectory name for input files\n", "\n", "lines = str.splitlines # By default, split input text into lines\n", "\n", "def paragraphs(text): \"Split text into paragraphs\"; return text.split('\\n\\n')\n", + "def whole(text): \"The whole text\"; return [text]\n", "\n", "def parse(day_or_text:Union[int, str], parser=str, sections=lines, show=8) -> tuple:\n", " \"\"\"Split the input text into `sections`, and apply `parser` to each.\n", @@ -541,12 +554,16 @@ " 0 <= Y_(point) < Y_(self.size))\n", "\n", " def follow_line(self, start: Point, direction: Vector) -> Iterable[Point]:\n", + " \"\"\"All points from start going in direction, until the edge of the grid.\"\"\"\n", " while self.in_range(start):\n", " yield start\n", " start = add(start, direction)\n", "\n", - " def copy(self): \n", - " return Grid(self, directions=self.directions, skip=self.skip, default=self.default)\n", + " def copy(self, updates={}): \n", + " \"\"\"Make a copy of this grid, and optionally update some positions with new values.\"\"\"\n", + " grid = Grid(self, directions=self.directions, skip=self.skip, default=self.default)\n", + " grid.update(updates)\n", + " return grid\n", " \n", " def neighbors(self, point) -> List[Point]:\n", " \"\"\"Points on the grid that neighbor `point`.\"\"\"\n",