diff --git a/ipynb/Advent-2025-AI.ipynb b/ipynb/Advent-2025-AI.ipynb index 6fbc341..9fb1b80 100644 --- a/ipynb/Advent-2025-AI.ipynb +++ b/ipynb/Advent-2025-AI.ipynb @@ -11,15 +11,27 @@ "\n", "I enjoy doing the [**Advent of Code**](https://adventofcode.com/) (AoC) programming puzzles, and **my** solutions are [**over here**](Advent2025.ipynb). \n", "\n", - "In **this** notebook I show some solutions by various AI Large Language Models: Gemini, Claude, and ChatGPT. Each day I'll choose a model and give it the prompt \"*Write code to solve the following problem:*\" followed by the full text of the AoC Part 1 problem description. Then I'll pronmpt again with \"*There is a change to the specification:*\" followed by the AoC Part 2 description.\n", + "In **this** notebook I show some solutions by various AI Large Language Models: Gemini, Claude, and ChatGPT.\n", + "\n", + "In order to understand what's going on here, you'll have to look at the problem descriptions at [**Advent of Code**](https://adventofcode.com/2025).\n", + "\n", + "Each day I'll choose a model and give it the prompt \"*Write code to solve the following problem:*\" followed by the full text of the AoC Part 1 problem description. Then I'll prompt again with \"*There is a change to the specification:*\" followed by the AoC Part 2 description. So far the LLMs are doing quite well. \n", + "\n", + "For brevity, I have removed some of the models' output, such as:\n", + "- Prose descriptions of the programs. (In most cases these were aaccurate and thorough!)\n", + "- The \"__main__\"` idiom for running code from the command line\n", + "- Test examples to run.\n", + "\n", + "Overall, the models did well, producing code that gives the correct answer in a reasonable run time. Some of the cosde could be improved stylistically. (But I guess if you're vibe coding and not looking at the code, maybe that doesn't matter.)\n", "\n", "# Day 0\n", + "\n", "I load my [**AdventUtils.ipynb**](AdventUtils.ipynb) and set the`current_year` so I can access my input files with `get_text(day_number)` and can use my `answer` function to verify whether the AI systems get the right answer." ] }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 1, "id": "34563e0b-09c5-4600-a455-3ff0f31b81a0", "metadata": {}, "outputs": [], @@ -35,12 +47,12 @@ "source": [ "# [Day 1](https://adventofcode.com/2025/day/1) Gemini 3 Pro\n", "\n", - "I started with the Gemini 3 Pro Fast model, which produced a nice analysis of the problem (which I have omitted) and the following code:" + "I started with the Gemini 3 Pro Fast model, which produced this following code:" ] }, { "cell_type": "code", - "execution_count": 60, + "execution_count": 2, "id": "a03354cc-951c-4609-80e1-a7e1a6d0c2bd", "metadata": {}, "outputs": [], @@ -103,22 +115,22 @@ "id": "c5219309-4f2f-49b3-8208-8c7a3526e328", "metadata": {}, "source": [ - "A bit verbose and over-commented, but pretty nice. Below I interface my data (obtained by a call to `get_text(1)`) to this function and verify that it is correct:" + "A bit verbose and over-commented, but pretty nice. Below I fetch the Day 1 data with `get_text(1)`, split it into lines (as expected by Gemini's `solve_safe_password` function), and verify that the answer is correct:" ] }, { "cell_type": "code", - "execution_count": 49, + "execution_count": 3, "id": "c3ff875a-6267-47b8-a032-115a40bfd246", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "Puzzle 1.1: .0014 seconds, answer 1182 ok" + "Puzzle 1.1: .0013 seconds, answer 1182 ok" ] }, - "execution_count": 49, + "execution_count": 3, "metadata": {}, "output_type": "execute_result" } @@ -135,12 +147,12 @@ "id": "47a04265-2dab-4a4c-9a24-e1c4c1b93c6b", "metadata": {}, "source": [ - "I then gave Gemini Fast the instructions for Part 2, and again it had a nice analysis and the following code:" + "For Part 2 I gave Gemini Fast the updated instructions, and again it had a nice analysis and the following code:" ] }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 4, "id": "a1cf0e58-4567-4713-9d4b-e57f5c958800", "metadata": {}, "outputs": [], @@ -219,29 +231,7 @@ " \n", " current_position = (current_position + rotation_value) % MODULO\n", "\n", - " return total_zero_crossings\n", - "\n", - "# --- EXAMPLE INPUT VERIFICATION ---\n", - "example_rotations = [\n", - " \"L68\", \"L30\", \"R48\", \"L5\", \"R60\", \n", - " \"L55\", \"L1\", \"L99\", \"R14\", \"L82\"\n", - "]\n", - "# Expected output for example is 6.\n", - "# print(f\"Example verification: {solve_new_safe_password(example_rotations)}\") \n", - "# The function will output 6, confirming the logic.\n", - "\n", - "# --- YOUR PUZZLE INPUT GOES HERE ---\n", - "# Replace this list with your actual puzzle input data\n", - "puzzle_input = [\n", - " # Paste your list of rotations here, one string per line\n", - " # e.g., \"R10\", \"L5\", ...\n", - " \"L68\", \"L30\", \"R48\", \"L5\", \"R60\", \"L55\", \"L1\", \"L99\", \"R14\", \"L82000\",\n", - " # ... and so on for all your input lines\n", - "]\n", - "\n", - "# Run the simulation with your full input\n", - "#actual_password = solve_new_safe_password(puzzle_input)\n", - "#print(f\"The actual password (total zero crossings) is: {actual_password}\")" + " return total_zero_crossings" ] }, { @@ -249,24 +239,24 @@ "id": "f8e02f20-87b1-4bd1-9bdb-2bd187fc42cb", "metadata": {}, "source": [ - "Note that the code does `import math` but then never uses the math module. (I've done that, but I try to clean things up.)\n", + "Note that the code does `import math` but then never uses the math module. (I've done that, but I try to eventually clean things up. Maybe that's the different between Gemini \"Fast\" and Gemini \"Thinking\" models.)\n", "\n", "Here I check whether the code is correct:" ] }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 5, "id": "a0b3e0fc-e044-4c1e-b4b6-6bc735145f57", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "Puzzle 1.2: .0017 seconds, answer 7509 WRONG; expected answer is 6907" + "Puzzle 1.2: .0018 seconds, answer 7509 WRONG; expected answer is 6907" ] }, - "execution_count": 14, + "execution_count": 5, "metadata": {}, "output_type": "execute_result" } @@ -281,7 +271,7 @@ "id": "d640d8e4-6940-416d-93af-b55d82482a35", "metadata": {}, "source": [ - "Too bad. I switched to the Gemini 3 Pro Thinking model and gave the prompt \"*That's not quite right.*\" It responded with\n", + "Too bad. I switched to the Gemini 3 Pro \"Thinking\" model and gave the prompt \"*That's not quite right.*\" It responded with\n", "\n", ">It seems I made a subtle logic error in my previous calculation for the Right (R) rotation.\n", ">\n", @@ -292,7 +282,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 6, "id": "196a6a93-e318-4a45-a023-9997fa0fb0f5", "metadata": {}, "outputs": [], @@ -344,24 +334,7 @@ " else:\n", " current_position = (current_position - distance) % 100\n", "\n", - " return total_zero_crossings\n", - "\n", - "# --- Example Input Verification ---\n", - "example_rotations = [\n", - " \"L68\", \"L30\", \"R48\", \"L5\", \"R60\", \n", - " \"L55\", \"L1\", \"L99\", \"R14\", \"L82\"\n", - "]\n", - "# Expected output: 6\n", - "# print(f\"Example result: {solve_safe_password_method_2(example_rotations)}\")\n", - "\n", - "# --- YOUR PUZZLE INPUT GOES HERE ---\n", - "puzzle_input = [\n", - " # Copy and paste your full input list here\n", - " # \"L10\", \"R5\", etc...\n", - "]\n", - "\n", - "# result = solve_safe_password_method_2(puzzle_input)\n", - "# print(f\"The actual password is: {result}\")" + " return total_zero_crossings" ] }, { @@ -374,17 +347,17 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 7, "id": "021043ed-2ac5-4aaf-8171-985f0f5911f2", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "Puzzle 1.2: .0015 seconds, answer 6907 ok" + "Puzzle 1.2: .0024 seconds, answer 6907 ok" ] }, - "execution_count": 19, + "execution_count": 7, "metadata": {}, "output_type": "execute_result" } @@ -401,12 +374,12 @@ "source": [ "# [Day 2](https://adventofcode.com/2025/day/2) Claude Opus 4.5\n", "\n", - "I gave Claude the instructions for Day 2 Part 1 and it wrote some code and then asked me to paste in the input file. I did and Claude ran the code, producing the correct answer but printing a lot of unneccessary debugging output along the way. I prompted it to \"*Change the code to not print anything, just return the answer*\" and got the following:" + "I gave Claude the instructions for Day 2 Part 1 and it wrote some code that produces the correct answer but prints a lot of unneccessary debugging output along the way. I prompted it to \"*Change the code to not print anything, just return the answer*\" and got the following:" ] }, { "cell_type": "code", - "execution_count": 105, + "execution_count": 8, "id": "8eac98f3-b884-4d95-b38b-ea4365ec3004", "metadata": {}, "outputs": [], @@ -447,24 +420,24 @@ "id": "2bd0db00-952b-47e5-b787-b3887b7539f1", "metadata": {}, "source": [ - "This code is overall rather nice, but conspicously lacks comments and doc strings. It uses the more efficient \"enumerate over the first half of the digit string\" strategy, but is not precise in narrowing down the range it enumerates over. For example, if the range is \"999000-109000\", this code will enumnrate the range(100, 1000), when it could enumerate just range(999, 1000).\n", + "This code is overall rather nice, but conspicously lacks comments and doc strings. It uses the more efficient \"enumerate over the first half of the digit string\" strategy, but is not precise in narrowing down the range it enumerates over. For example, for the range \"999000-109000\", this code will enumerate the range (100, 1000), when it could enumerate just the range (999, 1000).\n", "\n", "I verified that the code gives the correct answer:" ] }, { "cell_type": "code", - "execution_count": 98, + "execution_count": 9, "id": "a91845ec-ace7-482e-b0b5-8a620ef3461f", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "Puzzle 2.1: .1303 seconds, answer 23560874270 ok" + "Puzzle 2.1: .1263 seconds, answer 23560874270 ok" ] }, - "execution_count": 98, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" } @@ -481,12 +454,12 @@ "id": "a31d006f-8cf2-4e4c-92d3-d7b7def22227", "metadata": {}, "source": [ - "Claude then wrote the following code when given the Part 2 instructions, nicely generalizing to any number of repeats:" + "When given the Part 2 instructions, Claude wrote the following code:" ] }, { "cell_type": "code", - "execution_count": 99, + "execution_count": 10, "id": "f0dc176b-dd85-40a4-ac5c-dfa936a6a524", "metadata": {}, "outputs": [], @@ -538,17 +511,17 @@ }, { "cell_type": "code", - "execution_count": 100, + "execution_count": 11, "id": "9c0049e6-a992-4aa8-a2d7-3ea748e361a6", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "Puzzle 2.2: .1816 seconds, answer 44143124633 ok" + "Puzzle 2.2: .1350 seconds, answer 44143124633 ok" ] }, - "execution_count": 100, + "execution_count": 11, "metadata": {}, "output_type": "execute_result" } @@ -565,12 +538,12 @@ "source": [ "# [Day 3](https://adventofcode.com/2025/day/3) ChatGPT 5.1 Auto\n", "\n", - "ChatGPT gave a very brief analysis of the problem and then wrote a program that was designed to be called from the command line, using the `if __name__ == \"__main__\"` idiom. I told it \"I don't want to run it like that, I want a function that I can pass the input text and have it return an int.\" and it produced this code:\n" + "ChatGPT gave a very brief analysis of the problem and then wrote a program that was designed to be called from the command line, using the `\"__main__\"` idiom. I told it \"I don't want to run it like that, I want a function that I can pass the input text and have it return an int.\" and it produced this code (lacking comments or doc strings):" ] }, { "cell_type": "code", - "execution_count": 55, + "execution_count": 12, "id": "3aa266f3-50d0-4d8d-a464-4c74c52daa69", "metadata": {}, "outputs": [], @@ -609,17 +582,17 @@ }, { "cell_type": "code", - "execution_count": 56, + "execution_count": 13, "id": "09bf306b-8762-4346-aff9-bcff33639c71", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "Puzzle 3.1: .0040 seconds, answer 17085 ok" + "Puzzle 3.1: .0044 seconds, answer 17085 ok" ] }, - "execution_count": 56, + "execution_count": 13, "metadata": {}, "output_type": "execute_result" } @@ -632,16 +605,16 @@ ] }, { - "cell_type": "code", - "execution_count": null, - "id": "f0398d5b-485d-4479-9321-878564180b68", + "cell_type": "markdown", + "id": "4a07f37f-c5e3-4484-a7b1-2cae0ff5bd01", "metadata": {}, - "outputs": [], - "source": [] + "source": [ + "For Part 2 ChatGPT did well (and for some reason included comments and doc strings this time):" + ] }, { "cell_type": "code", - "execution_count": 58, + "execution_count": 14, "id": "bdb8b4e4-bed0-48dc-a045-47cd4c6002fd", "metadata": {}, "outputs": [], @@ -692,17 +665,17 @@ }, { "cell_type": "code", - "execution_count": 59, + "execution_count": 15, "id": "70bde9b9-beb1-4e9d-bef6-0f20fb958891", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "Puzzle 3.2: .0069 seconds, answer 169408143086082 ok" + "Puzzle 3.2: .0076 seconds, answer 169408143086082 ok" ] }, - "execution_count": 59, + "execution_count": 15, "metadata": {}, "output_type": "execute_result" } @@ -719,12 +692,12 @@ "source": [ "# [Day 4](https://adventofcode.com/2025/day/4): Gemini 3 Pro\n", "\n", - "Gemini produced a solution to Part 1 that is straightforward and efficient, although perhpas could use some abstraction (e.g. if they had a function to count neighbors, they wouldn't need the `continue`)." + "Gemini produced a solution to Part 1 that is straightforward and efficient, although perhaps could use some abstraction (e.g. if they had a function to count neighbors, they wouldn't need the `continue`)." ] }, { "cell_type": "code", - "execution_count": 75, + "execution_count": 16, "id": "35bf1f30-07c7-4842-a6e3-e33fb874e779", "metadata": {}, "outputs": [], @@ -780,22 +753,22 @@ "id": "1e12bc4c-8cc8-4c01-b4ad-5392b49642e6", "metadata": {}, "source": [ - "Here I get the input and verify that the code does produce the correct answer:" + "Here I verify that the code does produce the correct answer:" ] }, { "cell_type": "code", - "execution_count": 82, + "execution_count": 17, "id": "5b54c152-ce26-4baf-8b51-d4a166c6c2e7", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "Puzzle 4.1: .0163 seconds, answer 1569 ok" + "Puzzle 4.1: .0171 seconds, answer 1569 ok" ] }, - "execution_count": 82, + "execution_count": 17, "metadata": {}, "output_type": "execute_result" } @@ -817,7 +790,7 @@ }, { "cell_type": "code", - "execution_count": 83, + "execution_count": 18, "id": "16a1a0db-7501-41fd-a606-87fbb79273bd", "metadata": {}, "outputs": [], @@ -878,17 +851,17 @@ }, { "cell_type": "code", - "execution_count": 79, + "execution_count": 19, "id": "b47c2e05-978a-4b22-aafc-e31ee1825387", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "Puzzle 4.2: .4017 seconds, answer 9280 ok" + "Puzzle 4.2: .3960 seconds, answer 9280 ok" ] }, - "execution_count": 79, + "execution_count": 19, "metadata": {}, "output_type": "execute_result" } @@ -912,7 +885,7 @@ }, { "cell_type": "code", - "execution_count": 86, + "execution_count": 20, "id": "71bfe887-fbd4-4378-b37f-d0b88f9fa3e7", "metadata": {}, "outputs": [], @@ -987,17 +960,17 @@ }, { "cell_type": "code", - "execution_count": 88, + "execution_count": 21, "id": "f370ee38-67af-42a6-9ad3-cdeec2019ff3", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "Puzzle 4.2: .0761 seconds, answer 9280 ok" + "Puzzle 4.2: .0937 seconds, answer 9280 ok" ] }, - "execution_count": 88, + "execution_count": 21, "metadata": {}, "output_type": "execute_result" } @@ -1009,15 +982,454 @@ }, { "cell_type": "markdown", - "id": "8aa26008-a652-4860-9c84-5ba4344d32f3", + "id": "78434cfe-d728-453c-8f45-fc6b5fea18c3", "metadata": {}, "source": [ - "# Summary" + "# [Day 5](https://adventofcode.com/2025/day/5): Claude Opus 4.5\n", + "\n", + "Claude produces a straightforward program that solves Part 1 just fine and demonstrates good use of abstraction." ] }, { "cell_type": "code", - "execution_count": 101, + "execution_count": 22, + "id": "e7ab7dac-9686-4a76-b83f-6779275c3519", + "metadata": {}, + "outputs": [], + "source": [ + "\"\"\"\n", + "Solution for the Fresh Ingredients puzzle.\n", + "Parses a database with fresh ingredient ID ranges and available ingredient IDs,\n", + "then counts how many available ingredients are fresh.\n", + "\"\"\"\n", + "\n", + "def parse_input(input_text):\n", + " \"\"\"Parse the input into ranges and ingredient IDs.\"\"\"\n", + " parts = input_text.strip().split('\\n\\n')\n", + " \n", + " # Parse fresh ranges\n", + " ranges = []\n", + " for line in parts[0].strip().split('\\n'):\n", + " start, end = map(int, line.split('-'))\n", + " ranges.append((start, end))\n", + " \n", + " # Parse available ingredient IDs\n", + " ingredient_ids = []\n", + " for line in parts[1].strip().split('\\n'):\n", + " ingredient_ids.append(int(line))\n", + " \n", + " return ranges, ingredient_ids\n", + "\n", + "\n", + "def is_fresh(ingredient_id, ranges):\n", + " \"\"\"Check if an ingredient ID falls within any fresh range.\"\"\"\n", + " for start, end in ranges:\n", + " if start <= ingredient_id <= end:\n", + " return True\n", + " return False\n", + "\n", + "\n", + "def count_fresh_ingredients(ranges, ingredient_ids):\n", + " \"\"\"Count how many ingredient IDs are fresh.\"\"\"\n", + " count = 0\n", + " for ingredient_id in ingredient_ids:\n", + " if is_fresh(ingredient_id, ranges):\n", + " count += 1\n", + " return count\n", + "\n", + "\n", + "def solve(input_text):\n", + " \"\"\"Solve the puzzle and return the count of fresh ingredients.\"\"\"\n", + " ranges, ingredient_ids = parse_input(input_text)\n", + " return count_fresh_ingredients(ranges, ingredient_ids)" + ] + }, + { + "cell_type": "markdown", + "id": "40fc662b-ecee-4abe-a23a-ca7786edd438", + "metadata": {}, + "source": [ + "Here I feed the input to `solve` and verify that the answer is correct:" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "49f0bb9c-00c0-4983-ab26-d1cec1e692ac", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Puzzle 5.1: .0058 seconds, answer 635 ok" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "input_text = get_text(5)\n", + "\n", + "answer(5.1, 635, lambda:\n", + " solve(input_text))" + ] + }, + { + "cell_type": "markdown", + "id": "dabe3fbc-0fa6-46d0-adfb-f6413f3a63d5", + "metadata": {}, + "source": [ + "For Part 2, Claude chose to sort ranges and then merge them. That's a reasonable approach." + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "b70269fb-3a0b-4728-9d60-421e3b35a877", + "metadata": {}, + "outputs": [], + "source": [ + "\"\"\"\n", + "Solution for the Fresh Ingredients puzzle - Part 2.\n", + "Count all unique ingredient IDs that are considered fresh by any range.\n", + "\"\"\"\n", + "\n", + "def parse_input(input_text):\n", + " \"\"\"Parse the input to get fresh ranges (ignore ingredient IDs section).\"\"\"\n", + " parts = input_text.strip().split('\\n\\n')\n", + " \n", + " # Parse fresh ranges (only first section matters now)\n", + " ranges = []\n", + " for line in parts[0].strip().split('\\n'):\n", + " start, end = map(int, line.split('-'))\n", + " ranges.append((start, end))\n", + " \n", + " return ranges\n", + "\n", + "\n", + "def merge_ranges(ranges):\n", + " \"\"\"Merge overlapping ranges to count unique IDs efficiently.\"\"\"\n", + " if not ranges:\n", + " return []\n", + " \n", + " # Sort ranges by start value\n", + " sorted_ranges = sorted(ranges)\n", + " \n", + " merged = [sorted_ranges[0]]\n", + " \n", + " for current_start, current_end in sorted_ranges[1:]:\n", + " last_start, last_end = merged[-1]\n", + " \n", + " # Check if current range overlaps or is adjacent to the last merged range\n", + " if current_start <= last_end + 1:\n", + " # Merge by extending the end if needed\n", + " merged[-1] = (last_start, max(last_end, current_end))\n", + " else:\n", + " # No overlap, add as new range\n", + " merged.append((current_start, current_end))\n", + " \n", + " return merged\n", + "\n", + "\n", + "def count_fresh_ids(ranges):\n", + " \"\"\"Count total unique fresh ingredient IDs.\"\"\"\n", + " merged = merge_ranges(ranges)\n", + " \n", + " total = 0\n", + " for start, end in merged:\n", + " # Count is inclusive: end - start + 1\n", + " total += end - start + 1\n", + " \n", + " return total\n", + "\n", + "\n", + "def solve(input_text):\n", + " \"\"\"Solve part 2 and return the count of all fresh ingredient IDs.\"\"\"\n", + " ranges = parse_input(input_text)\n", + " return count_fresh_ids(ranges)" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "bc525b8f-8c03-4ac1-a52d-1ba2d8c1e5bb", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Puzzle 5.2: .0002 seconds, answer 369761800782619 ok" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "answer(5.2, 369761800782619, lambda:\n", + " solve(input_text))" + ] + }, + { + "cell_type": "markdown", + "id": "b1503029-3a5f-4949-8502-75b051f78a23", + "metadata": {}, + "source": [ + "# [Day 6](https://adventofcode.com/2025/day/6): ChatGPT 5.1 Auto\n", + "\n", + "ChatGPT produced a program that is correct, but has poor abstraction, with one long 63-line function. (It also contains a pet peeve of mine: the pattern \"`if some_boolean: True else: False`\" can always be replaced with \"`some_boolean`\".)" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "fc4e8201-532f-47e8-aa95-e5dad13b8f4f", + "metadata": {}, + "outputs": [], + "source": [ + "import re\n", + "from functools import reduce\n", + "from operator import add, mul\n", + "\n", + "def solve(lines):\n", + " if not lines:\n", + " return 0\n", + "\n", + " # Preserve internal spaces; pad all lines to same width\n", + " width = max(len(line) for line in lines)\n", + " grid = [line.rstrip('\\n').ljust(width) for line in lines]\n", + " h = len(grid)\n", + "\n", + " # Identify separator columns: columns that are all spaces\n", + " sep = []\n", + " for c in range(width):\n", + " if all(grid[r][c] == ' ' for r in range(h)):\n", + " sep.append(True)\n", + " else:\n", + " sep.append(False)\n", + "\n", + " # Find ranges of columns that form individual problems\n", + " problem_ranges = []\n", + " in_problem = False\n", + " start = 0\n", + " for c in range(width):\n", + " if not sep[c]:\n", + " if not in_problem:\n", + " in_problem = True\n", + " start = c\n", + " else:\n", + " if in_problem:\n", + " in_problem = False\n", + " problem_ranges.append((start, c - 1))\n", + " if in_problem:\n", + " problem_ranges.append((start, width - 1))\n", + "\n", + " grand_total = 0\n", + "\n", + " # Process each problem\n", + " for c_start, c_end in problem_ranges:\n", + " numbers = []\n", + "\n", + " # All rows except the last contain numbers (potentially)\n", + " for r in range(h - 1):\n", + " segment = grid[r][c_start:c_end + 1]\n", + " for match in re.finditer(r'\\d+', segment):\n", + " numbers.append(int(match.group(0)))\n", + "\n", + " # Last row contains the operator (+ or *)\n", + " bottom_segment = grid[h - 1][c_start:c_end + 1]\n", + " if '+' in bottom_segment:\n", + " op = add\n", + " elif '*' in bottom_segment:\n", + " op = mul\n", + " else:\n", + " # No valid operator found; skip this malformed problem\n", + " continue\n", + "\n", + " if not numbers:\n", + " continue\n", + "\n", + " # Fold numbers using the chosen operator\n", + " result = reduce(op, numbers)\n", + " grand_total += result\n", + "\n", + " return grand_total" + ] + }, + { + "cell_type": "markdown", + "id": "54e76b17-4372-4074-b7f9-9439c44bc3c4", + "metadata": {}, + "source": [ + "Here I interface my input data to ChatGPT's function and verify it is correct:" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "d6beae51-7205-4cae-b4d7-495da2953baf", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Puzzle 6.1: .0078 seconds, answer 5877594983578 ok" + ] + }, + "execution_count": 27, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "lines = get_text(6).splitlines()\n", + "\n", + "answer(6.1, 5877594983578, lambda:\n", + " solve(lines))" + ] + }, + { + "cell_type": "markdown", + "id": "28de0612-03f3-4587-88cb-18ef9b10ee00", + "metadata": {}, + "source": [ + "For Part 2 we get an even-longer function, with even more complications. For example in \"Determine the operator\"; the 14 lines could be replaced with `op = (add if '+' in segment else mul)`. However, the program is correct." + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "053c3971-9aa5-490a-a016-f303b391c5d8", + "metadata": {}, + "outputs": [], + "source": [ + "import re\n", + "from functools import reduce\n", + "from operator import add, mul\n", + "\n", + "def solve(lines):\n", + " if not lines:\n", + " return 0\n", + "\n", + " # Normalize lines to a rectangle, preserving internal spaces\n", + " width = max(len(line) for line in lines)\n", + " grid = [line.rstrip('\\n').ljust(width) for line in lines]\n", + " h = len(grid)\n", + "\n", + " # A separator column is all spaces -> separates problems\n", + " sep = []\n", + " for c in range(width):\n", + " if all(grid[r][c] == ' ' for r in range(h)):\n", + " sep.append(True)\n", + " else:\n", + " sep.append(False)\n", + "\n", + " # Find contiguous ranges of non-separator columns (each is a problem)\n", + " problem_ranges = []\n", + " in_problem = False\n", + " start = 0\n", + " for c in range(width):\n", + " if not sep[c]:\n", + " if not in_problem:\n", + " in_problem = True\n", + " start = c\n", + " else:\n", + " if in_problem:\n", + " in_problem = False\n", + " problem_ranges.append((start, c - 1))\n", + " if in_problem:\n", + " problem_ranges.append((start, width - 1))\n", + "\n", + " grand_total = 0\n", + "\n", + " for c_start, c_end in problem_ranges:\n", + " # Find the operator row for THIS problem slice:\n", + " # bottom-most row with + or * in [c_start, c_end]\n", + " op_row = None\n", + " for r in range(h):\n", + " segment = grid[r][c_start:c_end + 1]\n", + " if '+' in segment or '*' in segment:\n", + " op_row = r\n", + "\n", + " if op_row is None:\n", + " # No operator for this vertical slice -> skip\n", + " continue\n", + "\n", + " # Determine the operator\n", + " op_segment = grid[op_row][c_start:c_end + 1]\n", + " if '+' in op_segment and '*' in op_segment:\n", + " # If both appear (shouldn't in valid input), choose the leftmost\n", + " plus_idx = op_segment.find('+') if '+' in op_segment else float('inf')\n", + " mul_idx = op_segment.find('*') if '*' in op_segment else float('inf')\n", + " op = add if plus_idx < mul_idx else mul\n", + " elif '+' in op_segment:\n", + " op = add\n", + " elif '*' in op_segment:\n", + " op = mul\n", + " else:\n", + " # No operator actually present\n", + " continue\n", + "\n", + " # Build numbers column-by-column (each column is one number),\n", + " # reading digits from top to just above op_row\n", + " numbers = []\n", + " for c in range(c_start, c_end + 1):\n", + " digits = []\n", + " for r in range(op_row):\n", + " ch = grid[r][c]\n", + " if ch.isdigit():\n", + " digits.append(ch)\n", + " if digits:\n", + " num = int(''.join(digits))\n", + " numbers.append(num)\n", + "\n", + " if not numbers:\n", + " continue\n", + "\n", + " result = reduce(op, numbers)\n", + " grand_total += result\n", + "\n", + " return grand_total" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "e37018e7-0c82-4501-830e-dfe55efc3ad6", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Puzzle 6.2: .0064 seconds, answer 11159825706149 ok" + ] + }, + "execution_count": 29, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "answer(6.2, 11159825706149, lambda:\n", + " solve(lines))" + ] + }, + { + "cell_type": "markdown", + "id": "8aa26008-a652-4860-9c84-5ba4344d32f3", + "metadata": {}, + "source": [ + "# Summary\n", + "\n", + "The LLMs got all the problems right (with only a little extra prompting), and the programs are all reasonably efficient (the Day 2 programs could be faster). The readability of the code varies." + ] + }, + { + "cell_type": "code", + "execution_count": 30, "id": "2d03c39d-42f5-4f51-89b9-638d8d4a4a35", "metadata": {}, "outputs": [ @@ -1025,29 +1437,36 @@ "name": "stdout", "output_type": "stream", "text": [ - "Puzzle 1.1: .0014 seconds, answer 1182 ok\n", - "Puzzle 1.2: .0015 seconds, answer 6907 ok\n", - "Puzzle 2.1: .1303 seconds, answer 23560874270 ok\n", - "Puzzle 2.2: .1816 seconds, answer 44143124633 ok\n", - "Puzzle 3.1: .0040 seconds, answer 17085 ok\n", - "Puzzle 3.2: .0069 seconds, answer 169408143086082 ok\n", - "Puzzle 4.1: .0163 seconds, answer 1569 ok\n", - "Puzzle 4.2: .0761 seconds, answer 9280 ok\n" + "Puzzle 1.1: .0013 seconds, answer 1182 ok\n", + "Puzzle 1.2: .0024 seconds, answer 6907 ok\n", + "Puzzle 2.1: .1263 seconds, answer 23560874270 ok\n", + "Puzzle 2.2: .1350 seconds, answer 44143124633 ok\n", + "Puzzle 3.1: .0044 seconds, answer 17085 ok\n", + "Puzzle 3.2: .0076 seconds, answer 169408143086082 ok\n", + "Puzzle 4.1: .0171 seconds, answer 1569 ok\n", + "Puzzle 4.2: .0937 seconds, answer 9280 ok\n", + "Puzzle 5.1: .0058 seconds, answer 635 ok\n", + "Puzzle 5.2: .0002 seconds, answer 369761800782619 ok\n", + "Puzzle 6.1: .0078 seconds, answer 5877594983578 ok\n", + "Puzzle 6.2: .0064 seconds, answer 11159825706149 ok\n" ] + }, + { + "data": { + "text/plain": [ + "0.40801501274108887" + ] + }, + "execution_count": 30, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ "for d in sorted(answers):\n", - " print(answers[d])" + " print(answers[d])\n", + "sum(a.secs for a in answers.values())" ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d0e8b776-455d-405b-9370-2443daddee9b", - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { diff --git a/ipynb/Advent-2025.ipynb b/ipynb/Advent-2025.ipynb index 4af27cd..0be623a 100644 --- a/ipynb/Advent-2025.ipynb +++ b/ipynb/Advent-2025.ipynb @@ -9,7 +9,7 @@ "\n", "# Advent of Code 2025\n", "\n", - "I enjoy doing the [**Advent of Code**](https://adventofcode.com/) (AoC) programming puzzles, so here we go for 2025! This year I will be doing something different: I will solve each problem, and then [**in another notebook**](Advent-2025-AI.ipynb) I will ask an AI Large Language Model to solve the same problemm, record the response, and comment on it. I'll alternate between Gemini, Claude, and ChatGPT.\n", + "I enjoy doing the [**Advent of Code**](https://adventofcode.com/) (AoC) programming puzzles, so here we go for 2025! This year I will be doing something different: I will solve each problem, and then [**in another notebook**](Advent-2025-AI.ipynb) I will ask an AI Large Language Model to solve the same problem, record the response, and comment on it. I'll alternate between Gemini, Claude, and ChatGPT. So far the LLMs are doing great, but IMHO I prefer my code.\n", "\n", "# Day 0\n", "\n", @@ -25,15 +25,7 @@ "execution_count": 1, "id": "d5f1da68-5da6-434d-a068-1d93497a86b3", "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Matplotlib is building the font cache; this may take a moment.\n" - ] - } - ], + "outputs": [], "source": [ "%run AdventUtils.ipynb\n", "current_year = 2025" @@ -131,7 +123,7 @@ { "data": { "text/plain": [ - "Puzzle 1.1: .0004 seconds, answer 1182 ok" + "Puzzle 1.1: .0006 seconds, answer 1182 ok" ] }, "execution_count": 4, @@ -165,7 +157,7 @@ { "data": { "text/plain": [ - "Puzzle 1.2: .0968 seconds, answer 6907 ok" + "Puzzle 1.2: .1425 seconds, answer 6907 ok" ] }, "execution_count": 5, @@ -180,46 +172,15 @@ }, { "cell_type": "markdown", - "id": "82c39114-fe32-4f98-8ea2-1bb73601883e", + "id": "bb0f6906-369e-4b3c-8840-d5648d713942", "metadata": {}, "source": [ - "I can speed this up by adding up the full-circle rotations separately from the partial rotations. That is, a rotation of \"L995\" does 9 complete circles of the dial (and thus 9 zero crossings), and then moves 95 more clicks (possibly crossing zero once more, depending on where we start). That gives us this:" + "That's a long run time for a Day 1 problem, so here's a faster method: I break each rotation down into a number of full circles and some remainder, then add the full circles to the count of zeros, and add one more if the remainder is at least as much as the distance to zero (in the appropriate direction): " ] }, { "cell_type": "code", "execution_count": 6, - "id": "6e1cda01-5f1d-4363-a923-8c212667afc6", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "Puzzle 1.2: .0312 seconds, answer 6907 ok" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "answer(1.2, 6907, lambda:\n", - " sum(abs(r) // 100 for r in rotations) +\n", - " count_zeros(sign(r) for r in rotations for _ in range(abs(r) % 100)))" - ] - }, - { - "cell_type": "markdown", - "id": "bb0f6906-369e-4b3c-8840-d5648d713942", - "metadata": {}, - "source": [ - "That's three times faster, but still a comparatively long run time for a Day 1 problem, so here's a faster method. I break each rotation down into a number of full circles and some remainder, then add the full circles to the count of zeros, and add one more if the remainder is at least as much as the distance to zero: " - ] - }, - { - "cell_type": "code", - "execution_count": 7, "id": "e0b676d8-3d4f-4b36-835d-b045ece53bd6", "metadata": {}, "outputs": [], @@ -239,17 +200,17 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 7, "id": "8969191e-bd50-4187-8e4b-64524bc8427a", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "Puzzle 1.2: .0007 seconds, answer 6907 ok" + "Puzzle 1.2: .0009 seconds, answer 6907 ok" ] }, - "execution_count": 8, + "execution_count": 7, "metadata": {}, "output_type": "execute_result" } @@ -279,7 +240,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 8, "id": "63bb5099-68ab-41b3-8d0c-2f409433b3f2", "metadata": {}, "outputs": [ @@ -326,7 +287,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 9, "id": "d682f3f2-415e-4556-b6c8-43e331a38703", "metadata": {}, "outputs": [ @@ -336,7 +297,7 @@ "1990936" ] }, - "execution_count": 10, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" } @@ -350,20 +311,20 @@ "id": "a9bf1aab-bd09-49a9-ab57-8b26b1f4a98d", "metadata": {}, "source": [ - "Only 2 million! So it would indeed be feasible to check every one. But I have a suspicion that Part 2 would make it infeasible, so I'll invest in a more efficient approach. For each ID range, instead of enumerating every number in the range and checking each one for validity, I will instead enumerate over the *first half* of the possible digit strings, and automatically generate invalid IDs by appending a copy of the first half to itself.\n", + "Only 2 million! So it would indeed be feasible to check every one. But I have a suspicion that Part 2 would make it infeasible, so I'll invest in a more efficient approach. For each ID range, instead of enumerating every number in the range and checking each one for validity, I will instead enumerate over the *first half* of the possible digit strings, and automatically generate invalid IDs by appending a copy of the first half to itself. By *first half* I mean by digits: the first half of a six-digit number is the first three digits.\n", "\n", "Suppose the range is 123456-223000. I enumerate from 123 to 223, and for each one form generate an invalid ID:\n", - "[123123, 124124, 125125, ... 223223]. I then yield the IDs that are within the range; in this all but the first and the last. Altogether I only have to consider 101 IDs rather than 100,001. The algorithm scales with the square root of the size of the range, not with the size of the range itself." + "[123123, 124124, 125125, ... 223223]. I then yield the IDs that are within the range (in this case all but the first and the last). Altogether I only have to consider 101 IDs rather than 100,001. The algorithm scales with the square root of the size of the range, not with the size of the range itself." ] }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 10, "id": "1345f93d-84c5-43f8-b6c2-9fc7b8f5ed90", "metadata": {}, "outputs": [], "source": [ - "def generate_invalids(lo: int, hi: int) -> Iterable[int]:\n", + "def invalids_in_range(lo: int, hi: int) -> Iterable[int]:\n", " \"\"\"Yield all the invalid IDs between lo and hi inclusive.\n", " An ID is invalid if it consists of a digit sequence repeated twice.\"\"\"\n", " lo_str = str(lo)\n", @@ -375,29 +336,39 @@ " elif id > hi:\n", " return\n", "\n", - "assert list(generate_invalids(11, 22)) == [11, 22]" + "def invalids(id_ranges) -> List[int]:\n", + " \"\"\"Invalid IDs, according to the list of invalid ID ranges.\"\"\"\n", + " return [id for (lo, hi) in id_ranges\n", + " for id in invalids_in_range(lo, hi)]\n", + "\n", + "def invalids(id_ranges) -> List[int]:\n", + " \"\"\"Invalid IDs, according to the list of invalid ID ranges.\"\"\"\n", + " return append(invalids_in_range(lo, hi)\n", + " for (lo, hi) in id_ranges)\n", + "\n", + "assert invalids([(11, 22)]) == [11, 22]" ] }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 11, "id": "8b0d12b3-f184-4149-8b49-f9ff78663d46", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "Puzzle 2.1: .0021 seconds, answer 23560874270 ok" + "Puzzle 2.1: .0027 seconds, answer 23560874270 ok" ] }, - "execution_count": 12, + "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "answer(2.1, 23560874270, lambda:\n", - " sum(sum(generate_invalids(lo, hi)) for lo, hi in id_ranges))" + " sum(invalids(id_ranges)))" ] }, { @@ -407,19 +378,19 @@ "source": [ "### Part 2: What is the sum of the invalid IDs, under the new rules?\n", "\n", - "In Part 2 we discover that an ID should be considered invalid if it repeats a sequence of digits two *or more* times. So 111 (repeated three times), 12121212 (four times), and 222222 (six times) are all invalid. I'll rewrite `generate_invalids` to take an optional argument saying how many repeats we're looking for, and introduce `generate_all_invalids` to try all possible repeat lengths:" + "In Part 2 we discover that an ID should be considered invalid if it consists of two *or more* repeats of a sequence of digits. So 111 (1 repeated three times), 12121212 (12 repeated four times), and 222222 (2 repeated six times) are all invalid. I'll rewrite `invalids_in_range` to take an optional argument saying how many repeats we're looking for, and introduce `all_invalids` to try all possible repeat lengths:" ] }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 12, "id": "6b8d6dad-c5b6-4ed8-8dfa-5acbb54c8001", "metadata": {}, "outputs": [], "source": [ - "def generate_invalids(lo: int, hi: int, repeat=2) -> Iterable[int]:\n", + "def invalids_in_range(lo: int, hi: int, repeat=2) -> Iterable[int]:\n", " \"\"\"Yield all the invalid IDs between lo and hi inclusive\n", - " that are formed from `repeat` sequences.\"\"\"\n", + " that are formed from exactly `repeat` repeated digit sequences.\"\"\"\n", " lo_str = str(lo)\n", " start = int(lo_str[:len(lo_str) // repeat] or 1)\n", " for i in count_from(start):\n", @@ -429,16 +400,15 @@ " elif id > hi:\n", " return\n", "\n", - "def generate_all_invalids(lo: int, hi: int) -> Set[int]:\n", - " \"\"\"All invalid numbers in the range lo to hi inclusive,\n", - " under the rules where 2 or more repeated digit sequences are invalid.\"\"\"\n", - " return {id for repeat in range(2, len(str(hi)) + 1)\n", - " for id in generate_invalids(lo, hi, repeat)}\n", - " \n", - "assert list(generate_invalids(11, 22)) == [11, 22]\n", - "assert list(generate_invalids(2121212118, 2121212124, 5)) == [2121212121]\n", - "assert list(generate_invalids(95, 115, 3)) == [111]\n", - "assert list(generate_all_invalids(95, 115)) == [99, 111]" + "def all_invalids(id_ranges) -> Set[int]:\n", + " \"\"\"All invalid IDs, according to the list of ranges, with any number of repeats.\"\"\"\n", + " return union(invalids_in_range(lo, hi, repeat)\n", + " for (lo, hi) in id_ranges\n", + " for repeat in range(2, len(str(hi)) + 1))\n", + "\n", + "assert invalids([(11, 22)]) == [11, 22]\n", + "assert list(invalids_in_range(2121212118, 2121212124, 5)) == [2121212121]\n", + "assert all_invalids([(95, 115)]) == {99, 111}" ] }, { @@ -451,14 +421,36 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 13, "id": "8a7c6c25-4b5f-4178-8559-166ba1a9f924", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "Puzzle 2.1: .0021 seconds, answer 23560874270 ok" + "Puzzle 2.1: .0031 seconds, answer 23560874270 ok" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "answer(2.1, 23560874270, lambda:\n", + " sum(invalids(id_ranges)))" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "32fefd65-df2a-4ea3-9acd-7525ebd32380", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Puzzle 2.2: .0040 seconds, answer 44143124633 ok" ] }, "execution_count": 14, @@ -466,31 +458,9 @@ "output_type": "execute_result" } ], - "source": [ - "answer(2.1, 23560874270, lambda:\n", - " sum(sum(generate_invalids(lo, hi)) for lo, hi in id_ranges))" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "id": "32fefd65-df2a-4ea3-9acd-7525ebd32380", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "Puzzle 2.2: .0032 seconds, answer 44143124633 ok" - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], "source": [ "answer(2.2, 44143124633, lambda:\n", - " sum(sum(generate_all_invalids(lo, hi)) for lo, hi in id_ranges))" + " sum(all_invalids(id_ranges)))" ] }, { @@ -498,7 +468,7 @@ "id": "872cf212-bfbf-4edd-b898-5f76ad122a85", "metadata": {}, "source": [ - "I had another **bug** here: initially I counted \"222222\" twice: once as 2 repeats of \"222\" and once as 3 repeats of \"22\". I changed the output of `generate_all_invalids` to be a set to fix that." + "I had another **bug** here: initially I counted \"222222\" twice: once as 2 repeats of \"222\" and once as 3 repeats of \"22\". I changed the output of `all_invalids` to be a set to fix that." ] }, { @@ -513,7 +483,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 15, "id": "04ef4f2f-2b07-43cd-87d4-a8f63f7fe840", "metadata": {}, "outputs": [ @@ -552,7 +522,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 16, "id": "d1d18422-9054-4d89-85ed-37f3a21a2ef6", "metadata": {}, "outputs": [], @@ -574,17 +544,17 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 17, "id": "9cc62ae9-b313-4b82-b8fb-bab9c0ef5cb6", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "Puzzle 3.1: .0005 seconds, answer 17085 ok" + "Puzzle 3.1: .0006 seconds, answer 17085 ok" ] }, - "execution_count": 18, + "execution_count": 17, "metadata": {}, "output_type": "execute_result" } @@ -608,7 +578,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 18, "id": "b79011e6-b448-4b71-88ca-3301888a7f29", "metadata": {}, "outputs": [], @@ -642,7 +612,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 19, "id": "fe3cff78-81c0-4d4a-bb4d-f0f841067e0d", "metadata": {}, "outputs": [ @@ -652,7 +622,7 @@ "Puzzle 3.1: .0006 seconds, answer 17085 ok" ] }, - "execution_count": 20, + "execution_count": 19, "metadata": {}, "output_type": "execute_result" } @@ -664,17 +634,17 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 20, "id": "f971839e-81ea-49b4-a92f-a44884be645d", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "Puzzle 3.2: .0015 seconds, answer 169408143086082 ok" + "Puzzle 3.2: .0020 seconds, answer 169408143086082 ok" ] }, - "execution_count": 21, + "execution_count": 20, "metadata": {}, "output_type": "execute_result" } @@ -696,7 +666,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 21, "id": "c9f227d5-2748-48e1-80c1-7385dad46323", "metadata": {}, "outputs": [ @@ -728,14 +698,17 @@ "id": "4dd00e21-228c-41f6-a28c-e2213e60d4ce", "metadata": {}, "source": [ + "\"Gary\n", + "\n", + "\n", "### Part 1: How many rolls of paper can be accessed by a forklift?\n", "\n", - "A roll is **accessible** by forklift if there are fewer than four rolls of paper in the eight adjacent positions. Counting the number of accessible rolls is easy, but I decided to make `accessible rolls` return a list of positions, because I might need that in Part 2." + "A roll is **accessible** by forklift if there are fewer than four rolls of paper in the eight adjacent positions (that's why I specified `directions=directions8` in defining `paper_grid`). Counting the number of accessible rolls is easy, but I decided to make `accessible rolls` return a list of positions, because I might need that in Part 2." ] }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 22, "id": "6fe5bd44-8a28-4d7a-bc8b-edc4af8f23c3", "metadata": {}, "outputs": [], @@ -743,8 +716,11 @@ "def accessible_rolls(grid: Grid) -> List[Point]:\n", " \"\"\"A roll of paper is accessible if there are fewer than \n", " four rolls of paper in the eight adjacent positions.\"\"\"\n", - " return [p for p in grid if grid[p] == '@'\n", - " if grid.neighbor_contents(p).count('@') < 4]" + " return [p for p in grid if is_accessible(p, grid)]\n", + "\n", + "def is_accessible(p: Point, grid: Grid) -> bool:\n", + " \"\"\"Is point p on the grid an accessible paper roll?\"\"\"\n", + " return grid[p] == '@' and grid.neighbor_contents(p).count('@') < 4" ] }, { @@ -757,17 +733,17 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 23, "id": "a5ef09cf-b204-41eb-80d8-de107d385dbb", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "Puzzle 4.1: .0331 seconds, answer 1569 ok" + "Puzzle 4.1: .0531 seconds, answer 1569 ok" ] }, - "execution_count": 24, + "execution_count": 23, "metadata": {}, "output_type": "execute_result" } @@ -784,41 +760,41 @@ "source": [ "### Part 2: How many rolls of paper can be removed?\n", "\n", - "If the elves can access a paper roll, they can remove it by forklift. That may make other rolls accessible. How many in total can be removed?\n", + "If the elves can access a paper roll, they can remove it by forklift. That may make other rolls accessible, enabling them to remove more. How many rolls in total can be removed?\n", "\n", "It looks like I was right to make `accessible_rolls` return a list of points rather than a count! I can answer the question by repeatedly finding the accessible rolls, removing them (on a copy of the grid so I don't mess up the original grid), and repeating until there are no more accessible rolls." ] }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 24, "id": "0ed53853-268c-4c2f-a929-cb3e6005a348", "metadata": {}, "outputs": [], "source": [ "def removable_rolls(grid: Grid) -> Iterable[Point]:\n", " \"\"\"The positions of paper rolls that can be removed, in any nuber of iterations.\"\"\"\n", - " grid = grid.copy() # To avoid mutating the input grid\n", - " points = accessible_rolls(grid)\n", + " grid2 = grid.copy() # To avoid mutating the input grid\n", + " points = accessible_rolls(grid2)\n", " while points:\n", " yield from points\n", - " grid.update({p: '.' for p in points})\n", - " points = accessible_rolls(grid)" + " grid2.update({p: '.' for p in points})\n", + " points = accessible_rolls(grid2)" ] }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 25, "id": "2fb17a51-05f7-42ec-8d6c-222121a026cf", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "Puzzle 4.2: .7829 seconds, answer 9280 ok" + "Puzzle 4.2: 1.2392 seconds, answer 9280 ok" ] }, - "execution_count": 26, + "execution_count": 25, "metadata": {}, "output_type": "execute_result" } @@ -833,67 +809,469 @@ "id": "7143f73e-3b9b-49f3-bfa9-625899a56e37", "metadata": {}, "source": [ - "That's the right answer, but the run time is slow. One issue is that `accessible_rolls` has to look at the whole grid on every iteration. If the previous iteration only removed one or two rolls, that's a waste of time. Instead, we can keep a queue of possibly removable points (initially the points with a paper roll) and repeatedly pop a point off the queue, and if it is an accessible roll, remove it and put all its neighbors on the queue." + "That's the right answer, but the run time is slow. The main issue is that `accessible_rolls` has to look at the whole grid on every iteration. If the previous iteration only removed one roll, all we really need to look at on the next iteration is the neighbors of the removed roll. So I'll keep a queue of possibly removable points and repeatedly pop a point off the queue, and if it is an accessible roll, remove it and put all its neighbors on the queue." + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "54f20b5e-6713-459c-8d40-e545ce6b8e42", + "metadata": {}, + "outputs": [], + "source": [ + "def removable_rolls(grid: Grid) -> Iterable[Point]:\n", + " \"\"\"The positions of paper rolls that can be removed, in any nuber of iterations.\"\"\"\n", + " grid2 = grid.copy() # To avoid mutating the original input grid\n", + " Q = list(grid) # A queue of possibly removable positions in the grid\n", + " while Q:\n", + " p = Q.pop()\n", + " if is_accessible(p, grid2):\n", + " yield p\n", + " grid2[p] = '.'\n", + " Q.extend(grid2.neighbors(p))" ] }, { "cell_type": "code", "execution_count": 27, - "id": "15741f57-8527-4427-8cea-84951a1d14ee", - "metadata": {}, - "outputs": [], - "source": [ - "def count_removable_rolls(grid1: Grid) -> int:\n", - " \"\"\"Count the number of paper rolls that can be removed.\"\"\"\n", - " grid = grid1.copy() # To avoid mutating the original input grid\n", - " Q = grid.findall('@') # A queue of possibly removable positions in the grid\n", - " while Q:\n", - " p = Q.pop()\n", - " if grid[p] == '@' and grid.neighbor_contents(p).count('@') < 4:\n", - " grid[p] = '.'\n", - " Q.extend(grid.neighbors(p))\n", - " return len(grid1.findall('@')) - len(grid.findall('@')) # The number of '@' removed" - ] - }, - { - "cell_type": "code", - "execution_count": 28, "id": "bcba970b-09aa-479b-9c6d-4f6a7ac49fed", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "Puzzle 4.2: .0916 seconds, answer 9280 ok" + "Puzzle 4.2: .1394 seconds, answer 9280 ok" ] }, - "execution_count": 28, + "execution_count": 27, "metadata": {}, "output_type": "execute_result" } ], "source": [ "answer(4.2, 9280, lambda:\n", - " count_removable_rolls(paper_grid))" + " quantify(removable_rolls(paper_grid)))" + ] + }, + { + "cell_type": "markdown", + "id": "1f9a1e40-192a-4386-8cfb-bc5f69c88a9b", + "metadata": {}, + "source": [ + "# [Day 5](https://adventofcode.com/2025/day/5): Cafeteria\n", + "\n", + "Today we're in the cafeteria, and the elves need to figure out which of their ingredients are fresh or spoiled. The input file is in two paragraphs, the first consisting of ranges of fresh ingredient IDs, like \"3-5\" and the second consisting of the available ingredient IDs, like \"8\". I can parse the data like this:" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "4b508db5-aeae-410e-a062-1b2d4ce41253", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "────────────────────────────────────────────────────────────────────────────────────────────────────\n", + "Puzzle input ➜ 1184 strs:\n", + "────────────────────────────────────────────────────────────────────────────────────────────────────\n", + "292632986393425-296797126337251\n", + "428261559408337-431275643240865\n", + "197704206528056-198822557342819\n", + "36791726875734-37049023408764\n", + "134880223152389-139959748438608\n", + "31870818340663-32138457068292\n", + "443613579514078-447616030778273\n", + "284142407577672-288117756872436\n", + "...\n", + "────────────────────────────────────────────────────────────────────────────────────────────────────\n", + "Parsed representation ➜ 2 tuples:\n", + "────────────────────────────────────────────────────────────────────────────────────────────────────\n", + "((292632986393425, 296797126337251), (428261559408337, 431275643240865), (197704206528056, 19882 ...\n", + "(92087202605588, 314304587960778, 19184152482180, 331502336245673, 104093299503920, 224082459481 ...\n" + ] + } + ], + "source": [ + "def parse_ingredients(text: str) -> tuple:\n", + " \"\"\"Parse either ranges like \"3-5\", or integers like \"8\".\"\"\"\n", + " return mapt(positive_ints, lines(text)) if '-' in text else ints(text)\n", + " \n", + "fresh_ranges, available_ingredient_ids = parse(5, parse_ingredients, sections=paragraphs)" + ] + }, + { + "cell_type": "markdown", + "id": "ebb61d5f-4683-4ff6-986c-69154d512e1b", + "metadata": {}, + "source": [ + "### Part 1: How many of the available ingredient IDs are fresh?\n", + "\n", + "I saw that the input file is only a few pages long, so I don't need any fancy data structures (like binary trees); I can approach this in the simplest way: for each ingredient ID, check to see if it is contained in any of the ranges:" ] }, { "cell_type": "code", "execution_count": 29, - "id": "c4afcd9a-bf21-4b50-9081-9739e87eef30", + "id": "ae5b33e4-92f5-4fe2-bd5a-6b22095724fa", "metadata": {}, "outputs": [], "source": [ - "# [Day 5](https://adventofcode.com/2025/day/5): Cafeteria" + "def count_fresh_ingredients(fresh_ranges, available_ingredient_ids) -> int:\n", + " \"\"\"How many of the available ingredient IDs are in one of the fresh ranges?\"\"\"\n", + " return quantify(any(lo <= id <= hi for (lo, hi) in fresh_ranges)\n", + " for id in available_ingredient_ids)" ] }, { - "cell_type": "raw", - "id": "425a0e43-9240-4fdf-983e-53ac0196bea2", + "cell_type": "code", + "execution_count": 30, + "id": "112a84f7-9bb8-45f0-9d7e-f600f37f0fdf", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Puzzle 5.1: .0072 seconds, answer 635 ok" + ] + }, + "execution_count": 30, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "answer(5.1, 635, lambda:\n", + " count_fresh_ingredients(fresh_ranges, available_ingredient_ids))" + ] + }, + { + "cell_type": "markdown", + "id": "01c2c322-e873-412f-9c47-59ffdf9adf79", "metadata": {}, "source": [ - "ingredient_ids = parse(5, positive_ints)\n", - "ingredient_ids" + "### Part 2: How many ingredient IDs are fresh?\n", + "\n", + "In Part 2 we are asked how many of the possible ingredient IDs are fresh, regardless of whether the ID is available or not. I could convert a range like \"3-5\" into the set {3, 4, 5} and then union the sets together. How many elements would be in that set? Here's an estimate: " + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "47a660d2-746c-433f-b4ae-9d01f70bc504", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "476036797138761" + ] + }, + "execution_count": 31, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "sum(hi - lo for (lo, hi) in fresh_ranges)" + ] + }, + { + "cell_type": "markdown", + "id": "1360869f-3ccb-4c71-8d00-35de1215389f", + "metadata": {}, + "source": [ + "OK, switch to Plan B. Instead of explicitly building this set I can instead sort the fresh ID ranges (by their low number) and go through them, keeping track of the lowest ID that has not yet been explored for freshness, and a count of the fresh IDs found so far. For each range, the number of new fresh IDs is the length of the range that starts with either the start of the range or the first unexplored ID, and ends at the end of the range." + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "f52e8ecd-325e-4ed4-8928-de9365b5b7d4", + "metadata": {}, + "outputs": [], + "source": [ + "def count_fresh_ids(fresh_ranges) -> int:\n", + " \"\"\"How many IDs are contained in the union of the ranges?\"\"\"\n", + " fresh_count = 0\n", + " unexplored = 0\n", + " for (lo, hi) in sorted(fresh_ranges):\n", + " start = max(unexplored, lo)\n", + " fresh_count += len(range(start, hi + 1))\n", + " unexplored = max(unexplored, hi + 1)\n", + " return fresh_count" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "fab6f1ed-543f-4f42-b1d5-2551742e3a4f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Puzzle 5.2: .0001 seconds, answer 369761800782619 ok" + ] + }, + "execution_count": 33, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "answer(5.2, 369761800782619, lambda:\n", + " count_fresh_ids(fresh_ranges))" + ] + }, + { + "cell_type": "markdown", + "id": "6b83e353-3b77-4645-85b9-2f6ee6ff9e1d", + "metadata": {}, + "source": [ + "# [Day 6](https://adventofcode.com/2025/day/5): Trash Conmpactor\n", + "\n", + "Trash Compactor? [I've got a bad feeling about this!](https://youtu.be/CZgeYSqUeTA?si=5UPS_HiCOmTKrEWX&t=32) We've fallen into a garbage smasher and have been asked to help some of the resident cephalopods with their math homework. We can parse the homework worksheet, but we were told that the exact alignment of columns matters, so I'll keep each line as a string rather than converting it to a list of ints." + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "bca852a9-e4c5-4706-abfc-afc6d4a4eeb5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "────────────────────────────────────────────────────────────────────────────────────────────────────\n", + "Puzzle input ➜ 5 strs:\n", + "────────────────────────────────────────────────────────────────────────────────────────────────────\n", + " 4 82 68 85 74 6 56 14 2 8669 66 13 927 3 235 44 52 16 37 61 82 1774 96 15 775 789 91 ...\n", + " 6 87 39 72 56 12 69 79 58 4378 86 49 146 5 412 85 7751 577 69 813 55 9942 753 49 734 587 15 ...\n", + " 827 446 82 72 76 21 31 32 96 3257 21 21 171 2 31 17 9178 977 11 469 58 712 162 4 1 132 91 ...\n", + "9472 154 36 76 5 89 37 5 28 6 95 49 82 66 7 44 8183 524 38 819 11 7 825 9 8 397 48 ...\n", + "+ + + * * * + * * + * + * * + + + + * + * + * + * * * ...\n" + ] + } + ], + "source": [ + "worksheet = parse(6, str)" + ] + }, + { + "cell_type": "markdown", + "id": "ae8a7f1d-07ec-46e4-880f-abbd82d46a67", + "metadata": {}, + "source": [ + "### Part 1: What is the grand total of the answers to the individual problems?\n", + "\n", + "We humans are used to arithmetic problems written in left-to-right order, but cephalopods use top-to-bottom order, with a postfix operator in the last row. We need to compute the sum or product for each column and add all those results together. So the problem in the first column above would be \"4 + 6 + 827 + 9472\". Here's the code. (Note my utility function `T` is the matrix transpose function, which turns rows into columns, which is just what we need for this puzzle.)" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "92f320f4-c7a3-4dfd-ae61-6b1b180bdc93", + "metadata": {}, + "outputs": [], + "source": [ + "def grand_total(worksheet):\n", + " \"\"\"The sum of the individual sum/product problems, where each column is a problem.\"\"\"\n", + " columns = T(map(str.split, worksheet))\n", + " operations = {'*': prod, '+': sum}\n", + " return sum(operations[op](mapt(int, numbers)) \n", + " for (*numbers, op) in columns)" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "92a47f3a-127f-41e8-bee1-6276885bd36b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Puzzle 6.1: .0015 seconds, answer 5877594983578 ok" + ] + }, + "execution_count": 36, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "answer(6.1, 5877594983578, lambda:\n", + " grand_total(worksheet))" + ] + }, + { + "cell_type": "markdown", + "id": "226772ae-0d6e-43b8-9159-171ec1b36a5d", + "metadata": {}, + "source": [ + "### Part 2: What is the grand total of the answers to the individual problems now?\n", + "\n", + "We learn that we did all the problems wrong. Cephalopodish number notation is different; numbers are read vertically rather than horizontally and the exact column alignment of each digit matters. Given the worksheet:\n", + "\n", + " 4 82 68 85 74 6 56 14 2 8669 66 13 927 3 235 44 \n", + " 6 87 39 72 56 12 69 79 58 4378 86 49 146 5 412 85 \n", + " 827 446 82 72 76 21 31 32 96 3257 21 21 171 2 31 17 \n", + " 9472 154 36 76 5 89 37 5 28 6 95 49 82 66 7 44 \n", + " + + + * * * + * * + * + * * + + \n", + "\n", + "The problem in the leftmost column is not \"`4 + 6 + 827 + 9472`\", rather it is \"`9 + 84 + 27 + 4672`\".\n", + "\n", + "That means I can't just split each line into numbers, I'll have to be careful to maintain the blank spaces, and I have to know the difference between a blank space that marks a column between problems versus a blank space that marks the lack of a digit. In `grand_total2` I first break each line into columns (which requires finding the columns that are all blank spaces), then I do the math." + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "f332aec9-dbfa-4bf9-b338-9a33c5f3bd89", + "metadata": {}, + "outputs": [], + "source": [ + "def grand_total2(worksheet: List[str]):\n", + " \"\"\"Solve the problem in each column with cephalopodish math and return the sum.\"\"\"\n", + " problems = T(break_into_columns(worksheet))\n", + " return sum(map(cephalopodish_math, problems))\n", + "\n", + "def break_into_columns(worksheet: List[str]) -> List[List[str]]:\n", + " \"\"\"Break a whole worksheet into columns by finding the `blanks` (the columns with\n", + " only blank spaces) and then separating each line at these posisitons.\"\"\"\n", + " worksheet = [' ' + line + ' ' for line in worksheet] # Blanks on each end of line\n", + " *number_lines, operator_line = worksheet\n", + " blanks = [i for i in range(len(worksheet[0]))\n", + " if all(line[i] == ' ' for line in number_lines)]\n", + " return [break_line_into_columns(line, blanks) for line in worksheet]\n", + "\n", + "def break_line_into_columns(line: str, blanks: List[int]) -> List[str]:\n", + " \"\"\"Break one line into columns, as specified by the list of `blanks` positions.\"\"\"\n", + " return [line[blanks[i]+1:blanks[i + 1]] \n", + " for i in range(len(blanks) - 1)]\n", + "\n", + "def cephalopodish_math(problem) -> int:\n", + " \"\"\"Return the sum or product of the vertically-arranged numbers.\"\"\"\n", + " *numbers, op = problem\n", + " sum_or_prod = {'+': sum, '*': prod}[op.strip()]\n", + " return sum_or_prod(int(cat(digits)) for digits in T(numbers))" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "39a8fa78-946f-45aa-8d36-76883f4aeaff", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Puzzle 6.2: .0082 seconds, answer 11159825706149 ok" + ] + }, + "execution_count": 38, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "answer(6.2, 11159825706149, lambda:\n", + " grand_total2(worksheet))" + ] + }, + { + "cell_type": "markdown", + "id": "ca461ef3-50c3-4189-b724-5f2b3898f27d", + "metadata": {}, + "source": [ + "I initially had an `IndexError` **bug** because the operator line is shorter then the numbers lines. Then I had an off-by-one **bug** that made me mess up the problem in the last column. To debug the problems I worked on the smaller example worksheet:" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "a13dbe89-3178-433f-9a50-28ed9a2f8358", + "metadata": {}, + "outputs": [], + "source": [ + "worksheet2 = \"\"\"\\\n", + "123 328 51 64 \n", + " 45 64 387 23 \n", + " 6 98 215 314\n", + "* + * +\"\"\".splitlines()" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "id": "3aa0b661-367a-4316-905a-60f1d9e6df01", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[['123', '328', ' 51', '64 '],\n", + " [' 45', '64 ', '387', '23 '],\n", + " [' 6', '98 ', '215', '314'],\n", + " ['* ', '+ ', '* ', '+ ']]" + ] + }, + "execution_count": 40, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "break_into_columns(worksheet2)" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "13392d9c-fef8-4946-b945-48ad6bc7eca9", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[('123', ' 45', ' 6', '* '),\n", + " ('328', '64 ', '98 ', '+ '),\n", + " (' 51', '387', '215', '* '),\n", + " ('64 ', '23 ', '314', '+ ')]" + ] + }, + "execution_count": 41, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "problems = T(break_into_columns(worksheet2))\n", + "problems" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "id": "9a25ff50-f465-4de3-95ca-71b7a3d9569b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(8544, 625, 3253600, 1058)" + ] + }, + "execution_count": 42, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "mapt(cephalopodish_math, problems)" ] }, { @@ -906,7 +1284,7 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 43, "id": "ba36579c-d0b4-4fd3-939c-0026ecddd7e9", "metadata": {}, "outputs": [ @@ -914,20 +1292,35 @@ "name": "stdout", "output_type": "stream", "text": [ - "Puzzle 1.1: .0004 seconds, answer 1182 ok\n", - "Puzzle 1.2: .0007 seconds, answer 6907 ok\n", - "Puzzle 2.1: .0021 seconds, answer 23560874270 ok\n", - "Puzzle 2.2: .0032 seconds, answer 44143124633 ok\n", + "Puzzle 1.1: .0006 seconds, answer 1182 ok\n", + "Puzzle 1.2: .0009 seconds, answer 6907 ok\n", + "Puzzle 2.1: .0031 seconds, answer 23560874270 ok\n", + "Puzzle 2.2: .0040 seconds, answer 44143124633 ok\n", "Puzzle 3.1: .0006 seconds, answer 17085 ok\n", - "Puzzle 3.2: .0015 seconds, answer 169408143086082 ok\n", - "Puzzle 4.1: .0331 seconds, answer 1569 ok\n", - "Puzzle 4.2: .0916 seconds, answer 9280 ok\n" + "Puzzle 3.2: .0020 seconds, answer 169408143086082 ok\n", + "Puzzle 4.1: .0531 seconds, answer 1569 ok\n", + "Puzzle 4.2: .1394 seconds, answer 9280 ok\n", + "Puzzle 5.1: .0072 seconds, answer 635 ok\n", + "Puzzle 5.2: .0001 seconds, answer 369761800782619 ok\n", + "Puzzle 6.1: .0015 seconds, answer 5877594983578 ok\n", + "Puzzle 6.2: .0082 seconds, answer 11159825706149 ok\n" ] + }, + { + "data": { + "text/plain": [ + "0.22060370445251465" + ] + }, + "execution_count": 43, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ "for d in sorted(answers):\n", - " print(answers[d])" + " print(answers[d])\n", + "sum(a.secs for a in answers.values())" ] }, { @@ -955,7 +1348,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.13.5" + "version": "3.8.15" } }, "nbformat": 4, diff --git a/ipynb/AdventUtils.ipynb b/ipynb/AdventUtils.ipynb index 599e5c8..fb3fb03 100644 --- a/ipynb/AdventUtils.ipynb +++ b/ipynb/AdventUtils.ipynb @@ -15,7 +15,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 21, "metadata": {}, "outputs": [], "source": [ @@ -119,9 +119,20 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 18, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "((9, 5), (123, 456))" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "Char = str # Intended as the type of a one-character string\n", "Atom = Union[str, float, int] # The type of a string or number\n", @@ -211,7 +222,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 14, "metadata": {}, "outputs": [], "source": [