Add files via upload

This commit is contained in:
Peter Norvig authored and GitHub committed 2025-12-15 21:00:51 -08:00
1 parent a6b89dfbc0
commit 5d6392a5b2
1 file changed
+202 -192
+202 -192
View File
@@ -13,7 +13,7 @@
"\n",
"This year I will be doing something different: I will solve each problem myself here, and then [**in another notebook**](Advent-2025-AI.ipynb) I will ask an **AI Large Language Model** to solve the same problem. Check out the differences!\n",
"\n",
"# Day 0\n",
"# Day 0: Utilities\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, which also prints the first few lines of the input and the resulting parsed data objects), then solve Part 1 and Part 2 (recording the correct answer with my `answer` function)."
]
@@ -21,7 +21,7 @@
{
"cell_type": "code",
"execution_count": 1,
"id": "d5f1da68-5da6-434d-a068-1d93497a86b3",
"id": "a6fc45f3-ccc3-4905-881c-3cf7f6e897d7",
"metadata": {},
"outputs": [],
"source": [
@@ -44,9 +44,9 @@
"id": "37bc12b8-d0dc-4873-984c-6f09ae647229",
"metadata": {},
"source": [
"# [Day 1](https://adventofcode.com/2025/day/1): Secret Entrance\n",
"# Day 1: Secret Entrance (Rotating numbers mod 100)\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 24 challenges along the way; the first one involves unlocking a safe. The safe has a dial with 100 numbers, and an arrow that currently points at 50. 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. \n",
"On [**Day 1**](https://adventofcode.com/2025/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; the first one involves unlocking a safe. The safe has a dial with 100 numbers, and an arrow that currently points at 50. 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. \n",
"\n",
"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. "
]
@@ -89,7 +89,7 @@
}
],
"source": [
"def parse_rotation(line: str) -> str: return int(line.replace('L', '-').replace('R', '+'))\n",
"def parse_rotation(line: str) -> int: return int(line.replace('L', '-').replace('R', '+'))\n",
"\n",
"rotations = parse(day=1, parser=parse_rotation) "
]
@@ -158,7 +158,7 @@
{
"data": {
"text/plain": [
"Puzzle 1.1: .0006 seconds, answer 1182 correct"
"Puzzle 1.1: .0004 seconds, answer 1182 correct"
]
},
"execution_count": 5,
@@ -192,7 +192,7 @@
{
"data": {
"text/plain": [
"Puzzle 1.2: .1639 seconds, answer 6907 correct"
"Puzzle 1.2: .0906 seconds, answer 6907 correct"
]
},
"execution_count": 6,
@@ -242,7 +242,7 @@
{
"data": {
"text/plain": [
"Puzzle 1.2: .0013 seconds, answer 6907 correct"
"Puzzle 1.2: .0007 seconds, answer 6907 correct"
]
},
"execution_count": 8,
@@ -268,9 +268,9 @@
"id": "dd6908ae-1906-4687-a50d-a50293a1dad5",
"metadata": {},
"source": [
"# [Day 2](https://adventofcode.com/2025/day/2): Gift Shop\n",
"# Day 2: Gift Shop (Repeated digits as invalid IDs)\n",
"\n",
"We're in the North Pole gift shop, where the elves are doing inventory control. Today's input is 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:"
"On [**Day 2**](https://adventofcode.com/2025/day/2) we're in the North Pole gift shop, where the elves are doing inventory control. Today's input is 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:"
]
},
{
@@ -370,7 +370,7 @@
" elif id > hi:\n",
" return\n",
"\n",
"def invalids(id_ranges) -> Set[int]:\n",
"def invalids(id_ranges) -> set[int]:\n",
" \"\"\"Invalid IDs, according to any one of the list of invalid ID ranges.\"\"\"\n",
" return union(invalids_in_range(lo, hi)\n",
" for (lo, hi) in id_ranges)\n",
@@ -387,7 +387,7 @@
{
"data": {
"text/plain": [
"Puzzle 2.1: .0030 seconds, answer 23560874270 correct"
"Puzzle 2.1: .0023 seconds, answer 23560874270 correct"
]
},
"execution_count": 12,
@@ -428,7 +428,7 @@
" elif id > hi:\n",
" return\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",
" return union(invalids_in_range(lo, hi, repeat)\n",
" for (lo, hi) in id_ranges\n",
@@ -471,7 +471,7 @@
{
"data": {
"text/plain": [
"Puzzle 2.1: .0039 seconds, answer 23560874270 correct"
"Puzzle 2.1: .0023 seconds, answer 23560874270 correct"
]
},
"execution_count": 15,
@@ -493,7 +493,7 @@
{
"data": {
"text/plain": [
"Puzzle 2.2: .0039 seconds, answer 44143124633 correct"
"Puzzle 2.2: .0030 seconds, answer 44143124633 correct"
]
},
"execution_count": 16,
@@ -519,9 +519,9 @@
"id": "16e222aa-d424-44d8-bcb3-7aa11c31b540",
"metadata": {},
"source": [
"# [Day 3](https://adventofcode.com/2025/day/3): Lobby\n",
"# Day 3: Lobby (Maximum number from digit string)\n",
"\n",
"Entering the lobby, we find that the elevators are offline. We might be able to fix the problem by turning on some batteries. There are multiple battery banks, each bank consisting of a sequence of digits representing the *joltage* level of a battery. "
"Entering the lobby on [**Day 3**](https://adventofcode.com/2025/day/3), we find that the elevators are offline. We might be able to fix the problem by turning on some batteries. There are multiple battery banks, each bank consisting of a sequence of digits representing the *joltage* level of a battery. "
]
},
{
@@ -577,7 +577,7 @@
" second = max(bank[bank.index(first) + 1:]) # The second digit must come after the first\n",
" return first + second\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",
" return sum(int(joltage(bank)) for bank in banks)"
]
@@ -602,7 +602,7 @@
{
"data": {
"text/plain": [
"Puzzle 3.1: .0007 seconds, answer 17085 correct"
"Puzzle 3.1: .0006 seconds, answer 17085 correct"
]
},
"execution_count": 20,
@@ -644,7 +644,7 @@
" rest = bank[bank.index(first) + 1:]\n",
" return first + joltage(rest, n - 1)\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",
" when `n` batteries are turned on per bank.\"\"\"\n",
" return sum(int(joltage(bank, n)) for bank in banks)"
@@ -679,7 +679,7 @@
{
"data": {
"text/plain": [
"Puzzle 3.1: .0007 seconds, answer 17085 correct"
"Puzzle 3.1: .0006 seconds, answer 17085 correct"
]
},
"execution_count": 23,
@@ -701,7 +701,7 @@
{
"data": {
"text/plain": [
"Puzzle 3.2: .0020 seconds, answer 169408143086082 correct"
"Puzzle 3.2: .0015 seconds, answer 169408143086082 correct"
]
},
"execution_count": 24,
@@ -719,9 +719,9 @@
"id": "db13a440-9ad0-4344-b555-aec969869688",
"metadata": {},
"source": [
"# [Day 4](https://adventofcode.com/2025/day/4): Printing Department\n",
"# Day 4: Printing Department (Accessible squares in a grid)\n",
"\n",
"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](AdventUtils.ipynb):"
"On [**Day 4**](https://adventofcode.com/2025/day/4) 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](AdventUtils.ipynb):"
]
},
{
@@ -773,7 +773,7 @@
"metadata": {},
"outputs": [],
"source": [
"def accessible_rolls(grid: Grid) -> List[Point]:\n",
"def accessible_rolls(grid: Grid) -> list[Point]:\n",
" \"\"\"The positions of all the accessible rolls of paper on the grid.\"\"\"\n",
" return [p for p in grid if is_accessible(p, grid)]\n",
"\n",
@@ -800,7 +800,7 @@
{
"data": {
"text/plain": [
"Puzzle 4.1: .0610 seconds, answer 1569 correct"
"Puzzle 4.1: .0356 seconds, answer 1569 correct"
]
},
"execution_count": 27,
@@ -835,11 +835,9 @@
"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",
" points = accessible_rolls(grid2)\n",
" while points:\n",
" while points := accessible_rolls(grid2):\n",
" yield from points\n",
" grid2.update({p: '.' for p in points})\n",
" points = accessible_rolls(grid2)"
" grid2.update({p: '.' for p in points})"
]
},
{
@@ -851,7 +849,7 @@
{
"data": {
"text/plain": [
"Puzzle 4.2: 1.3428 seconds, answer 9280 correct"
"Puzzle 4.2: .8029 seconds, answer 9280 correct"
]
},
"execution_count": 29,
@@ -900,7 +898,7 @@
{
"data": {
"text/plain": [
"Puzzle 4.2: .1437 seconds, answer 9280 correct"
"Puzzle 4.2: .0920 seconds, answer 9280 correct"
]
},
"execution_count": 31,
@@ -931,7 +929,7 @@
"outputs": [
{
"data": {
"image/png": "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 truncated
"image/png": "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 truncated
"text/plain": [
"<Figure size 1000x1000 with 1 Axes>"
]
@@ -941,7 +939,7 @@
},
{
"data": {
"image/png": "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 truncated
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAxoAAAMWCAYAAAB2gvApAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlHJYcgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAY7dJREFUeJzt3TuW5EqXHlj8zZ5BCamTWsoUyAHkNFrjqFrradQASIFyaaReQg2BK1q4KxnMuICHmR879txbxHIPw8NgcATw2fnHx8fHxwUAANDQ/zV6BQAAgP240QAAAJpzowEAADTnRgMAAGjOjQYAANCcGw0AAKA5NxoAAEBzbjQAAIDm3GgAAADNudEAAACac6MBAAA050YDAABozo0GAADQnBsNAACgOTcaAABAc240AACA5txoAAAAzbnRAAAAmnOjAQAANOdGAwAAaM6NBgAA0JwbDQAAoDk3GgAAQHNuNAAAgObcaAAAAM250QAAAJpzowEAADTnRgMAAGjOjQYAANCcGw0AAKA5NxoAAEBzbjQAAIDm3GgAAADNudEAAACac6MBAAA050YDAABozo0GAADQnBsNAACgOTcaAABAc240AACA5txoAAAAzbnRAAAAmvu/R68AwGn++V/+9fqv/+Pfrv/8H/7p+vXzx+MyAFjZPz4+Pj5GrwTAKf75X/71+i//33+//t0//nH9r4+P6//9f/7jdV3X35a52QBgdV6dAujov/6Pf/vfNxT/7h//uP7b//y322UAsDo3GgAd/ef/8E//+4bif318XP/p3//T7TIAWJ1XpwA6++d/+dfrv/3Pf7v+07//M6PxdRkArEwYnCUJ07KKp3559y+er8tq+rlzoo2a/QvAa55osBxhWlZR2lcjy379/OGcaORuPz7tX/sS4HsyGixHmJZVlPbVyLKadnitZv8C8D03GixHmJZVlPbVyLKadnitZv8C8D2vTrEkYVpWUdpXI8tqP8uzmv0LwGvC4EyvdZgWeivpq79+/vhbH71b9vT3SttxTnyqGVvulgvgA7zmiQZTax2mdeGnp9Yh7Zqw8l07kbZ3U3Ns7F+A93iiwdTuQpgfH9fby1z06am0/5b2y7u/9+vnj9B5cuo5UXNs7F+A9wiDM7WM4Cz00rpfRsPgzolPGWF7+xfgT16dYnoZwVnopXW/jIbBnROfMsL29i/AJ69OsYSSkGvNMuipZUj7KSBeExx3Tnyq2T/vjjkC4sCpPNFgasLgrGxkiFi18Nd6hcGf/ibACTzRYGrC4KxsZIi4dRB9N73C4PY5cDJhcKYmDM7KRvZV58RrvcLg9jlwMq9OMT1hcFY2sq86J17rFQa3z4FTeXWK6UVCrk/fhV5Gh7RnPyd6Vde++5s1xyZ6HP1LD9rpNW4Q54kGyxFyZWW9+u9TFfGZzLYvavaZauEwht8Aa5HRYDl3wcy7ZTCjXv13hXNitn1R03bpuq9wHGAlzrO1uNFgOQKXrGxkGHw2s+2LmraFwWEM59lavDrFkgQuWdnIMPhsZtsXNW0Lg8MYzrN1CIOzpLtg5mwhV85TE1DsFdL+2s7IwGTpvnhaVrrupe3UrE80TD7TcYBVvDpPnGdr8EQDoIHZwsGzBSaj+ycS6C5tZ2TbfgTBn57OO+fZWjzRAGhgZBXwyPr0uvBG98/d9+/WPaOKd4+2/QCCPz2dd86ztQiDAzQwWzh4tsBkdP9EAt0rtA38qeZ8dJ7Ny6tTAI3MFg6eLTAZ3T+RQPcKbQN/qjkfnWdz8uoUwAu1FWhLg829/sXzbmAyK0QZ2T934evWIe1o29F2gD89nSfvnmcC4n15ogHwoCZQPVtAMSMUPWp9aqpz9wppz7Z/YTcZYfC7Zc69XJ5oADyoCVTPFlDMCEWPWp+ntkeGtGfbv7CbjDC4c68/YXCAB9HA8Aph8JHVuaNtjwxpz7Z/YTcZY61zrz+vTgG8EA0MrxAGH1mdO9r2yJD2bPsXdpMx1jr3+jru1anS0KOwEJwnGu6drVJ0Rig6Ooa2brt1SPtpn5W2Hd3nrj3wp6d/h787ltSMGcQd9URjtrAmMI/ZAsOzBcQzQpjRtkcGv0uPT3RCAdceTtWrMrjzLtdRTzRmC2sC85gtMDxbQDwjhBlte2Twu/T4RCcUcO3hVL0qgzvvch0VBhcWAp7MFhiecRtbhzBXqc4dOT4Z2wgn6DXxhvMu11GvTl2XsBDwbLbA8Izb2DqEuUp17sjxydhGOEGviTecd3m2fnWqdRgxIywkiA5zeXd8uK41xpxoWLnms+8ue1rH1sHvu2VPf6+07ej6vFoOp3kaC66r/TleM6ZTbtsnGj1Ci9FOVxMSBHKtEJQeGRCfcRtHrk9GdWKhVPher/Pe+djGtk80eoQWox2uJiQI5FohKD0yID7jNo5cn4zqxEKp8L1e573zsY1tw+C9Aoo91hHIt0JQeuR2z7iNI9dnZOAdTjbbBBS8tu2rU9fVL6DYYx2BfCsEpUdu94zbOHJ9Rgbe4WSzTUDBs21fnfrt7jaqZFlpYLImKBQJCQokQb4VgtIZ7rY7I4R5t+xVO6XeHb9rjndpZfCn75cua7E/YHc1Y1Z0rC35PeY32rNtn2j0qBxZ+t3o+tS0A7SzclA6w2zjWKRaeM3nRlYlB94zcoIP5/OnbZ9o9KgcWfrd6PrUtAO0s3JQeuT+6LU9kWrhNZ8bWZUceM/ICT6cz5+EwRtXm81YH4EkGGPloHSG2bandeXfGauSA+8ZOcEHn7Z9deq6+lSOHB0cBHKtHJTOMNv2tK78O2NVcuA9Iyf44C/bvjp1XbFgUDT4Fwl+t2iHvwhtEVUTOmwdRpwxGFy6P2oD5iVKx9Wa8fermhB8xjgPtBOdGOLus6XL/Nb4y9ZPNL7qERDPCBBFA4qnUnmd0YQJ2xkZRJ8tBA+8p+Z3k/O+ja2faHzVIyCeESCKBhRPpfI6owkTtjMyiD5bCB54T83vJud9G9uGwe/0CIiPbodPQluMJkzYzk4VyIExTOLT31GvTl1Xn4D46Hb4JLTFaMKE7exUgRwYwyQ+fR316tR1tQ+IP3239G/erU+Ldk6k8joz6hWU3smr87HkfC5dVnvev9uOY8t1tZ+c5Om7rmev1fzuuq62Y86Jjnui8VVG2CcSQo5WIj51UBHagj1kTLJRuiwjFGos4braT05ikph2eo05px6H455ofJUR9omEkKOViE/tyEJbsIeMSTai571QP1GtJycxSUw7vcacU4/DUWHwOxlhn14VyIWNPgltwR5mrLxuLCGqdX/xu6CdXmPOqY5/deq6csI+vSqQCxt9EtqCPcxYed1YQlTr/uJ3QTu9xpwTHf/q1HXlVbq9u4X7uiwSYP6u/Z1FK7eXLgPGeDofI+dzZEyOtsOn0lB062Wjt/G63u+D0UliRu+PFbQec079ffaVJxoJRoYWT1AadhPghPWMDIPfLYu2bSz508jjeMI2tg6dnyBjzLF/P3mikWBkaPEEpWE3AU5Yz8gw+NNYYCxpZ+RxPGEbW4fOT5Ax5ti/n44Pg2cYGVo8Qetgvf0L8xgZBs9omz+NPI4nbOOM+2N2zvtcXp1KMjK0eILWwXr7F+YxMgye0TZ/GnkcT9jGGffH7Jz3ebw6laR1wPzkUFFpCC4arI/ccs8YRoSVPZ2PJedu1sQQkbZP1LPacumy1mHyp89l9MHWfZVP0Yl9npbhiQaTWyE4KHwH7USDmRkTQzjH650Q6q+pwt1rkhh9tR1h8DY80WBqKwQHhe+gnWgwM2NiCOd4vRNC/TVVuHtNEqOvtiMM3oYwOFNbITgoHAbtZIRcBWz7OyHU36u6vL46hjB4G16dYnorBAeFw6CdjJCrgG1/J4T6e1WX11fHEAaPc6MBAAA0J6PB9FrPCFIzG1RkhpKats1aBd/rcT4679qJjne9js/IMT1jG/Xh/uzzZ55oMLUeM82YJQTmt8IMdHzqNetU9PhkrM/IbayZCYs27PPXPNFgaj1mmjFLCMxvhRno+NRr1qno8clYn5HbWDMTFm3Y56+ZdYqp9ZhpJtq2WUIg3woz0PGp16xTGes5cnarjFkQyWWfv+bVKabXY6aZ6PfNEgL5VpiBjk+9Zp3KWM+Rs1tlzIJILvv8mVenmN6vnz9uT9yvt8hPYay7W+nS75a2Xbrs6e9F13MnAro8uTt/SpddV/wcpU50vKs5thGt+1DtZ2v+5v/p1bjoX8h5Sn9ruG79xRMNltMrpN267Z5/c1WC8WTQr+ax6rGoWe8eYfCasP1s+3Jlrtf1PNFgOb1C2q3b7vk3VyUYTwb9ah6rHoua9e4RBq8J28+2L1fmel1PGJzljAyArvI3V3XqdpNLv5rHqsditjB4Tdu043pdz6tTLGlkAHSVv7mqU7ebXPrVPFY9FrOFwWvaph3X6zpenWJJpUG9aKAvEjB/Wvbk3XVfOXCWEeA/lRD9a72CxXxv1WNRs96tJxKpGStfLaeNjN8Au/JEgy2UBuBmC/RlrfsKZqsQvLLd+gbsptckJsLguYTB63miwRZKA3CzBfqy1n0Fs1UIXtlufQN202sSE2HwXMLg9YTB2UJGtfCRAfPouq9gtgrBK7OPYG69JjFx3udyjarn1Sm2kVEtfGTAPLruK5itQvDK7COYW69JTJz3uVyj6nh1iiXVBuO+qgltlXy2NCRYU8m1dVD66e+NDgxHQnWl+2wntdWAT9xHMKt3ryel3331fdpxjSrniQbLiQRfM8LgpYNFTSXXu3ZaLxsdHBxZ4X1VGX1ot30EM+p13SKXSUzqeaLBciLB14wweOlAUVPJNRIIrAmmjQwOjqzwvqqMPrTbPoIZ9bpukcskJvWEwVlO62qq0TB4ZL1r2tktODiywvuqMvoQkK/XdYtcrlH1vDrFklpXU42GwSPrXdPObsHBkRXeV5XRh4B8va5b5HKNquPVKZZ1d4tcettc892Sz9aEryPt1FSmjWxjRmDLine truncated
"text/plain": [
"<Figure size 1000x1000 with 1 Axes>"
]
@@ -963,9 +961,9 @@
"id": "1f9a1e40-192a-4386-8cfb-bc5f69c88a9b",
"metadata": {},
"source": [
"# [Day 5](https://adventofcode.com/2025/day/5): Cafeteria\n",
"# Day 5: Cafeteria (Counting IDs within ranges)\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 has two parts (paragraphs), the first consisting of ranges of fresh ingredient IDs, like \"6-7\" and the second consisting of the available ingredient IDs, like \"8\". I can parse the data like this:"
"On [**Day 5**](https://adventofcode.com/2025/day/5) 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 \"6-7\" and the second consisting of the available ingredient IDs, like \"8\". I can parse the data like this:"
]
},
{
@@ -1040,7 +1038,7 @@
{
"data": {
"text/plain": [
"Puzzle 5.1: .0118 seconds, answer 635 correct"
"Puzzle 5.1: .0069 seconds, answer 635 correct"
]
},
"execution_count": 35,
@@ -1121,7 +1119,7 @@
{
"data": {
"text/plain": [
"Puzzle 5.2: .0002 seconds, answer 369761800782619 correct"
"Puzzle 5.2: .0001 seconds, answer 369761800782619 correct"
]
},
"execution_count": 38,
@@ -1139,9 +1137,11 @@
"id": "6b83e353-3b77-4645-85b9-2f6ee6ff9e1d",
"metadata": {},
"source": [
"# [Day 6](https://adventofcode.com/2025/day/5): Trash Compactor\n",
"# Day 6: Trash Compactor (Vertical arithmetic)\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 cephalopod 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",
"Trash Compactor? [I've got a bad feeling about this!](https://youtu.be/CZgeYSqUeTA?si=5UPS_HiCOmTKrEWX&t=32) \n",
"\n",
"On [**Day 6**](https://adventofcode.com/2025/day/6) we've fallen into a garbage smasher and have been asked to help some of the resident cephalopod 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\">"
]
@@ -1205,7 +1205,7 @@
{
"data": {
"text/plain": [
"Puzzle 6.1: .0021 seconds, answer 5877594983578 correct"
"Puzzle 6.1: .0016 seconds, answer 5877594983578 correct"
]
},
"execution_count": 41,
@@ -1250,12 +1250,12 @@
"metadata": {},
"outputs": [],
"source": [
"def grand_total2(worksheet: List[str]):\n",
"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",
"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",
" *number_lines, operator_line = worksheet\n",
@@ -1263,7 +1263,7 @@
" column_starts = [i for i, ch in enumerate(operator_line) if ch != ' ']\n",
" return [break_line_into_columns(line, column_starts) for line in worksheet]\n",
"\n",
"def break_line_into_columns(line: str, column_starts: List[int]) -> List[str]:\n",
"def break_line_into_columns(line: str, column_starts: list[int]) -> list[str]:\n",
" \"\"\"Break one line into columns, as specified by the list of `blanks` positions.\"\"\"\n",
" return [line[column_starts[i]:column_starts[i + 1]-1] \n",
" for i in range(len(column_starts) - 1)]\n",
@@ -1274,7 +1274,7 @@
" sum_or_prod = (sum if op.strip() == '+' else prod)\n",
" return sum_or_prod(vertically(numbers))\n",
"\n",
"def vertically(numbers: List[str]) -> List[int]:\n",
"def vertically(numbers: List[str]) -> list[int]:\n",
" \"\"\"Return a list of integers found by reading numbers vertically by column.\"\"\"\n",
" return [int(cat(digits)) for digits in T(numbers)]"
]
@@ -1288,7 +1288,7 @@
{
"data": {
"text/plain": [
"Puzzle 6.2: .0061 seconds, answer 11159825706149 correct"
"Puzzle 6.2: .0028 seconds, answer 11159825706149 correct"
]
},
"execution_count": 43,
@@ -1402,9 +1402,9 @@
}
],
"source": [
"vertically(('123', \n",
"vertically(['123', \n",
" ' 45', \n",
" ' 6'))"
" ' 6'])"
]
},
{
@@ -1422,9 +1422,9 @@
"id": "953679f2-8b55-4bcb-b46f-db0d7c435ea3",
"metadata": {},
"source": [
"# [Day 7](https://adventofcode.com/2025/day/7): Laboratories\n",
"# Day 7: Laboratories (Counting paths through a grid)\n",
"\n",
"In the lab is a tachyon manifold in need of repair. We find a diagram of the manifold, with `S` marking the start of the tachyon beams and `^` marking a splitter. \n",
"In the lab on [**Day 7**](https://adventofcode.com/2025/day/7) is a tachyon manifold in need of repair. We find a diagram of the manifold, with `S` marking the start of the tachyon beams and `^` marking a splitter. \n",
"\n",
"I could parse this as a `Grid`, but I think I will just keep it as a list of strings. The main idea of a `Grid` is dealing with the 4 or 8 neighbors; a concept that this problem does not use."
]
@@ -1477,7 +1477,7 @@
"metadata": {},
"outputs": [],
"source": [
"def count_splits(manifold: List[str]) -> int:\n",
"def count_splits(manifold: list[str]) -> int:\n",
" \"\"\"How many beam split events occur as the beams make their way through the manifold?\"\"\"\n",
" start = manifold[0].index('S')\n",
" beams = {start}\n",
@@ -1500,7 +1500,7 @@
{
"data": {
"text/plain": [
"Puzzle 7.1: .0011 seconds, answer 1681 correct"
"Puzzle 7.1: .0006 seconds, answer 1681 correct"
]
},
"execution_count": 51,
@@ -1532,7 +1532,7 @@
"metadata": {},
"outputs": [],
"source": [
"def count_timelines(manifold: List[str]) -> int:\n",
"def count_timelines(manifold: list[str]) -> int:\n",
" \"\"\"How many possible paths are there to the final line of the manifold?\"\"\"\n",
" start = manifold[0].index('S')\n",
" beams = Counter({start: 1})\n",
@@ -1554,7 +1554,7 @@
{
"data": {
"text/plain": [
"Puzzle 7.2: .0020 seconds, answer 422102272495018 correct"
"Puzzle 7.2: .0015 seconds, answer 422102272495018 correct"
]
},
"execution_count": 53,
@@ -1572,9 +1572,9 @@
"id": "135b532d-d9ac-4400-9c6d-3b316d4596e1",
"metadata": {},
"source": [
"# [Day 8](https://adventofcode.com/2025/day/8): Playground\n",
"# Day 8: Playground (Unioning sets, in closest distance order)\n",
"\n",
"In a playground, some elves are setting up an ambitious Christmas light decoration project using electrical junction boxes. They have a list of the junction box coordinates in 3D space:"
"In a playground on [**Day 8**](https://adventofcode.com/2025/day/8), some elves are setting up an ambitious Christmas light decoration project using electrical junction boxes. They have a list of the junction box coordinates in 3D space:"
]
},
{
@@ -1641,9 +1641,9 @@
"metadata": {},
"outputs": [],
"source": [
"Circuit = Tuple[Point, ...]\n",
"Circuit = tuple[Point, ...]\n",
"\n",
"def greedy_connect(boxes, n=1000) -> Dict[Point, Circuit]:\n",
"def greedy_connect(boxes, n=1000) -> dict[Point, Circuit]:\n",
" \"\"\"Go through the `n` closest pairs of boxes, shortest first.\n",
" If two boxes can be connected to form a new circuit, do it.\"\"\"\n",
" circuits = {B: (B,) for B in boxes} # A dict of {box: circuit}\n",
@@ -1654,12 +1654,12 @@
" circuits[C] = new_circuit\n",
" return circuits\n",
"\n",
"def closest_pairs(points, n=1000) -> List[Tuple[Point, Point]]:\n",
"def closest_pairs(points, n=1000) -> list[tuple[Point, Point]]:\n",
" \"Return the `n` closest pairs of points, sorted shortest first.\"\n",
" pairs = combinations(points, 2)\n",
" return sorted(pairs, key=lambda link: distance_squared(*link))[:n]\n",
"\n",
"def largest(n, circuits) -> List[int]: \n",
"def largest(n, circuits) -> list[int]: \n",
" \"\"\"The lengths of the `n` largest circuits (ones with the most boxes).\"\"\"\n",
" return sorted(map(len, set(circuits.values())))[-n:]\n",
"\n",
@@ -1678,7 +1678,7 @@
{
"data": {
"text/plain": [
"Puzzle 8.1: .6053 seconds, answer 24360 correct"
"Puzzle 8.1: .4011 seconds, answer 24360 correct"
]
},
"execution_count": 56,
@@ -1708,7 +1708,7 @@
"metadata": {},
"outputs": [],
"source": [
"def last_connected(boxes) -> Tuple[Point, Point]:\n",
"def last_connected(boxes) -> tuple[Point, Point]:\n",
" \"\"\"Go through all the pairs of boxes, in closest first order. \n",
" Return the last two boxes that finally make it all one big circuit.\"\"\"\n",
" circuits = {B: (B,) for B in boxes} # A dict of {box: circuit}\n",
@@ -1731,7 +1731,7 @@
{
"data": {
"text/plain": [
"Puzzle 8.2: .6392 seconds, answer 2185817796 correct"
"Puzzle 8.2: .4378 seconds, answer 2185817796 correct"
]
},
"execution_count": 58,
@@ -1757,9 +1757,9 @@
"id": "9e75bcf2-d62b-47b1-bec0-b2449351d819",
"metadata": {},
"source": [
"# [Day 9](https://adventofcode.com/2025/day/9): Movie Theater \n",
"# Day 9: Movie Theater (Maximal rectangle inside a polygon)\n",
"\n",
"The Elves are redecorating the movie theater floor by switching out some of the square tiles in the big grid they form. Some of the tiles are red; their *x*, *y* coordinates form the day's input file:"
"On [**Day 9**](https://adventofcode.com/2025/day/9) the Elves are redecorating the movie theater floor by switching out some of the square tiles in the big grid they form. Some of the tiles are red; their *x*, *y* coordinates form the day's input file:"
]
},
{
@@ -1822,7 +1822,7 @@
"metadata": {},
"outputs": [],
"source": [
"Rectangle = Tuple[Point, Point] # Type: a rectangle is defined by two opposite corners\n",
"Rectangle = tuple[Point, Point] # Type: a rectangle is defined by two opposite corners\n",
"\n",
"def tile_area(rect: Rectangle):\n",
" \"\"\"Area, in tiles, of a rectangle defined by two opposite corner positions.\"\"\"\n",
@@ -1847,7 +1847,7 @@
{
"data": {
"text/plain": [
"Puzzle 9.1: .0262 seconds, answer 4772103936 correct"
"Puzzle 9.1: .0239 seconds, answer 4772103936 correct"
]
},
"execution_count": 61,
@@ -1882,7 +1882,7 @@
"outputs": [
{
"data": {
"image/png": "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 truncated
"image/png": "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 truncated
"text/plain": [
"<Figure size 700x700 with 1 Axes>"
]
@@ -1947,7 +1947,7 @@
"source": [
"GAP = 10000 # How big a gap between red tiles to put in a \"breadcrumb\" red tile\n",
"\n",
"def find_obstacles(red_tiles: Sequence[Point], d:int=GAP) -> Set[Point]:\n",
"def find_obstacles(red_tiles: Sequence[Point], d:int=GAP) -> set[Point]:\n",
" \"\"\"Obstacles are tiles that, when found inside a rectangle, disqualify that rectangle.\n",
" They are the set of red tiles with breadcrumbs added, minus the adjacent tiles with 180° turns.\"\"\"\n",
" return (set(red_tiles) | set(breadcrumbs(red_tiles, d))) - adjacent_180_tiles(red_tiles)\n",
@@ -1960,12 +1960,12 @@
" for i in range(1, distance(p, q) // d + 1):\n",
" yield (X_(q) + i * dx, Y_(q) + i * dy)\n",
"\n",
"def adjacent_180_tiles(red_tiles: Set[Point]) -> Set[Point]:\n",
"def adjacent_180_tiles(red_tiles: Collection[Point]) -> set[Point]:\n",
" \"\"\"Yield all adjacent points that form a 180 degree U-turn.\"\"\"\n",
" return union({q, r} for (p, q, r, s) in sliding_window(red_tiles, 4)\n",
" if distance(q, r) == 1 and direction(p, q) == direction(s, r))\n",
"\n",
"def find_2_corners(red_tiles, d:int=GAP) -> Optional[List[Point]]:\n",
"def find_2_corners(red_tiles, d:int=GAP) -> Optional[list[Point]]:\n",
" \"\"\"Find two adjacent corners, separated on each side by a gap of at least `d`.\"\"\"\n",
" return first([q, r] for [p, q, r, s] in sliding_window(red_tiles, 4)\n",
" if distance(p, q) > d and distance(r, s) > d)\n",
@@ -1992,7 +1992,7 @@
"outputs": [
{
"data": {
"image/png": "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 truncated
"image/png": "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 truncated
"text/plain": [
"<Figure size 700x700 with 1 Axes>"
]
@@ -2034,7 +2034,7 @@
" return rect\n",
" raise ValueError('No rectangle') # Shouldn't get here unless there are no corners\n",
"\n",
"def any_intrusions(obstacles: List[Point], rect: Rectangle) -> bool:\n",
"def any_intrusions(obstacles: list[Point], rect: Rectangle) -> bool:\n",
" \"\"\"Does any ostacle point intrude inside the rectangle?\"\"\"\n",
" # OK for an obstacle to be on border or just one square in, but not 2 squares in\n",
" xlo, xhi = min(Xs(rect)) + 2, max(Xs(rect)) - 2\n",
@@ -2060,7 +2060,7 @@
{
"data": {
"text/plain": [
"Puzzle 9.2: .0068 seconds, answer 1529675217 correct"
"Puzzle 9.2: .0039 seconds, answer 1529675217 correct"
]
},
"execution_count": 66,
@@ -2089,7 +2089,7 @@
"outputs": [
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAA/QAAAPHCAYAAACGywJhAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjYuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8o6BhiAAAACXBIWXMAAA9hAAAPYQGoP6dpAADHI0lEQVR4nOz9f3yU9Z3v/z+vmRDID5lla5Elo6OYgeLS3W7VWkyFSKvpp6Xudo9tt1g2spGkkGqp2irdle3up5Wj9Sj7gcaT6ByNlC7H2o9n91vRpdtNqWgplNbdUhTjWX5dUUvPN3RoxpAhmevzxzuZ38lMyCSTmXncb7e55cqVd2auQDLJa97v9/NlOY7jCAAAAAAAFBRXvi8AAAAAAACMHwU9AAAAAAAFiIIeAAAAAIACREEPAAAAAEABoqAHAAAAAKAAUdADAAAAAFCAKOgBAAAAAChAZfm+gOkuEonozTff1AUXXCDLsvJ9OQAAAACAIuc4jn73u99p/vz5crlGn4enoM/gzTff1MUXX5zvywAAAAAAlJiTJ0/K6/WO+nEK+gwuuOACSeYfcvbs2Xm+GgAAAABAsTtz5owuvvjiaD06Ggr6DEaW2c+ePZuCHgAAAAAwZTJt+yYUDwAAAACAAkRBDwAAAABAAaKgBwAAAACgAFHQAwAAAABQgCjoAQAAAAAoQBT0AAAAAAAUIAp6AAAAAAAKEAU9AAAAAAAFiIIeAAAAAIACREEPAAAAAEABoqAHAAAAAKAAUdADAAAAAFCAKOgBAAAAAChAFPQAAAAAABQgCnoAAAAAAAoQBT0AAAAAAAWIgh4AAAAAgAJEQQ8AAAAAQAGioAcAAAAAoACNu6D/8Y9/rE984hOaP3++LMvS//pf/yvh447j6Gtf+5rmz5+viooK1dfX61e/+lXCmIGBAd1+++268MILVVVVpZtuukm2bSeMOX36tFavXi2PxyOPx6PVq1frt7/9bcKYEydO6BOf+ISqqqp04YUX6o477lA4HE4Y88tf/lLLly9XRUWFampq9Pd///dyHGe8XzYAAAAAANPKuAv6UCikP/7jP9a2bdvSfvzBBx/Uww8/rG3btunAgQOaN2+ebrjhBv3ud7+LjtmwYYOeffZZ7dy5U3v37lVfX59WrlypoaGh6JhVq1bplVde0QsvvKAXXnhBr7zyilavXh39+NDQkD7+8Y8rFApp79692rlzp773ve/prrvuio45c+aMbrjhBs2fP18HDhzQ1q1b9dBDD+nhhx8e75cNAAAAAMC0YjkTmK62LEvPPvus/uzP/kySmZ2fP3++NmzYoHvuuUeSmY2/6KKL9MADD6ilpUXBYFDvfve7tX37dn3mM5+RJL355pu6+OKLtWvXLjU0NOjVV1/VFVdcoX379umaa66RJO3bt09Lly7Va6+9pkWLFun555/XypUrdfLkSc2fP1+StHPnTt166606deqUZs+erUcffVQbN27Ur3/9a82cOVOS9F//63/V1q1bZdu2LMvK+DWeOXNGHo9HwWBQs2fPPt9/KgAAAAAAspJtHZrTPfRHjx7V22+/rRtvvDF6bubMmVq+fLlefvllSdLBgwd17ty5hDHz58/XkiVLomN+8pOfyOPxRIt5SfrgBz8oj8eTMGbJkiXRYl6SGhoaNDAwoIMHD0bHLF++PFrMj4x58803dezYsbRfw8DAgM6cOZNwAwAAAABguslpQf/2229Lki666KKE8xdddFH0Y2+//bbKy8s1Z86cMcfMnTs35f7nzp2bMCb5cebMmaPy8vIxx4y8PzIm2ebNm6P79j0ejy6++OLMXzgAAAAAAFNsUlLuk5eyO46TcXl78ph043MxZmSHwWjXs3HjRgWDwejt5MmTY143AAAAAAD5kNOCft68eZJSZ79PnToVnRmfN2+ewuGwTp8+PeaYX//61yn3/5vf/CZhTPLjnD59WufOnRtzzKlTpySlriIYMXPmTM2ePTvhBgAAAADAdJPTgv6yyy7TvHnz9IMf/CB6LhwOa8+ePbr22mslSVdeeaVmzJiRMOatt97SoUOHomOWLl2qYDCo/fv3R8f89Kc/VTAYTBhz6NAhvfXWW9Exu3fv1syZM3XllVdGx/z4xz9OaGW3e/duzZ8/X5deemkuv3QAAAAAAKbUuAv6vr4+vfLKK3rllVckmSC8V155RSdOnJBlWdqwYYPuv/9+Pfvsszp06JBuvfVWVVZWatWqVZIkj8ejpqYm3XXXXfrhD3+oX/ziF/rc5z6n9773vfrIRz4iSVq8eLE++tGPau3atdq3b5/27duntWvXauXKlVq0aJEk6cYbb9QVV1yh1atX6xe/+IV++MMf6u6779batWujs+qrVq3SzJkzdeutt+rQoUN69tlndf/99+vO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 truncated
"image/png": "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 truncated
"text/plain": [
"<Figure size 1200x1200 with 1 Axes>"
]
@@ -2128,8 +2128,8 @@
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 1.3 s, sys: 139 ms, total: 1.44 s\n",
"Wall time: 547 ms\n"
"CPU times: user 510 ms, sys: 4.08 ms, total: 515 ms\n",
"Wall time: 519 ms\n"
]
},
{
@@ -2144,7 +2144,9 @@
}
],
"source": [
"%time tile_area(biggest_rectangle(red_tiles, red_tiles)) == tile_area(biggest_rectangle(red_tiles, find_2_corners(red_tiles)))"
"R = red_tiles\n",
"\n",
"%time tile_area(biggest_rectangle(R, R)) == tile_area(biggest_rectangle(R, find_2_corners(R)))"
]
},
{
@@ -2154,7 +2156,7 @@
"source": [
"Yes, it gets the correct answer, and yes, it is slower.\n",
"\n",
"In my puzzle (and if Eric Wastl is being fair (which he always is), then in everybody's puzzle) there were no adjacent points, and thus no `adjacent_180_tiles`. So here is a test case with five pairs of adjacent points. We can see that `adjacent_180_tiles` coorectly found the ten adjacent points (the magenta \"o\"s), that it correctly did not eliminate obstacles for the adjacent points that do not form 180° U-turns, and that `biggest_rectangle` finds what appears to be the right answer."
"In my puzzle there were no adjacent points, and thus no `adjacent_180_tiles`. (And if Eric Wastl is being fair (which he always is), then everybody's puzzle would be the same.) So here is a test case with five pairs of adjacent points. We can see that `adjacent_180_tiles` correctly found the ten adjacent points (the magenta \"o\"s) and eliminated them as obstacles. It also correctly did not eliminate obstacles for the adjacent points that do not form 180° U-turns (one on the left and one at the top). Finally, `biggest_rectangle` returns the blue rectangle, which appears to be the right answer."
]
},
{
@@ -2165,7 +2167,7 @@
"outputs": [
{
"data": {
"image/png": "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 truncated
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAlkAAAJKCAYAAAAfulByAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlHJYcgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAQ1JJREFUeJzt3X94VPWd//3XySQzyYQkgMGEkBTBhFZQqQWXEqWkVvBG15uW4raL6x1drGhASSnqgt9K3IsfLroIX1EL1QKrtdavlba3tS7sXYpYSwUqrYZeCLsoSSQGBRLyayY/zv3HSWYYCJkg+eRkmOfjus7FJ+/5MHlzZjJ5cc7M51i2bdsCAABAr0pwuwEAAIALESELAADAAEIWAACAAYQsAAAAAwhZAAAABhCyAAAADCBkAQAAGEDIAgAAMICQBQAAYAAhCwAAwACjIausrEyWZUVs2dnZodtt21ZZWZlycnKUkpKioqIilZeXm2wJAACgTySa/gZjxozRf/3Xf4W+9ng8ofHKlSu1atUqbdy4UaNGjdLSpUs1ZcoU7d+/X2lpaT26//b2dn388cdKS0uTZVm93j8AAMCpbNvWyZMnlZOTo4SEbo5X2QYtWbLEHjt2bJe3tbe329nZ2fajjz4aqjU3N9sZGRn2j370ox5/j4qKClsSGxsbGxsbG1ufbhUVFd1mFONHsg4cOKCcnBz5fD5NmDBBy5cv18iRI3Xo0CFVV1dr6tSpobk+n0+TJ0/W22+/rTlz5nR5f4FAQIFAIPS1bduSpIqKCqWnp5v9xwAAgLhXV1envLy8qGfdjIasCRMm6D/+4z80atQoffLJJ1q6dKkKCwtVXl6u6upqSVJWVlbE38nKytJHH3101vtcsWKFHnnkkTPq6enphCwAANBnor1Nyegb36dNm6Zvf/vbuuKKK3T99dfrN7/5jSRp06ZNZ23Qtu1um160aJFqa2tDW0VFhZnmAQAAzkOfLuGQmpqqK664QgcOHAh9yrDziFanmpqaM45uncrn84WOWnH0CgAA9Fd9GrICgYD+9re/aejQoRoxYoSys7O1devW0O3BYFDbt29XYWFhX7YFAADQ64y+J2vhwoW6+eab9YUvfEE1NTVaunSp6urqVFxcLMuyVFpaquXLl6ugoEAFBQVavny5/H6/Zs2aZbItAAAA44yGrMrKSv3jP/6jPv30Uw0ZMkRf/epXtXPnTg0fPlyS9MADD6ipqUklJSU6fvy4JkyYoC1btvR4jSwAAID+yrI710CIUXV1dcrIyFBtbS3vzwIAAMb1NHtw7UIAAAADCFkAAAAGELIAAAAMIGQBAAAYQMgCAAAwgJAFAABgACELAADAAEIWAACAAYQsAAAAAwhZAAAABhCyAAAADCBkAQAAGEDIAgAAMICQBQAAYAAhCwAAwABCFgAAgAGELAAAAAMIWQAAAAYQsgAAAAwgZAEAABhAyAIAADCAkAUAAGAAIQsAAMAAQhYAAIABhCwAAAADCFkAAAAGELIAAAAMIGQBAAAYQMgCAAAwgJAFAABgACELAADAAEIWAACAAYQsAAAAAwhZAAAABhCyAAAADCBkAQAAGEDIAgAAMICQBQAAYAAhCwAAwABCFgAAgAGELAAAAAMIWQAAAAYQsgAAAAwgZAEAABhAyAIAADCAkAUAAGAAIQsAAMAAQhYAAIABhCwAAAADCFkAAAAGELIAAAAMIGQBAAAYQMgCAAAwgJAFAABgACELAADAAEIWAACAAYQsAAAAAwhZAAAABhCyAAAADCBkAQAAGEDIAgAAMICQBQAAYAAhCwAAwABCFgAAgAGELAAAAAMIWQAAAAYQsgAAAAwgZAEAABhAyAIAADCAkAUAAGAAIQsAAMAAQhYAAIABhCwAAAADCFkAAAAG9FnIWrFihSzLUmlpaahm27bKysqUk5OjlJQUFRUVqby8vK9aAgAAMKZPQtauXbu0fv16XXnllRH1lStXatWqVVq7dq127dql7OxsTZkyRSdPnuyLtgCcq8pKads2508A6I/60etUoulvUF9fr1tvvVU//vGPtXTp0lDdtm2tXr1aDz30kGbMmCFJ2rRpk7KysvTiiy9qzpw5plsDcC6ee072XXfJam+XnZCg4NNPqvWOYre7AoCQxA2b5C25V1Z7u5SQIK1fL82e7Vo/lm3btslvUFxcrMGDB+uJJ55QUVGRvvzlL2v16tX6n//5H1166aX685//rKuuuio0f/r06Ro4cKA2bdrU5f0FAgEFAoHQ13V1dcrLy1Ntba3S09NN/lOA+FVZKXv4cOeFq0OrJV1SKlVluNcWAHQaVit9tFrynJpqPB7pww+l3Nxe/V51dXXKyMiImj2Mni586aWX9Oc//1krVqw447bq6mpJUlZWVkQ9KysrdFtXVqxYoYyMjNCWl5fXu00DONOBAxEBS5ISbSn/mEv9AMBpCo6dFrAkqa1NOnjQlX4kg6cLKyoqNH/+fG3ZskXJyclnnWdZVsTXtm2fUTvVokWLtGDBgtDXnUeyABhUUCA7ISEiaNkej15/eJ/s3GEuNgYADquySvbzl0X+h9DjkfLzXevJWMjas2ePampqNG7cuFCtra1Nb775ptauXav9+/dLco5oDR06NDSnpqbmjKNbp/L5fPL5fKbaBtCV3FwFn35SnnvmKtF2Apa1bp38I0e53RkAOEaOUqCL16nePlV4LoydLvzGN76h9957T3v37g1t48eP16233qq9e/dq5MiRys7O1tatW0N/JxgMavv27SosLDTVFoDPqfWOYl1SKhUVS00f7HP1zaQA0JX+9jpl7EhWWlqaLr/88ohaamqqLrroolC9tLRUy5cvV0FBgQoKCrR8+XL5/X7NmjXLVFsAzkNVhrNxihBAf9WfXqeML+HQnQceeEBNTU0qKSnR8ePHNWHCBG3ZskVpaWlutnWmXbukHTukSZOkq692uxvANcNqnTeXWpVVEqcKAaBbxpdwMK2nH6P83G6/XfamTbIk2ZIaZhWrce1TSkqSvF5nSnu71NTkjFNTw381EJBaWxUx17alxkZn7PdLne/xDwallpZzm5uYKJ369rSGBufPlBRneZBzndvS4sz3eKRTP6vQ2Oj0kpzs3Hauc1tbnX2RkOB8v05NTc6+O5e5Pp/zb5GcD400Nzv7xe//fHObm53bvF5n30tnfzzPZW60x/58nydnezw/7/PkbI/nqXPtn6xX6rwFarcTFbCSlfj0k0q++/bQ3HN57D/P86Srx/N8niddPZ698Tzp6vHkNaL7uRfyawT6VkOwQQNWDJAk1S+qV6o3Ncrf+Hx6nD3sGFdbW2tLsmtra3v/zt95x253Xg9CW7E2OMMpC22VydkW5Di1hGC4ViZbV6916pOXhGsPZoTv7oeJ4XrhSqdWuDJc+2FieO6DGeH65CVO7eq1kd8vIejUF+SEa1MWOrWxGyLn+o479Xvzw7UbS5za6Jcj56ZVOvU5Y8O16cVOreC1yLmDP3Dq/1wYrt0y06kN3xY5N+tdp37b9eHarBudWs47kXPz3nLq35kerhVPdmpD3o+cO3KLU58xK1z73ninlnEocu4XNzv1m+8M10pGOzV/TeTcK15w6jfMD9fmD3dqSfWRc7+y3qlftzhcuz8z/HieOnfCE05t0tJwbbE/PHexP1yftNSpTXgi8j46596fGa5dt9ipfWV95Nykeqc+f3i4dsN8p3bFC5Fz/TW2ZNsX3zbabrWcb7Jed9qSbU/XL227oiL04zJ8uNPDO++Ef4ReeMGpXX995I/W6NFOfdu2cG3zZqdWWBg5d/x4p/7aa+Hali1ObezYyLmTJzv1l18O1956y6nl50fOvfFGp75hQ7j27rtOLScncu7MmU597dpw7YMPnFpGRuTc4mKnvnJluFZZ6dQSEyPnlpQ49SVLwrXjx8MvOcFguL5woVNbuDBcCwbDc48fD9eXLHFqJSWR3y8x0alXVoZrK1c6teLiyLkZGU79gw/CtbVrndrMmZFzc3Kc+rvvhmsbNji1G2+MnJuf79Tfeitce/llpzZ5cuTcsWOd+pYt4dprrzm18eMj5xYWOvXNm8O1bduc2ujRkXOvv96pv/BCuPbOO05t+PDIudOnO/X168O19993apmZkXNnzXLqTzxhwyX1gfBrcX2g3tj36Wn24ALR3dmxQ43yy5ItS7Ya5I/+d4AL0PDaLtafke3q+jNAf9be7qyB+eGHzhjxidOF3dm1S/V/V6Q0OcfN65Uqj1pVv/X/k+erV3EqgFMBcXO60P9ZlVK/6Kw/06JEBeWVJ0FK/mh/6OPRnC50cLrw3OdeiK8RwaA0wDlrpfr6yHkwp7+dLiRkRbv/735bzT9/U5aki/SpEoqLpY0be/37AP3ec89Jc+Y4v0U8HmndOtc/Hg30Vw0N0sUXO+OaGkJWX+lvIcvVTxfGgs+eeVz/kDZS11RI9y/crGHXf9PtlgB3zJ4t3XCDc4owP9/VBf6A/i41NXw0EPGLkNUDu3Odbf64sW63ArgrN5dwBQA9RMiKIhiU9OZiZ3y3u70AAIDYQciKorXFkn63rGP8obvNAABiQiAgzZvnjNeujfxQAeIHISsKT6ItfeXHHePrXe4GABALWlulZ591xqtXE7LiFSErCp9P0v99V8f4f9xtBgAQE5KSpKVLw2PEJ0IWAAC9zOuVHnrI7S7gNlZ8BwAAMIAjWVE0NljSsnpnPPuoNMjlhgAA/Z5tS59+6owzM8Or8SO+ELJ6oqVzxdijrrYBAIgNjY3hFd+5rE78ImRFkZxiS/Mv6Rhvc7cZAAAQMwhZUSQkSBr0UXgMAEAUqanOKUPEN2IDAACAARzJiqKlRdIf5zvje9ztBQAAxA5CVhQtQUv6z9Ud4w9d7QUAEBsCAenBB53xv/0bK77HK0JWFB6PpCt+2jG+xt1mAAAxobVVWrPGGS9bRsiKV4SsKHzJtvTtf+oYc1kdAEB0SUnLine truncated
"text/plain": [
"<Figure size 700x700 with 1 Axes>"
]
@@ -2176,8 +2178,8 @@
],
"source": [
"points = [(-16, 3), (-16, 24), (-17, 24), (-17, 39), (-17, 32), (-5, 32), (-5, 33), (-17, 33), \n",
" (-17, 39), (30, 39), (30, 38), (41, 38), (41, -9), \n",
" (40, -9), (40, 27), (18, 27), (18, 26), (34, 26), (34, 3), (16, 3), (16, 15), \n",
" (-17, 39), (6, 39), (6, 40), (41, 40), (41, 0), \n",
" (40, 0), (40, 27), (18, 27), (18, 26), (34, 26), (34, 3), (16, 3), (16, 15), \n",
" (30, 15), (30, 16), (10, 16), (10, -3), (2, -3), (2, 27), (1, 27), (1, 3)]\n",
"\n",
"plot_tiles(points) # Green dots and red lines\n",
@@ -2192,8 +2194,8 @@
"source": [
"**Three final remarks**:\n",
"1) This was the first puzzle of the year that was **difficult**; we had to work to find an efficient solution.\n",
"2) My solution is **unsatisfying** in that it works for *my* input, and I strongly suspect that it would work for *your* input, because Eric Wastl probably created them all to be similar. But it does not work on every possible input (particularly a highly non-convex shape).\n",
"3) In retrospect, I could have done the standard edge-edge intersection algorithm for determining if the rectangle intersects the polygon formed by the red tiles. I was scared away from that because the geometry is tricky for general polygon intersection, when the edges can have any slope. But the geometry is actually easy when all lines are axis-aligned."
"2) My solution is **unsatisfying** in that it works for *my* input, and I strongly suspect that it would work for *your* input, because Eric Wastl probably created them all to be similar. But it does not work on every possible input. I know that with a highly non-convex polygon as input, it can find a maximal-area rectangle that is completely outside the polygon.\n",
"3) In retrospect, I could have done the standard edge-edge intersection algorithm for determining if the rectangle intersects the polygon formed by the red tiles. I was scared away from that because the geometry is tricky for general polygon intersection, when the edges can have any slope. But the geometry is actually easy when all lines are axis-aligned, and that approach (while slower) would handle the non-convex cases."
]
},
{
@@ -2201,9 +2203,9 @@
"id": "555e1ce1-9cf2-4f74-b5f4-9f94fb9ddaa0",
"metadata": {},
"source": [
"# [Day 10](https://adventofcode.com/2025/day/10): Factory\n",
"# Day 10: Factory (Integer linear programming)\n",
"\n",
"Today we find the machines in the factory are offline. Each machine has some lights, some buttons that can be pressed, and a joltage requirement for each light. For example, the machine described by:\n",
"On [**Day 10**](https://adventofcode.com/2025/day/10) we find the machines in the factory are offline. Each machine has some lights, some buttons that can be pressed, and a joltage requirement for each light. For example, the machine described by:\n",
"\n",
" [.##.] (3) (1,3) (2) (2,3) (0,2) (0,1) {3,5,4,7}\n",
"\n",
@@ -2298,7 +2300,7 @@
{
"data": {
"text/plain": [
"Puzzle 10.1: .0840 seconds, answer 441 correct"
"Puzzle 10.1: .0492 seconds, answer 441 correct"
]
},
"execution_count": 72,
@@ -2450,7 +2452,7 @@
{
"data": {
"text/plain": [
"Puzzle 10.2: .1304 seconds, answer 18559 correct"
"Puzzle 10.2: .1458 seconds, answer 18559 correct"
]
},
"execution_count": 76,
@@ -2516,7 +2518,7 @@
{
"data": {
"text/plain": [
"Counter({-1: 32, -2: 31, 1: 34, 2: 31, 0: 32, -3: 5})"
"Counter({1: 34, -1: 32, 0: 32, -2: 31, 2: 31, -3: 5})"
]
},
"execution_count": 78,
@@ -2566,9 +2568,9 @@
"id": "2e4af311-07cb-4777-ad81-7973ba5a3633",
"metadata": {},
"source": [
"# [Day 11](https://adventofcode.com/2025/day/11): Reactor\n",
"# Day 11: Reactor (Counting paths through a graph)\n",
"\n",
"There are a bunch of servers here in the reactor room. Today's input consists of a list of device connectivity specifications; the line \"`wfc: ond mpw vsz`\" means that device `wfc` outputs to 3 devices, `ond`, `mpw`, and `vsz`. I'll capture the data like this:"
"On [**Day 11**](https://adventofcode.com/2025/day/11) there are a bunch of servers here in the reactor room. Today's input consists of a list of device connectivity specifications; the line \"`wfc: ond mpw vsz`\" means that device `wfc` outputs to 3 devices, `ond`, `mpw`, and `vsz`. I'll capture the data like this:"
]
},
{
@@ -2712,7 +2714,7 @@
{
"data": {
"text/plain": [
"Puzzle 11.2: .0020 seconds, answer 306594217920240 correct"
"Puzzle 11.2: .0029 seconds, answer 306594217920240 correct"
]
},
"execution_count": 84,
@@ -2730,9 +2732,9 @@
"id": "dc115ac2-5357-499d-8917-3fff29f4063b",
"metadata": {},
"source": [
"# [Day 12](https://adventofcode.com/2025/day/12): Christmas Tree Farm \n",
"# Day 12: Christmas Tree Farm (Treating trivial cases first)\n",
"\n",
"On the twelfth day, we're in a cavern full of Christmas trees and the elves would like help arranging presents under the trees. The day's input is in two sections. The first section is a list of 6 present shapes, each annotated with their shape ID. Every present shape fits in a 3x3 grid, but they are odd shapes so some shapes could be squished closer together. The second section is a list of regions, where each region has a width and length, and a desired number of presents of each shape to fit, in shape-ID order. Each shape is a separate paragraph, but the regions are all in on paragraph, so I can parse them like this:"
"On the [**twelfth day**](https://adventofcode.com/2025/day/12), we're in a cavern full of Christmas trees and the elves would like help arranging presents under the trees. The day's input is in two sections. The first section is a list of 6 present shapes, each annotated with their sequential shape ID. The second section is a list of regions, where each region has a width and length, and a desired number of presents of each shape to fit, in shape-ID order. Each shape is a separate paragraph, but the regions are all in on paragraph, so I can parse them like this:"
]
},
{
@@ -2783,26 +2785,28 @@
"41x37: 44 34 38 35 40 44\n",
"...\n",
"────────────────────────────────────────────────────────────────────────────────────────────────────\n",
"Parsed representation ➜ 7 tuples of size 2 to 1000:\n",
"Parsed representation ➜ 7 lists of size 3 to 1000:\n",
"────────────────────────────────────────────────────────────────────────────────────────────────────\n",
"(0, ['###', '.##', '##.'])\n",
"(1, ['##.', '##.', '###'])\n",
"(2, ['#..', '##.', '###'])\n",
"(3, ['###', '#.#', '#.#'])\n",
"(4, ['#.#', '###', '#.#'])\n",
"(5, ['##.', '.##', '..#'])\n",
"((45, 41, [52, 43, 45, 41, 47, 59]), (45, 41, [29, 27, 34, 34, 34, 36]), (41, 37, [44, 34, 38, 3 ...\n"
"['###', '.##', '##.']\n",
"['##.', '##.', '###']\n",
"['#..', '##.', '###']\n",
"['###', '#.#', '#.#']\n",
"['#.#', '###', '#.#']\n",
"['##.', '.##', '..#']\n",
"[Region(W=45, L=41, quantities=[52, 43, 45, 41, 47, 59]), Region(W=45, L=41, quantities=[29, 27, ...\n"
]
}
],
"source": [
"Region = namedtuple('Region', 'W, L, quantities') # width, length, and list of quantities of each present shape\n",
"\n",
"def parse_presents(text: str):\n",
" \"\"\"Parse either a single present (e.g. \"5: ###...\") or list of regions (e.g. \"12x5: 1 0 1 0 2 2\\n...\").\"\"\"\n",
" if 'x' in text:\n",
" return tuple((W, L, quantities) for (W, L, *quantities) in map(ints, text.splitlines()))\n",
" return [Region(W, L, quantities) for (W, L, *quantities) in map(ints, text.splitlines())]\n",
" else:\n",
" id, *shape = text.splitlines()\n",
" return (int(id[:-1]), shape)\n",
" return shape\n",
" \n",
"*shapes, regions = parse(12, parse_presents, paragraphs, show=33)"
]
@@ -2814,41 +2818,25 @@
"source": [
"### Part 1: How many of the regions can fit all of the presents listed?\n",
"\n",
"There have been Tetris-like puzzles in past AoC years. Is this another search problem? If so, how big will the search trees be? I want to get a feel. First, how many regions?"
"Every present shape fits in a 3x3 **box**, so we could just start laying down presents next to each other, box by box. So 2 presents fit in a 6x3 region:\n",
"\n",
" ### XXX ###XXX\n",
" #.# + X.X = #.#X.X\n",
" #.# X.X #.#X.X\n",
"\n",
"But if we rotate some presents and squish them together we can do better. We could fit two presents into a 4x4 region:\n",
"\n",
" ### X.X ###.\n",
" #.# + X.X = #X#X\n",
" #.# XXX #X#X\n",
" .XXX\n",
"\n",
"I want to know how much squishing we have to do; is this going to be easy or hard? First, what's the average area of the regions?"
]
},
{
"cell_type": "code",
"execution_count": 86,
"id": "194cbece-0104-4934-b335-13a4e7c720e0",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"1000"
]
},
"execution_count": 86,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"len(regions)"
]
},
{
"cell_type": "markdown",
"id": "f579c5f1-f44c-4f9e-b6a9-5c75dbf20cfa",
"metadata": {},
"source": [
"What's the average area of the regions?"
]
},
{
"cell_type": "code",
"execution_count": 87,
"id": "388d13ab-b5db-47e4-9f33-50f7062424b9",
"metadata": {},
"outputs": [
@@ -2858,13 +2846,13 @@
"1822.223"
]
},
"execution_count": 87,
"execution_count": 86,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"mean(W * L for (W, L, _) in regions)"
"mean(region.W * region.L for region in regions)"
]
},
{
@@ -2877,7 +2865,7 @@
},
{
"cell_type": "code",
"execution_count": 88,
"execution_count": 87,
"id": "5d982737-cbca-4739-be1c-fa1dce319ef4",
"metadata": {},
"outputs": [
@@ -2887,13 +2875,13 @@
"240.488"
]
},
"execution_count": 88,
"execution_count": 87,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"mean(sum(quantities) for (_, _, quantities) in regions)"
"mean(sum(region.quantities) for region in regions)"
]
},
{
@@ -2901,18 +2889,18 @@
"id": "af3fe830-aa75-469b-9046-3f36ff3a03e2",
"metadata": {},
"source": [
"Next I want to get a feel for the variation in how tight the packing is. Each present can definitely fit into a 3x3 **box**, so what's the ratio of the total quantity of presents to the number of 3x3 boxes? I'll make a histogram of that ratio, which I'll call the occupancy ratio, for each region:"
"Next I want to get a feel for the variation in how tight the packing is. Each present can definitely fit into a 3x3 box, so what's the ratio of the total quantity of presents to the number of 3x3 boxes? I'll call that the **occupancy ratio**. I'll make a histogram of occupancy ratio for each region:"
]
},
{
"cell_type": "code",
"execution_count": 89,
"execution_count": 88,
"id": "b8cf45b6-5513-426a-86c2-425d0d74d781",
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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 truncated
"image/png": "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 truncated
"text/plain": [
"<Figure size 640x480 with 1 Axes>"
]
@@ -2922,17 +2910,12 @@
}
],
"source": [
"def boxes(region_W, region_L, shape_W=3, shape_L=3) -> int: \n",
" \"\"\"Number of shape-sized boxes that can fit into a WxL region. By default 3x3 boxes.\"\"\"\n",
" # Our presents all fit in a 3x3 box, but if they were diffferent, say 4x3,\n",
" # then we try to fit the boxes into the region both ways and take the most.\n",
" return max((region_W // shape_W) * (region_L // shape_L),\n",
" (region_W // shape_L) * (region_L // shape_W))\n",
"def boxes(region: Region) -> int: \n",
" \"\"\"Number of 3x3 boxes that can fit into the region.\"\"\"\n",
" return (region.W // 3) * (region.L // 3)\n",
" \n",
"occupancy_ratios = [sum(quantities) / boxes(W, L) \n",
" for (W, L, quantities) in regions]\n",
"\n",
"plt.hist(occupancy_ratios, bins=100);"
"occupancy_ratios = [sum(region.quantities) / boxes(region) for region in regions]\n",
"plt.hist(occupancy_ratios, bins=120);"
]
},
{
@@ -2940,27 +2923,29 @@
"id": "54f2eaa0-b8b6-4a60-bcde-28eeefa26e8c",
"metadata": {},
"source": [
"**Very interesting!** There's a real split. About half the regions have an occupancy ratio below 1.0 and thus are trivially easy to fit into the region: just plop one present into each 3x3 box, without worrying about rotations. The rest of the regions with occupancy ratios above 1.35 may well be impossible to fit. Just looking at the shapes, I estimate that the average overlap of one shape with another would be to save two \".\" squares; so I could see getting to an occupancy rato of 1 + 2/9 = 1.22, but I don't think it is possible to get to 1.35; almost certainly not 1.4 or more. I can prove it is impossible to fit all the presents in a region if the total area of the solid parts of the presents (the '#' squares) is more than the total area of the region (the width times length). \n",
"**Very interesting!** There's a real split:\n",
"- About half the regions have an occupancy ratio below 1.0 and thus trivially fit into the region: plop one present into each 3x3 box.\n",
"- The rest of the regions have occupancy ratios above 1.35. The average shape looks like it could overlap with another piece to save about two \".\" squares. So an occupancy rato of 1 + 2/9 = 1.22 looks like it could be made to fit, but 1.35 or more seems unlikely. I could prove it is impossible if the total area of the solid parts of the presents (the '#' squares) is more than the total area of the region (the width times length). \n",
"\n",
"I can do **triage** on the regions to classify each one as a trivial fit, an impossible fit, or an uncertain fit (for which we would have to do a search):"
]
},
{
"cell_type": "code",
"execution_count": 90,
"execution_count": 89,
"id": "0cd74af7-a078-4e63-975a-ee459f3eb26d",
"metadata": {},
"outputs": [],
"source": [
"def triage(regions, shapes) -> str:\n",
" \"\"\"Decide if a region's presents trivially fit, or are impossible to fit, or it is uncertain.\"\"\"\n",
" shape_area = [cat(shape).count('#') for shape in shapes] # List of total number of '#' in each shape\n",
"def triage(regions, shapes) -> Counter[str]:\n",
" \"\"\"Count how many regions fit, fail to fit, or are uncertain at first glance.\"\"\"\n",
" shape_area = [cat(shape).count('#') for shape in shapes] # list of total number of '#' in each shape\n",
" counts = Counter()\n",
" for (width, length, quantities) in regions:\n",
" presents_area = sum(q * shape_area[i] for (i, q) in enumerate(quantities))\n",
" result = ('fit' if sum(quantities) <= boxes(width, length) # The presents all fit in their own 3x3 box\n",
" else 'fail' if presents_area > width * length # The presents are bigger than the region\n",
" else '????') # Uncertain; would need a search\n",
" for region in regions:\n",
" presents_area = sum(q * shape_area[i] for (i, q) in enumerate(region.quantities))\n",
" result = ('fit' if sum(region.quantities) <= boxes(region) # The presents all fit\n",
" else 'fail' if presents_area > region.W * region.L # The presents can't fit\n",
" else 'uncertain') # We're uncertain\n",
" counts[result] += 1\n",
" return counts"
]
@@ -2975,7 +2960,7 @@
},
{
"cell_type": "code",
"execution_count": 91,
"execution_count": 90,
"id": "6768fa83-2af7-4aab-a930-9106da3859bd",
"metadata": {},
"outputs": [
@@ -2985,7 +2970,7 @@
"Counter({'fail': 546, 'fit': 454})"
]
},
"execution_count": 91,
"execution_count": 90,
"metadata": {},
"output_type": "execute_result"
}
@@ -3004,17 +2989,17 @@
},
{
"cell_type": "code",
"execution_count": 92,
"execution_count": 91,
"id": "0ec553c4-85eb-40a6-8bed-7adddf75e512",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"Puzzle 12.1: .0021 seconds, answer 454 correct"
"Puzzle 12.1: .0012 seconds, answer 454 correct"
]
},
"execution_count": 92,
"execution_count": 91,
"metadata": {},
"output_type": "execute_result"
}
@@ -3024,6 +3009,31 @@
" triage(regions, shapes)['fit'])"
]
},
{
"cell_type": "markdown",
"id": "0f9aa492-91ad-457a-b83e-13a12b899bc5",
"metadata": {},
"source": [
"You might well object that I didn't solve the problem, that there *could* have been uncertain regions. From the comments in [**r/adventofcode/**](https://www.reddit.com/r/adventofcode/) it looks like nobody got any uncertain regions, so I solved the problem that Eric Wastl intended, but I didn't solve for all possible inputs. You can see [an AI-generated solution](Advent-2025-AI.ipynb). For now, I'll just handle one part of a more general solution: what if the input shapes are not all 3x3? You could handle that with this updated version of boxes:"
]
},
{
"cell_type": "code",
"execution_count": 92,
"id": "edc1a7eb-f47e-4995-a775-ceee79045484",
"metadata": {},
"outputs": [],
"source": [
"def boxes(region, shapes=shapes) -> int: \n",
" \"\"\"Number of boxes that can fit into a region. Get the box dimensions from `shapes`.\"\"\"\n",
" def dimensions(shape) -> tuple[int, int]: return len(shape), len(shape[0])\n",
" shape_W = max(min(dimensions(shape) for shape in shapes)) # The smallest dimension of each box\n",
" shape_L = max(max(dimensions(shape) for shape in shapes)) # The largest dimension of each box\n",
" # Try to fit the boxes into the region both ways (i.e. one turned 90 degrees)\n",
" return max((region.W // shape_W) * (region.L // shape_L),\n",
" (region.W // shape_L) * (region.L // shape_W))"
]
},
{
"cell_type": "markdown",
"id": "7f31ae9b-6606-40b0-9bb1-ed9b3fe3cbf0",
@@ -3044,31 +3054,31 @@
"name": "stdout",
"output_type": "stream",
"text": [
"Puzzle 1.1: .0006 seconds, answer 1182 correct\n",
"Puzzle 1.2: .0013 seconds, answer 6907 correct\n",
"Puzzle 2.1: .0039 seconds, answer 23560874270 correct\n",
"Puzzle 2.2: .0039 seconds, answer 44143124633 correct\n",
"Puzzle 3.1: .0007 seconds, answer 17085 correct\n",
"Puzzle 3.2: .0020 seconds, answer 169408143086082 correct\n",
"Puzzle 4.1: .0610 seconds, answer 1569 correct\n",
"Puzzle 4.2: .1437 seconds, answer 9280 correct\n",
"Puzzle 5.1: .0118 seconds, answer 635 correct\n",
"Puzzle 5.2: .0002 seconds, answer 369761800782619 correct\n",
"Puzzle 6.1: .0021 seconds, answer 5877594983578 correct\n",
"Puzzle 6.2: .0061 seconds, answer 11159825706149 correct\n",
"Puzzle 7.1: .0011 seconds, answer 1681 correct\n",
"Puzzle 7.2: .0020 seconds, answer 422102272495018 correct\n",
"Puzzle 8.1: .6053 seconds, answer 24360 correct\n",
"Puzzle 8.2: .6392 seconds, answer 2185817796 correct\n",
"Puzzle 9.1: .0262 seconds, answer 4772103936 correct\n",
"Puzzle 9.2: .0068 seconds, answer 1529675217 correct\n",
"Puzzle 10.1: .0840 seconds, answer 441 correct\n",
"Puzzle 10.2: .1304 seconds, answer 18559 correct\n",
"Puzzle 1.1: .0004 seconds, answer 1182 correct\n",
"Puzzle 1.2: .0007 seconds, answer 6907 correct\n",
"Puzzle 2.1: .0023 seconds, answer 23560874270 correct\n",
"Puzzle 2.2: .0030 seconds, answer 44143124633 correct\n",
"Puzzle 3.1: .0006 seconds, answer 17085 correct\n",
"Puzzle 3.2: .0015 seconds, answer 169408143086082 correct\n",
"Puzzle 4.1: .0356 seconds, answer 1569 correct\n",
"Puzzle 4.2: .0920 seconds, answer 9280 correct\n",
"Puzzle 5.1: .0069 seconds, answer 635 correct\n",
"Puzzle 5.2: .0001 seconds, answer 369761800782619 correct\n",
"Puzzle 6.1: .0016 seconds, answer 5877594983578 correct\n",
"Puzzle 6.2: .0028 seconds, answer 11159825706149 correct\n",
"Puzzle 7.1: .0006 seconds, answer 1681 correct\n",
"Puzzle 7.2: .0015 seconds, answer 422102272495018 correct\n",
"Puzzle 8.1: .4011 seconds, answer 24360 correct\n",
"Puzzle 8.2: .4378 seconds, answer 2185817796 correct\n",
"Puzzle 9.1: .0239 seconds, answer 4772103936 correct\n",
"Puzzle 9.2: .0039 seconds, answer 1529675217 correct\n",
"Puzzle 10.1: .0492 seconds, answer 441 correct\n",
"Puzzle 10.2: .1458 seconds, answer 18559 correct\n",
"Puzzle 11.1: .0003 seconds, answer 574 correct\n",
"Puzzle 11.2: .0020 seconds, answer 306594217920240 correct\n",
"Puzzle 12.1: .0021 seconds, answer 454 correct\n",
"Puzzle 11.2: .0029 seconds, answer 306594217920240 correct\n",
"Puzzle 12.1: .0012 seconds, answer 454 correct\n",
"\n",
"Time in seconds: sum = 1.737, mean = .076, median = .004, max = .639\n"
"Time in seconds: sum = 1.216, mean = .053, median = .003, max = .438\n"
]
}
],
@@ -3081,7 +3091,7 @@
"id": "1098c017-2746-403c-bfb0-1a08cacc835d",
"metadata": {},
"source": [
"I solved all the puzzles and they run in under 2 seconds of total run time. \n",
"I solved all the puzzles and they run in well under 2 seconds of total run time. \n",
"\n",
"Happy Advent everyone, and thank you [Eric Wastl](https://was.tl/) for the interesting puzzles!\n",
"\n",
@@ -3110,7 +3120,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.15"
"version": "3.13.5"
}
},
"nbformat": 4,