Add files via upload
This commit is contained in:
parent
5c1c011e3d
commit
0d92f5de35
@ -9,13 +9,13 @@
|
|||||||
"\n",
|
"\n",
|
||||||
"# Advent of Code 2025\n",
|
"# Advent of Code 2025\n",
|
||||||
"\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 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",
|
"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, and I'll comment on it. So far the LLMs are doing great, but IMHO I prefer my code.\n",
|
||||||
"\n",
|
"\n",
|
||||||
"# Day 0\n",
|
"# Day 0\n",
|
||||||
"\n",
|
"\n",
|
||||||
"I'm glad that [@GaryGrady](https://mastodon.social/@garygrady) is providing cartoons:\n",
|
"I'm glad that [@GaryGrady](https://mastodon.social/@garygrady) is providing cartoons:\n",
|
||||||
"\n",
|
"\n",
|
||||||
"<a href=\"https://x.com/garyjgrady\"><img src=\"https://pbs.twimg.com/media/Gdp709FW8AAq2_m?format=jpg&name=medium\" width=400 alt=\"Gary Grady cartoon\"></a>\n",
|
"<a href=\"https://x.com/garyjgrady\"><img src=\"https://pbs.twimg.com/media/Gdp709FW8AAq2_m?format=jpg&name=medium\" width=500 alt=\"Gary Grady cartoon\"></a>\n",
|
||||||
"\n",
|
"\n",
|
||||||
"I start by loading up my [**AdventUtils.ipynb**](AdventUtils.ipynb) notebook (same as last time except for the `current_year`). On each day I will first parse the input (with the help of my `parse` utility function), then solve Part 1 and Part 2 (recording the correct answer with my `answer` function)."
|
"I start by loading up my [**AdventUtils.ipynb**](AdventUtils.ipynb) notebook (same as last time except for the `current_year`). On each day I will first parse the input (with the help of my `parse` utility function), then solve Part 1 and Part 2 (recording the correct answer with my `answer` function)."
|
||||||
]
|
]
|
||||||
@ -38,12 +38,12 @@
|
|||||||
"source": [
|
"source": [
|
||||||
"# [Day 1](https://adventofcode.com/2025/day/1): Secret Entrance\n",
|
"# [Day 1](https://adventofcode.com/2025/day/1): Secret Entrance\n",
|
||||||
"\n",
|
"\n",
|
||||||
"On Day 1 we meet an elf and learn that our task is to finish decorating the North Pole by December 12th. There will be challenges along the way. Today we need to unlock a safe. The safe has a dial with 100 numbers. Our input for today is a sequence of left and right rotations; for example \"R20\" means move the dial right by 20 numbers. I'll use my `parse` utility function to parse each line of the input as an integer, after replacing each 'L' with a minus sign and each 'R' with a plus sign:"
|
"On Day 1 we meet an elf and learn that our task is to finish decorating the North Pole by December 12th. There will be 24 challenges along the way. Today we need to unlock a safe. The safe has a dial with 100 numbers. Our input for today is a sequence of left and right rotations; for example \"R20\" means move the dial right by 20 numbers and \"L13\" means move it left by 13 numbers. I'll use my `parse` utility function to parse each line of the input as an integer, after replacing each 'L' with a minus sign and each 'R' with a plus sign:"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 2,
|
"execution_count": 45,
|
||||||
"id": "ed911a15-addc-4c04-8546-2c9f37aee341",
|
"id": "ed911a15-addc-4c04-8546-2c9f37aee341",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [
|
"outputs": [
|
||||||
@ -79,7 +79,9 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"source": [
|
"source": [
|
||||||
"rotations = parse(day=1, parser=lambda line: int(line.replace('L', '-').replace('R', '+')))"
|
"def parse_rotation(line: str): return int(line.replace('L', '-').replace('R', '+'))\n",
|
||||||
|
"\n",
|
||||||
|
"rotations = parse(day=1, parser=parse_rotation) "
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -87,11 +89,13 @@
|
|||||||
"id": "7c98c883-d1dc-4d4e-9590-d47b1de000a0",
|
"id": "7c98c883-d1dc-4d4e-9590-d47b1de000a0",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": [
|
||||||
"<img src=\"https://files.mastodon.social/media_attachments/files/115/646/343/679/448/846/original/428b312ca88f62c4.jpg\" width=400 alt=\"Gary Grady cartoon\">\n",
|
"Note that my `parse` function prints the first few input lines, then prints the parsed representation of these lines. That helps me debug and helps you the reader understand what is going on.\n",
|
||||||
|
"\n",
|
||||||
|
"<img src=\"https://files.mastodon.social/media_attachments/files/115/646/343/679/448/846/original/428b312ca88f62c4.jpg\" width=500 alt=\"Gary Grady cartoon\">\n",
|
||||||
"\n",
|
"\n",
|
||||||
"### Part 1: How many times is the dial left pointing at 0 after any rotation in the sequence?\n",
|
"### Part 1: How many times is the dial left pointing at 0 after any rotation in the sequence?\n",
|
||||||
"\n",
|
"\n",
|
||||||
"Initially the safe's arrow is pointing at 50, and then we apply the rotations in order. We are asked how many of the rotations leave the dial pointing at 0. The `itertools.accumulate` function yields running totals of its input sequence, so we just have to count (quantify) how many times the running total of the rotations is 0 mod 100:"
|
"Initially the safe's arrow is pointing at 50, and then we apply the rotations in order. We are asked how many of the rotations leave the dial pointing at 0. The `itertools.accumulate` function yields running totals of its input sequence, so we just have to count (quantify) how many times the running total of the rotations is 0 mod 100. I'll write a function to do that and run it on the input:"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -106,12 +110,33 @@
|
|||||||
" return quantify(total % dial == 0 for total in accumulate(numbers, initial=50))"
|
" return quantify(total % dial == 0 for total in accumulate(numbers, initial=50))"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 46,
|
||||||
|
"id": "eb90f15c-8f87-4d94-b49c-fcaa53aab8ae",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"text/plain": [
|
||||||
|
"1182"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"execution_count": 46,
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "execute_result"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"count_zeros(rotations)"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
"id": "b5f68a70-8465-4249-954c-cba2b78751d8",
|
"id": "b5f68a70-8465-4249-954c-cba2b78751d8",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": [
|
||||||
"Here's the process I repeat for each puzzle: I ran `count_zeros(rotations)`, submitted the answer to AoC, and once I saw it was correct, I recorded the answer as follows:"
|
"I submitted \"1182\" to AoC and saw it was correct, so I record the `answer` like this:"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -143,7 +168,7 @@
|
|||||||
"source": [
|
"source": [
|
||||||
"### Part 2: How many times does the dial point to 0 at any time?\n",
|
"### Part 2: How many times does the dial point to 0 at any time?\n",
|
||||||
"\n",
|
"\n",
|
||||||
"For Part 2 we need to count both when a rotation ends up at 0 and when the arrow passes 0 in the middle of the rotation. For example, if the arrow points to 95, then only a \"R5\" or a \"L95\" would register a 0 in Part 1, but now a rotation of \"R10\" would count because it passes 0 (as would any rotation of \"R5\" or larger, or \"L95\" or larger). \n",
|
"For Part 2 we need to count both when a rotation ends up at 0 and when the arrow passes 0 at any time during a rotation. For example, if the arrow points to 95, then only a \"R5\" or a \"L95\" would register a 0 in Part 1, but now a rotation of \"R10\" would also count because it passes 0 (as would any rotation of \"R5\" or larger, or \"L95\" or larger). \n",
|
||||||
"\n",
|
"\n",
|
||||||
"I'll start with a simple but slow approach: treat a rotation of, say, -20 as 20 rotations of -1, and then use the same `count_zeros` function from part 1. (Note that `sign(r)` returns +1 for any positive input, and -1 for any negative input.)"
|
"I'll start with a simple but slow approach: treat a rotation of, say, -20 as 20 rotations of -1, and then use the same `count_zeros` function from part 1. (Note that `sign(r)` returns +1 for any positive input, and -1 for any negative input.)"
|
||||||
]
|
]
|
||||||
@ -235,12 +260,12 @@
|
|||||||
"source": [
|
"source": [
|
||||||
"# [Day 2](https://adventofcode.com/2025/day/2): Gift Shop\n",
|
"# [Day 2](https://adventofcode.com/2025/day/2): Gift Shop\n",
|
||||||
"\n",
|
"\n",
|
||||||
"Today we're in the North Pole gift shop, and are asked to help the elves identify invalid product IDs on the items there. We're giving a list of ranges of product IDs. Each range is a pair of integers separated by a dash, and the ranges are separated by commas:"
|
"Today we're in the North Pole gift shop, and are asked to help the elves identify invalid product IDs on the items there. We're giving a list of ranges of product IDs of items in stock. Each range is a pair of integers separated by a dash, and the ranges are separated by commas:"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 8,
|
"execution_count": 47,
|
||||||
"id": "63bb5099-68ab-41b3-8d0c-2f409433b3f2",
|
"id": "63bb5099-68ab-41b3-8d0c-2f409433b3f2",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [
|
"outputs": [
|
||||||
@ -276,11 +301,11 @@
|
|||||||
"id": "a5463e74-a3a1-4c79-9497-a1e307ef81e2",
|
"id": "a5463e74-a3a1-4c79-9497-a1e307ef81e2",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": [
|
||||||
"<img src=\"https://files.mastodon.social/media_attachments/files/115/652/152/368/251/243/original/56e4ed8e5f24db96.jpg\" width=400 alt=\"GaryJGrady cartoon\">\n",
|
"<img src=\"https://files.mastodon.social/media_attachments/files/115/652/152/368/251/243/original/56e4ed8e5f24db96.jpg\" width=500 alt=\"GaryJGrady cartoon\">\n",
|
||||||
"\n",
|
"\n",
|
||||||
"### Part 1: What is the sum of the invalid IDs?\n",
|
"### Part 1: What is the sum of the invalid IDs?\n",
|
||||||
"\n",
|
"\n",
|
||||||
"An invalid ID is one that consists of a digit sequence repeated twice. So 55, 6464 and 123123 are invalid. We're asked for the sum of the invalid IDs across all the ID ranges.\n",
|
"An invalid ID is defined as one that consists of a digit sequence repeated twice. So 55, 6464 and 123123 are invalid. We're asked for the sum of the invalid IDs across all the ID ranges.\n",
|
||||||
"\n",
|
"\n",
|
||||||
"We could look at every number in each range and check if the first half of the number (as a string) is the same as the second half. How many checks would that be?"
|
"We could look at every number in each range and check if the first half of the number (as a string) is the same as the second half. How many checks would that be?"
|
||||||
]
|
]
|
||||||
@ -311,15 +336,15 @@
|
|||||||
"id": "a9bf1aab-bd09-49a9-ab57-8b26b1f4a98d",
|
"id": "a9bf1aab-bd09-49a9-ab57-8b26b1f4a98d",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"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. By *first half* I mean by digits: the first half of a six-digit number is the first three digits.\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 don't mean divide by 2; I mean the first half of the digit string: the first half of \"123456\" is \"123\".\n",
|
||||||
"\n",
|
"\n",
|
||||||
"Suppose the range is 123456-223000. I enumerate from 123 to 223, and for each one form generate an invalid ID:\n",
|
"Suppose the range is 123456-223000. I enumerate from 123 to 223, and for each number generate an invalid ID:\n",
|
||||||
"[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."
|
"[123123, 124124, 125125, ... 223223]. I then yield the IDs that are within the range (in this case all but the first and the last are in the range 123456-223000). 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",
|
"cell_type": "code",
|
||||||
"execution_count": 10,
|
"execution_count": 54,
|
||||||
"id": "1345f93d-84c5-43f8-b6c2-9fc7b8f5ed90",
|
"id": "1345f93d-84c5-43f8-b6c2-9fc7b8f5ed90",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
@ -327,22 +352,16 @@
|
|||||||
"def invalids_in_range(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",
|
" \"\"\"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",
|
" An ID is invalid if it consists of a digit sequence repeated twice.\"\"\"\n",
|
||||||
" lo_str = str(lo)\n",
|
" first_half = str(lo)[:max(1, len(str(lo)) // 2)]\n",
|
||||||
" start = int(lo_str[:max(1, len(lo_str) // 2)])\n",
|
" for i in count_from(int(first_half)):\n",
|
||||||
" for half in count_from(start):\n",
|
" id = int(str(i) * 2)\n",
|
||||||
" id = int(str(half) * 2)\n",
|
|
||||||
" if lo <= id <= hi:\n",
|
" if lo <= id <= hi:\n",
|
||||||
" yield id\n",
|
" yield id\n",
|
||||||
" elif id > hi:\n",
|
" elif id > hi:\n",
|
||||||
" return\n",
|
" return\n",
|
||||||
"\n",
|
"\n",
|
||||||
"def invalids(id_ranges) -> List[int]:\n",
|
"def invalids(id_ranges) -> List[int]:\n",
|
||||||
" \"\"\"Invalid IDs, according to the list of invalid ID ranges.\"\"\"\n",
|
" \"\"\"Invalid IDs, according to any one of 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",
|
" return append(invalids_in_range(lo, hi)\n",
|
||||||
" for (lo, hi) in id_ranges)\n",
|
" for (lo, hi) in id_ranges)\n",
|
||||||
"\n",
|
"\n",
|
||||||
@ -351,17 +370,17 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 11,
|
"execution_count": 55,
|
||||||
"id": "8b0d12b3-f184-4149-8b49-f9ff78663d46",
|
"id": "8b0d12b3-f184-4149-8b49-f9ff78663d46",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [
|
"outputs": [
|
||||||
{
|
{
|
||||||
"data": {
|
"data": {
|
||||||
"text/plain": [
|
"text/plain": [
|
||||||
"Puzzle 2.1: .0027 seconds, answer 23560874270 ok"
|
"Puzzle 2.1: .0032 seconds, answer 23560874270 ok"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"execution_count": 11,
|
"execution_count": 55,
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"output_type": "execute_result"
|
"output_type": "execute_result"
|
||||||
}
|
}
|
||||||
@ -383,17 +402,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 12,
|
"execution_count": 66,
|
||||||
"id": "6b8d6dad-c5b6-4ed8-8dfa-5acbb54c8001",
|
"id": "4fbf52e7-a06a-4000-8627-80d159788da1",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"def invalids_in_range(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",
|
" \"\"\"Yield all the invalid IDs between lo and hi inclusive\n",
|
||||||
" that are formed from exactly `repeat` repeated digit sequences.\"\"\"\n",
|
" that are formed from exactly `repeat` repeated digit sequences.\"\"\"\n",
|
||||||
" lo_str = str(lo)\n",
|
" first_half = int(str(lo)[:len(str(lo)) // repeat] or 1)\n",
|
||||||
" start = int(lo_str[:len(lo_str) // repeat] or 1)\n",
|
" for i in count_from(int(first_half)):\n",
|
||||||
" for i in count_from(start):\n",
|
|
||||||
" id = int(str(i) * repeat)\n",
|
" id = int(str(i) * repeat)\n",
|
||||||
" if lo <= id <= hi:\n",
|
" if lo <= id <= hi:\n",
|
||||||
" yield id\n",
|
" yield id\n",
|
||||||
@ -401,14 +419,29 @@
|
|||||||
" return\n",
|
" return\n",
|
||||||
"\n",
|
"\n",
|
||||||
"def all_invalids(id_ranges) -> Set[int]:\n",
|
"def all_invalids(id_ranges) -> Set[int]:\n",
|
||||||
" \"\"\"All invalid IDs, according to the list of ranges, with any number of repeats.\"\"\"\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",
|
" return union(invalids_in_range(lo, hi, repeat)\n",
|
||||||
" for (lo, hi) in id_ranges\n",
|
" for (lo, hi) in id_ranges\n",
|
||||||
" for repeat in range(2, len(str(hi)) + 1))\n",
|
" for repeat in range(2, len(str(hi)) + 1))"
|
||||||
"\n",
|
]
|
||||||
"assert invalids([(11, 22)]) == [11, 22]\n",
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "1e7d71d2-8c08-4673-8d12-661edd6ab6f5",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"Since this is a bit tricky, I include some unit test assertions:"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 68,
|
||||||
|
"id": "5a29e5bc-3b07-4800-9a0f-4bd433e01c55",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
"assert list(invalids_in_range(2121212118, 2121212124, 5)) == [2121212121]\n",
|
"assert list(invalids_in_range(2121212118, 2121212124, 5)) == [2121212121]\n",
|
||||||
"assert all_invalids([(95, 115)]) == {99, 111}"
|
"assert all_invalids([(11, 22), (95, 115)]) == {11, 22, 99, 111}"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -416,22 +449,22 @@
|
|||||||
"id": "cb254f91-8c6d-445d-86be-97df9a93018c",
|
"id": "cb254f91-8c6d-445d-86be-97df9a93018c",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": [
|
||||||
"Now verify that the answer for Part 1 still works, and go ahead and compute the answer for Part 2:"
|
"Now I'll verify that the answer for Part 1 still works, and go ahead and compute the answer for Part 2:"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 13,
|
"execution_count": 69,
|
||||||
"id": "8a7c6c25-4b5f-4178-8559-166ba1a9f924",
|
"id": "8a7c6c25-4b5f-4178-8559-166ba1a9f924",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [
|
"outputs": [
|
||||||
{
|
{
|
||||||
"data": {
|
"data": {
|
||||||
"text/plain": [
|
"text/plain": [
|
||||||
"Puzzle 2.1: .0031 seconds, answer 23560874270 ok"
|
"Puzzle 2.1: .0027 seconds, answer 23560874270 ok"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"execution_count": 13,
|
"execution_count": 69,
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"output_type": "execute_result"
|
"output_type": "execute_result"
|
||||||
}
|
}
|
||||||
@ -443,17 +476,17 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 14,
|
"execution_count": 70,
|
||||||
"id": "32fefd65-df2a-4ea3-9acd-7525ebd32380",
|
"id": "32fefd65-df2a-4ea3-9acd-7525ebd32380",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [
|
"outputs": [
|
||||||
{
|
{
|
||||||
"data": {
|
"data": {
|
||||||
"text/plain": [
|
"text/plain": [
|
||||||
"Puzzle 2.2: .0040 seconds, answer 44143124633 ok"
|
"Puzzle 2.2: .0033 seconds, answer 44143124633 ok"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"execution_count": 14,
|
"execution_count": 70,
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"output_type": "execute_result"
|
"output_type": "execute_result"
|
||||||
}
|
}
|
||||||
@ -468,7 +501,7 @@
|
|||||||
"id": "872cf212-bfbf-4edd-b898-5f76ad122a85",
|
"id": "872cf212-bfbf-4edd-b898-5f76ad122a85",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"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 `all_invalids` to be a set to fix that."
|
"I initially had another **bug** here: I counted \"222222\" three times: once as 2 repeats of \"222\", once as 3 repeats of \"22\", and once as 6 repeats of \"2\". I changed the output of `all_invalids` to be a `set` to fix that."
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -517,34 +550,42 @@
|
|||||||
"source": [
|
"source": [
|
||||||
"### Part 1: What is the maximum possible total output joltage?\n",
|
"### Part 1: What is the maximum possible total output joltage?\n",
|
||||||
"\n",
|
"\n",
|
||||||
"We can turn on exactly two batteries in each bank, resulting in a two digit number which is the *joltage* of the bank. For example, given the bank \"8647\" we could choose to turn on the \"8\" and \"7\" to produce a joltage of 87. The function `joltage` chooses the biggest first digit, and then the biggest second digit that follows the first digit. Note that the possible choices for the first digit exclude the last digit, because if we chose that, then we couldn't choose a second digit to follow. Note I chose to do the string to int conversion in `total_joltage`; it would also be fine to have `joltage` return an int."
|
"We can turn on exactly two batteries in each bank, resulting in a two digit number which is the *joltage* of the bank. For example, given the bank \"8647\" we could choose to turn on the \"8\" and \"7\" to produce a joltage of 87; that's the maximum. The function `joltage` chooses the biggest first digit, and then the biggest second digit that follows the first digit. Note that the possible choices for the first digit exclude the last digit, because if we chose that, then there would be no choices left for the second digit. (I chose to do the string-to-int conversion in `total_joltage`; it would also be fine to have `joltage` return an int.)"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 16,
|
"execution_count": 76,
|
||||||
"id": "d1d18422-9054-4d89-85ed-37f3a21a2ef6",
|
"id": "0168ada2-b53e-4215-9e6a-2468759f095c",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"def joltage(bank: str) -> str:\n",
|
"def joltage(bank: str) -> str:\n",
|
||||||
" \"\"\"The maximum possible joltage by turning on 2 batteries in the bank.\n",
|
" \"\"\"The maximum possible joltage by turning on 2 batteries in the bank.\n",
|
||||||
" Pick the first digit, then the biggest digit from the rest of the bank.\"\"\"\n",
|
" Pick the biggest first digit, then the biggest digit that follows.\"\"\"\n",
|
||||||
" digit = max(bank[:-1]) # The first digit can't be the last character\n",
|
" first = max(bank[:-1]) # The first digit can't be the last character\n",
|
||||||
" rest = bank[bank.index(digit) + 1:]\n",
|
" second = max(bank[bank.index(first) + 1:]) # The second digit must come after the first\n",
|
||||||
" return digit + max(rest)\n",
|
" return first + second\n",
|
||||||
"\n",
|
"\n",
|
||||||
"def total_joltage(banks: List[str]) -> int:\n",
|
"def total_joltage(banks: List[str]) -> int:\n",
|
||||||
" \"\"\"The maximum possible joltage from all the banks.\"\"\"\n",
|
" \"\"\"The maximum possible joltage from all the banks.\"\"\"\n",
|
||||||
" return sum(int(joltage(bank)) for bank in banks)\n",
|
" return sum(int(joltage(bank)) for bank in banks)"
|
||||||
"\n",
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 74,
|
||||||
|
"id": "3f6390f5-78b7-4472-acf9-8855d2c2453e",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
"assert joltage(\"8647\") == \"87\"\n",
|
"assert joltage(\"8647\") == \"87\"\n",
|
||||||
"assert joltage(\"1119\") == \"19\""
|
"assert joltage(\"1119\") == \"19\""
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 17,
|
"execution_count": 75,
|
||||||
"id": "9cc62ae9-b313-4b82-b8fb-bab9c0ef5cb6",
|
"id": "9cc62ae9-b313-4b82-b8fb-bab9c0ef5cb6",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [
|
"outputs": [
|
||||||
@ -554,7 +595,7 @@
|
|||||||
"Puzzle 3.1: .0006 seconds, answer 17085 ok"
|
"Puzzle 3.1: .0006 seconds, answer 17085 ok"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"execution_count": 17,
|
"execution_count": 75,
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"output_type": "execute_result"
|
"output_type": "execute_result"
|
||||||
}
|
}
|
||||||
@ -571,15 +612,15 @@
|
|||||||
"source": [
|
"source": [
|
||||||
"### Part 2: What is the new maximum possible total output joltage?\n",
|
"### Part 2: What is the new maximum possible total output joltage?\n",
|
||||||
"\n",
|
"\n",
|
||||||
"In Part 2 the elf hits the \"joltage limit safety override\" button, and we can now turn on 12 batteries per bank, resulting in a 12-digit joltage. What is the new maximum possible total joltage?\n",
|
"In Part 2 the elf hits the \"*joltage limit safety override*\" button, and we can now turn on 12 batteries per bank, resulting in a 12-digit joltage. What is the new maximum possible total joltage?\n",
|
||||||
"\n",
|
"\n",
|
||||||
"I will make a change to the two functions, passing in the number of digits to be chosen, *n*. The function `joltage` stops when we get to 1 digit remaining, and recurses when there is more than one digit remaining, choosing the first digit from the bank up to the last *n* - 1 characters, then recursively finding the biggest joltage from the rest, but with *n* - 1 choices. "
|
"I will make a change to the two functions, passing in the number of digits to be chosen, *n* (with default 2 for backwards compatibility). The function `joltage` recurses when there is more than one digit remaining, choosing the first digit from the bank up to the last *n* - 1 characters, then recursively finding the biggest joltage from the rest. "
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 18,
|
"execution_count": 85,
|
||||||
"id": "b79011e6-b448-4b71-88ca-3301888a7f29",
|
"id": "b9507c12-421b-41af-b926-a4d3d83f622d",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
@ -589,14 +630,23 @@
|
|||||||
" if n == 1:\n",
|
" if n == 1:\n",
|
||||||
" return max(bank)\n",
|
" return max(bank)\n",
|
||||||
" else:\n",
|
" else:\n",
|
||||||
" digit = max(bank[:-(n - 1)]) # The first digit can't be the last n-1 characters\n",
|
" first = max(bank[:-(n - 1)]) # The first digit can't be the last n-1 characters\n",
|
||||||
" rest = bank[bank.index(digit) + 1:]\n",
|
" rest = bank[bank.index(first) + 1:]\n",
|
||||||
" return digit + joltage(rest, n - 1)\n",
|
" return first + joltage(rest, n - 1)\n",
|
||||||
"\n",
|
"\n",
|
||||||
"def total_joltage(banks: List[str], n=2) -> int:\n",
|
"def total_joltage(banks: List[str], n=2) -> int:\n",
|
||||||
" \"\"\"The maximum possible joltage from all the banks.\"\"\"\n",
|
" \"\"\"The maximum possible joltage from all the banks,\n",
|
||||||
" return sum(int(joltage(bank, n)) for bank in banks)\n",
|
" when `n` batteries are turned on per bank.\"\"\"\n",
|
||||||
"\n",
|
" return sum(int(joltage(bank, n)) for bank in banks)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 86,
|
||||||
|
"id": "4a58a657-8f32-4001-a4ff-f30dfbe7e5e7",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
"assert joltage(\"811111111111119\", 2) == '89'\n",
|
"assert joltage(\"811111111111119\", 2) == '89'\n",
|
||||||
"assert joltage(\"818181911112111\", 5) == '92111'\n",
|
"assert joltage(\"818181911112111\", 5) == '92111'\n",
|
||||||
"assert joltage(\"818181911112111\", 12) == '888911112111'"
|
"assert joltage(\"818181911112111\", 12) == '888911112111'"
|
||||||
@ -607,12 +657,12 @@
|
|||||||
"id": "28d2e2ce-98b5-4b3b-9f4d-26efcb7e1258",
|
"id": "28d2e2ce-98b5-4b3b-9f4d-26efcb7e1258",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": [
|
||||||
"I'll first make sure that the new version of `joltage` is backwards compatible, and then solve Part 2:"
|
"I'll make sure Part 1 still works, and then solve Part 2:"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 19,
|
"execution_count": 87,
|
||||||
"id": "fe3cff78-81c0-4d4a-bb4d-f0f841067e0d",
|
"id": "fe3cff78-81c0-4d4a-bb4d-f0f841067e0d",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [
|
"outputs": [
|
||||||
@ -622,7 +672,7 @@
|
|||||||
"Puzzle 3.1: .0006 seconds, answer 17085 ok"
|
"Puzzle 3.1: .0006 seconds, answer 17085 ok"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"execution_count": 19,
|
"execution_count": 87,
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"output_type": "execute_result"
|
"output_type": "execute_result"
|
||||||
}
|
}
|
||||||
@ -634,17 +684,17 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 20,
|
"execution_count": 88,
|
||||||
"id": "f971839e-81ea-49b4-a92f-a44884be645d",
|
"id": "f971839e-81ea-49b4-a92f-a44884be645d",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [
|
"outputs": [
|
||||||
{
|
{
|
||||||
"data": {
|
"data": {
|
||||||
"text/plain": [
|
"text/plain": [
|
||||||
"Puzzle 3.2: .0020 seconds, answer 169408143086082 ok"
|
"Puzzle 3.2: .0022 seconds, answer 169408143086082 ok"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"execution_count": 20,
|
"execution_count": 88,
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"output_type": "execute_result"
|
"output_type": "execute_result"
|
||||||
}
|
}
|
||||||
@ -661,7 +711,7 @@
|
|||||||
"source": [
|
"source": [
|
||||||
"# [Day 4](https://adventofcode.com/2025/day/4): Printing Department\n",
|
"# [Day 4](https://adventofcode.com/2025/day/4): Printing Department\n",
|
||||||
"\n",
|
"\n",
|
||||||
"The floor of the printing department is divided into squares. Some squares contain a roll of paper; other squares are empty. The day's input is a map of the floor, with `@` representing a roll of paper. I can handle that with the `Grid` class from my AdventUtils:"
|
"The floor of the printing department is divided into squares, some of which contain a roll of paper. The day's input is a map of the floor, with `@` representing a roll of paper. I can handle that with the `Grid` class from my AdventUtils:"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -698,28 +748,28 @@
|
|||||||
"id": "4dd00e21-228c-41f6-a28c-e2213e60d4ce",
|
"id": "4dd00e21-228c-41f6-a28c-e2213e60d4ce",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": [
|
||||||
"<img src=\"https://files.mastodon.social/media_attachments/files/115/663/375/845/834/867/original/4d3180e12fa726fe.jpg\" width=400 alt=\"Gary Grady cartoon\">\n",
|
"<img src=\"https://files.mastodon.social/media_attachments/files/115/663/375/845/834/867/original/4d3180e12fa726fe.jpg\" width=500 alt=\"Gary Grady cartoon\">\n",
|
||||||
"\n",
|
"\n",
|
||||||
"\n",
|
"\n",
|
||||||
"### Part 1: How many rolls of paper can be accessed by a forklift?\n",
|
"### Part 1: How many rolls of paper can be accessed by a forklift?\n",
|
||||||
"\n",
|
"\n",
|
||||||
"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."
|
"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 `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 rather than a count, in anticipation of Part 2."
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 22,
|
"execution_count": 89,
|
||||||
"id": "6fe5bd44-8a28-4d7a-bc8b-edc4af8f23c3",
|
"id": "6fe5bd44-8a28-4d7a-bc8b-edc4af8f23c3",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"def accessible_rolls(grid: Grid) -> List[Point]:\n",
|
"def accessible_rolls(grid: Grid) -> List[Point]:\n",
|
||||||
" \"\"\"A roll of paper is accessible if there are fewer than \n",
|
" \"\"\"The positions of all the accessible rolls of paper on the grid.\"\"\"\n",
|
||||||
" four rolls of paper in the eight adjacent positions.\"\"\"\n",
|
|
||||||
" return [p for p in grid if is_accessible(p, grid)]\n",
|
" return [p for p in grid if is_accessible(p, grid)]\n",
|
||||||
"\n",
|
"\n",
|
||||||
"def is_accessible(p: Point, grid: Grid) -> bool:\n",
|
"def is_accessible(p: Point, grid: Grid) -> bool:\n",
|
||||||
" \"\"\"Is point p on the grid an accessible paper roll?\"\"\"\n",
|
" \"\"\"A roll of paper is accessible if there are fewer than \n",
|
||||||
|
" four rolls of paper in the eight adjacent positions.\"\"\"\n",
|
||||||
" return grid[p] == '@' and grid.neighbor_contents(p).count('@') < 4"
|
" return grid[p] == '@' and grid.neighbor_contents(p).count('@') < 4"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -733,17 +783,17 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 23,
|
"execution_count": 90,
|
||||||
"id": "a5ef09cf-b204-41eb-80d8-de107d385dbb",
|
"id": "a5ef09cf-b204-41eb-80d8-de107d385dbb",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [
|
"outputs": [
|
||||||
{
|
{
|
||||||
"data": {
|
"data": {
|
||||||
"text/plain": [
|
"text/plain": [
|
||||||
"Puzzle 4.1: .0531 seconds, answer 1569 ok"
|
"Puzzle 4.1: .0530 seconds, answer 1569 ok"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"execution_count": 23,
|
"execution_count": 90,
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"output_type": "execute_result"
|
"output_type": "execute_result"
|
||||||
}
|
}
|
||||||
@ -760,14 +810,14 @@
|
|||||||
"source": [
|
"source": [
|
||||||
"### Part 2: How many rolls of paper can be removed?\n",
|
"### Part 2: How many rolls of paper can be removed?\n",
|
||||||
"\n",
|
"\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",
|
"If the elves can access a paper roll, they can remove it by forklift. That may in turn make other rolls accessible, and hence removable. How many rolls in total can be removed?\n",
|
||||||
"\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."
|
"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",
|
"cell_type": "code",
|
||||||
"execution_count": 24,
|
"execution_count": 91,
|
||||||
"id": "0ed53853-268c-4c2f-a929-cb3e6005a348",
|
"id": "0ed53853-268c-4c2f-a929-cb3e6005a348",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
@ -784,17 +834,17 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 25,
|
"execution_count": 92,
|
||||||
"id": "2fb17a51-05f7-42ec-8d6c-222121a026cf",
|
"id": "2fb17a51-05f7-42ec-8d6c-222121a026cf",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [
|
"outputs": [
|
||||||
{
|
{
|
||||||
"data": {
|
"data": {
|
||||||
"text/plain": [
|
"text/plain": [
|
||||||
"Puzzle 4.2: 1.2392 seconds, answer 9280 ok"
|
"Puzzle 4.2: 1.2244 seconds, answer 9280 ok"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"execution_count": 25,
|
"execution_count": 92,
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"output_type": "execute_result"
|
"output_type": "execute_result"
|
||||||
}
|
}
|
||||||
@ -809,12 +859,12 @@
|
|||||||
"id": "7143f73e-3b9b-49f3-bfa9-625899a56e37",
|
"id": "7143f73e-3b9b-49f3-bfa9-625899a56e37",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": [
|
||||||
"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."
|
"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. That's wasteful: 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. When the queue is empty, no more rolls can be removed."
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 26,
|
"execution_count": 93,
|
||||||
"id": "54f20b5e-6713-459c-8d40-e545ce6b8e42",
|
"id": "54f20b5e-6713-459c-8d40-e545ce6b8e42",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
@ -833,17 +883,17 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 27,
|
"execution_count": 94,
|
||||||
"id": "bcba970b-09aa-479b-9c6d-4f6a7ac49fed",
|
"id": "bcba970b-09aa-479b-9c6d-4f6a7ac49fed",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [
|
"outputs": [
|
||||||
{
|
{
|
||||||
"data": {
|
"data": {
|
||||||
"text/plain": [
|
"text/plain": [
|
||||||
"Puzzle 4.2: .1394 seconds, answer 9280 ok"
|
"Puzzle 4.2: .1397 seconds, answer 9280 ok"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"execution_count": 27,
|
"execution_count": 94,
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"output_type": "execute_result"
|
"output_type": "execute_result"
|
||||||
}
|
}
|
||||||
@ -860,7 +910,7 @@
|
|||||||
"source": [
|
"source": [
|
||||||
"# [Day 5](https://adventofcode.com/2025/day/5): Cafeteria\n",
|
"# [Day 5](https://adventofcode.com/2025/day/5): Cafeteria\n",
|
||||||
"\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:"
|
"Today we're in the cafeteria, and the elves need to figure out which of their ingredients are fresh or spoiled. The input file has two parts (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:"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -898,7 +948,7 @@
|
|||||||
" \"\"\"Parse either ranges like \"3-5\", or integers like \"8\".\"\"\"\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",
|
" return mapt(positive_ints, lines(text)) if '-' in text else ints(text)\n",
|
||||||
" \n",
|
" \n",
|
||||||
"fresh_ranges, available_ingredient_ids = parse(5, parse_ingredients, sections=paragraphs)"
|
"fresh_ranges, available_ingredient_ids = parse(day=5, parse_ingredients, sections=paragraphs)"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -908,7 +958,7 @@
|
|||||||
"source": [
|
"source": [
|
||||||
"### Part 1: How many of the available ingredient IDs are fresh?\n",
|
"### Part 1: How many of the available ingredient IDs are fresh?\n",
|
||||||
"\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:"
|
"Keep it simple: For each ingredient ID, check to see if it is contained in any of the ranges (then we'll see if this is fast enough):"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -951,6 +1001,8 @@
|
|||||||
"id": "01c2c322-e873-412f-9c47-59ffdf9adf79",
|
"id": "01c2c322-e873-412f-9c47-59ffdf9adf79",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": [
|
||||||
|
"Fast enough! Let's move on.\n",
|
||||||
|
"\n",
|
||||||
"### Part 2: How many ingredient IDs are fresh?\n",
|
"### Part 2: How many ingredient IDs are fresh?\n",
|
||||||
"\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: "
|
"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: "
|
||||||
@ -987,15 +1039,15 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 32,
|
"execution_count": 97,
|
||||||
"id": "f52e8ecd-325e-4ed4-8928-de9365b5b7d4",
|
"id": "f52e8ecd-325e-4ed4-8928-de9365b5b7d4",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"def count_fresh_ids(fresh_ranges) -> int:\n",
|
"def count_fresh_ids(fresh_ranges) -> int:\n",
|
||||||
" \"\"\"How many IDs are contained in the union of the ranges?\"\"\"\n",
|
" \"\"\"How many IDs are contained in the union of the ranges?\"\"\"\n",
|
||||||
" fresh_count = 0\n",
|
" fresh_count = 0 # The number of fresh IDs found so far\n",
|
||||||
" unexplored = 0\n",
|
" unexplored = 0 # The highest ID number that we haven't considered yet\n",
|
||||||
" for (lo, hi) in sorted(fresh_ranges):\n",
|
" for (lo, hi) in sorted(fresh_ranges):\n",
|
||||||
" start = max(unexplored, lo)\n",
|
" start = max(unexplored, lo)\n",
|
||||||
" fresh_count += len(range(start, hi + 1))\n",
|
" fresh_count += len(range(start, hi + 1))\n",
|
||||||
@ -1005,7 +1057,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 33,
|
"execution_count": 98,
|
||||||
"id": "fab6f1ed-543f-4f42-b1d5-2551742e3a4f",
|
"id": "fab6f1ed-543f-4f42-b1d5-2551742e3a4f",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [
|
"outputs": [
|
||||||
@ -1015,7 +1067,7 @@
|
|||||||
"Puzzle 5.2: .0001 seconds, answer 369761800782619 ok"
|
"Puzzle 5.2: .0001 seconds, answer 369761800782619 ok"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"execution_count": 33,
|
"execution_count": 98,
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"output_type": "execute_result"
|
"output_type": "execute_result"
|
||||||
}
|
}
|
||||||
@ -1030,14 +1082,16 @@
|
|||||||
"id": "6b83e353-3b77-4645-85b9-2f6ee6ff9e1d",
|
"id": "6b83e353-3b77-4645-85b9-2f6ee6ff9e1d",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": [
|
||||||
"# [Day 6](https://adventofcode.com/2025/day/5): Trash Conmpactor\n",
|
"# [Day 6](https://adventofcode.com/2025/day/5): Trash Compactor\n",
|
||||||
"\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."
|
"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 children 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.\n",
|
||||||
|
"\n",
|
||||||
|
"<img src=\"https://files.mastodon.social/media_attachments/files/115/674/362/938/836/432/original/3b83547aa749a914.jpg\" width=500 alt=\"Gary Grady cartoon\">"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 34,
|
"execution_count": 100,
|
||||||
"id": "bca852a9-e4c5-4706-abfc-afc6d4a4eeb5",
|
"id": "bca852a9-e4c5-4706-abfc-afc6d4a4eeb5",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [
|
"outputs": [
|
||||||
@ -1067,19 +1121,19 @@
|
|||||||
"source": [
|
"source": [
|
||||||
"### Part 1: What is the grand total of the answers to the individual problems?\n",
|
"### Part 1: What is the grand total of the answers to the individual problems?\n",
|
||||||
"\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.)"
|
"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:"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 35,
|
"execution_count": 102,
|
||||||
"id": "92f320f4-c7a3-4dfd-ae61-6b1b180bdc93",
|
"id": "92f320f4-c7a3-4dfd-ae61-6b1b180bdc93",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"def grand_total(worksheet):\n",
|
"def grand_total(worksheet):\n",
|
||||||
" \"\"\"The sum of the individual sum/product problems, where each column is a problem.\"\"\"\n",
|
" \"\"\"The sum of the individual sum/product problems, where each column is a problem.\"\"\"\n",
|
||||||
" columns = T(map(str.split, worksheet))\n",
|
" columns = T(map(str.split, worksheet)) # Columns are the transpose of the rows; `T` is transpose\n",
|
||||||
" operations = {'*': prod, '+': sum}\n",
|
" operations = {'*': prod, '+': sum}\n",
|
||||||
" return sum(operations[op](mapt(int, numbers)) \n",
|
" return sum(operations[op](mapt(int, numbers)) \n",
|
||||||
" for (*numbers, op) in columns)"
|
" for (*numbers, op) in columns)"
|
||||||
@ -1087,17 +1141,17 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 36,
|
"execution_count": 103,
|
||||||
"id": "92a47f3a-127f-41e8-bee1-6276885bd36b",
|
"id": "92a47f3a-127f-41e8-bee1-6276885bd36b",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [
|
"outputs": [
|
||||||
{
|
{
|
||||||
"data": {
|
"data": {
|
||||||
"text/plain": [
|
"text/plain": [
|
||||||
"Puzzle 6.1: .0015 seconds, answer 5877594983578 ok"
|
"Puzzle 6.1: .0013 seconds, answer 5877594983578 ok"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"execution_count": 36,
|
"execution_count": 103,
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"output_type": "execute_result"
|
"output_type": "execute_result"
|
||||||
}
|
}
|
||||||
@ -1112,7 +1166,7 @@
|
|||||||
"id": "226772ae-0d6e-43b8-9159-171ec1b36a5d",
|
"id": "226772ae-0d6e-43b8-9159-171ec1b36a5d",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": [
|
||||||
"### Part 2: What is the grand total of the answers to the individual problems now?\n",
|
"### Part 2: What is the grand total of the answers to the individual problems with the new rules?\n",
|
||||||
"\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",
|
"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",
|
"\n",
|
||||||
@ -1122,14 +1176,14 @@
|
|||||||
" 9472 154 36 76 5 89 37 5 28 6 95 49 82 66 7 44 \n",
|
" 9472 154 36 76 5 89 37 5 28 6 95 49 82 66 7 44 \n",
|
||||||
" + + + * * * + * * + * + * * + + \n",
|
" + + + * * * + * * + * + * * + + \n",
|
||||||
"\n",
|
"\n",
|
||||||
"The problem in the leftmost column is not \"`4 + 6 + 827 + 9472`\", rather it is \"`9 + 84 + 27 + 4672`\".\n",
|
"The problem in the leftmost column is not \"`4 + 6 + 827 + 9472`\"; rather it is \"`9 + 84 + 27 + 4672`\".\n",
|
||||||
"\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."
|
"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 separator between problems versus a blank space that marks the lack of a digit within a problem. In `grand_total2` I first break each line into columns (which requires finding the column indexes that are all blank spaces), then I do the math."
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 37,
|
"execution_count": 104,
|
||||||
"id": "f332aec9-dbfa-4bf9-b338-9a33c5f3bd89",
|
"id": "f332aec9-dbfa-4bf9-b338-9a33c5f3bd89",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
@ -1162,17 +1216,17 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 38,
|
"execution_count": 105,
|
||||||
"id": "39a8fa78-946f-45aa-8d36-76883f4aeaff",
|
"id": "39a8fa78-946f-45aa-8d36-76883f4aeaff",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [
|
"outputs": [
|
||||||
{
|
{
|
||||||
"data": {
|
"data": {
|
||||||
"text/plain": [
|
"text/plain": [
|
||||||
"Puzzle 6.2: .0082 seconds, answer 11159825706149 ok"
|
"Puzzle 6.2: .0048 seconds, answer 11159825706149 ok"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"execution_count": 38,
|
"execution_count": 105,
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"output_type": "execute_result"
|
"output_type": "execute_result"
|
||||||
}
|
}
|
||||||
@ -1187,12 +1241,12 @@
|
|||||||
"id": "ca461ef3-50c3-4189-b724-5f2b3898f27d",
|
"id": "ca461ef3-50c3-4189-b724-5f2b3898f27d",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"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:"
|
"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 my errors I worked on the smaller example worksheet, doing things like this:"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 39,
|
"execution_count": 106,
|
||||||
"id": "a13dbe89-3178-433f-9a50-28ed9a2f8358",
|
"id": "a13dbe89-3178-433f-9a50-28ed9a2f8358",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
@ -1206,7 +1260,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 40,
|
"execution_count": 107,
|
||||||
"id": "3aa0b661-367a-4316-905a-60f1d9e6df01",
|
"id": "3aa0b661-367a-4316-905a-60f1d9e6df01",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [
|
"outputs": [
|
||||||
@ -1219,7 +1273,7 @@
|
|||||||
" ['* ', '+ ', '* ', '+ ']]"
|
" ['* ', '+ ', '* ', '+ ']]"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"execution_count": 40,
|
"execution_count": 107,
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"output_type": "execute_result"
|
"output_type": "execute_result"
|
||||||
}
|
}
|
||||||
@ -1230,48 +1284,34 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 41,
|
"execution_count": 110,
|
||||||
"id": "13392d9c-fef8-4946-b945-48ad6bc7eca9",
|
"id": "13392d9c-fef8-4946-b945-48ad6bc7eca9",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [
|
"outputs": [
|
||||||
{
|
{
|
||||||
"data": {
|
"data": {
|
||||||
"text/plain": [
|
"text/plain": [
|
||||||
"[('123', ' 45', ' 6', '* '),\n",
|
"('123', ' 45', ' 6', '* ')"
|
||||||
" ('328', '64 ', '98 ', '+ '),\n",
|
|
||||||
" (' 51', '387', '215', '* '),\n",
|
|
||||||
" ('64 ', '23 ', '314', '+ ')]"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"execution_count": 41,
|
"execution_count": 110,
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"output_type": "execute_result"
|
"output_type": "execute_result"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"source": [
|
"source": [
|
||||||
"problems = T(break_into_columns(worksheet2))\n",
|
"problems = T(break_into_columns(worksheet2))\n",
|
||||||
"problems"
|
"problems[0]"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 42,
|
"execution_count": 115,
|
||||||
"id": "9a25ff50-f465-4de3-95ca-71b7a3d9569b",
|
"id": "9a25ff50-f465-4de3-95ca-71b7a3d9569b",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [
|
"outputs": [],
|
||||||
{
|
|
||||||
"data": {
|
|
||||||
"text/plain": [
|
|
||||||
"(8544, 625, 3253600, 1058)"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"execution_count": 42,
|
|
||||||
"metadata": {},
|
|
||||||
"output_type": "execute_result"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"source": [
|
"source": [
|
||||||
"mapt(cephalopodish_math, problems)"
|
"assert cephalopodish_math(problems[0]) == 1 * 24 * 356"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user