{"id":23535697,"url":"https://github.com/marcelrobitaille/2021-advent-of-code","last_synced_at":"2025-10-07T13:50:45.495Z","repository":{"id":93953051,"uuid":"434363244","full_name":"MarcelRobitaille/2021-advent-of-code","owner":"MarcelRobitaille","description":"My solutions to the 2021 advent of code","archived":false,"fork":false,"pushed_at":"2022-01-05T03:48:07.000Z","size":3542,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-09-23T14:54:23.279Z","etag":null,"topics":["advent-of-code","advent-of-code-2021"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/MarcelRobitaille.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2021-12-02T20:20:50.000Z","updated_at":"2025-02-23T12:51:01.000Z","dependencies_parsed_at":"2023-04-12T20:31:25.908Z","dependency_job_id":null,"html_url":"https://github.com/MarcelRobitaille/2021-advent-of-code","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/MarcelRobitaille/2021-advent-of-code","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MarcelRobitaille%2F2021-advent-of-code","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MarcelRobitaille%2F2021-advent-of-code/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MarcelRobitaille%2F2021-advent-of-code/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MarcelRobitaille%2F2021-advent-of-code/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/MarcelRobitaille","download_url":"https://codeload.github.com/MarcelRobitaille/2021-advent-of-code/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MarcelRobitaille%2F2021-advent-of-code/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":278786690,"owners_count":26045588,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-07T02:00:06.786Z","response_time":59,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["advent-of-code","advent-of-code-2021"],"created_at":"2024-12-26T01:19:27.028Z","updated_at":"2025-10-07T13:50:45.485Z","avatar_url":"https://github.com/MarcelRobitaille.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# [Advent of Code 2021](https://adventofcode.com/2021)\n\nThis repository holds my solutions for the Advent of Code 2021.\nI did every day in Rust, but some are also done in Python (I started in Python and did some speedrun attempts in Python as well).\n\nI did not optimize for speed or for lines of code, but rather tried to write readable, idiomatic Rust. That said, I am still learning Rust and there are probably things I could do better. I also try to limit mutable state, wherever possible. For example, I prefer iterators rather than modifying a mutable variable in a loop. However, in some questions, I was not able to develop a totally immutable solution (in these cases, I like to at least write pure functions, even if there is some mutability inside).\n\n## The problems\n\nA quick summary of the problems and an explanation of my solution. (Spoilers, duh)\n\n**[Day 01: Sonar Sweep](./day_01)**\n\n[Problem statement](https://adventofcode.com/2021/day/1)\n\nThe goal of this challenge was simply to find the number of times the value in a sequence increases, or to apply a rolling-sum, then find the number of times the value increases.\n\n The solution is pretty short using iterators and itertools.\n\n**[Day 02: Dive!](./day_02)**\n\n[Problem statement](https://adventofcode.com/2021/day/2)\n\nThe goal of this problem is to execute a series of commands that move the submarine's position, and to determine the final location.\n\n\u003e - `forward X` increases the horizontal position by X units.\n\u003e - `down X` increases the depth by X units.\n\u003e - `up X` decreases the depth by X units.\n\nAgain, the solution is pretty straightforward (it's only day 2).\nI use iterators and `fold`.\n\n**[Day 03: Binary Diagnostic](./day_03)**\n\n[Problem statement](https://adventofcode.com/2021/day/3)\n\nThis problem was quite interesting. There is a series of binary numbers. In part one, we have to select the most common and least common bit at each position, and interpret the resulting binary number. In part two, we had to filter the numbers by the most common bit value in each position starting from the left until only one number remained. Selecting the most common or least common is quite easy, but I found it surprisingly challenging to select both simultaneously in a concise and DRY way. I think I did alright at this, but not amazing.\n\nI first did this in Python, but I wasn't terribly happing with the implementation and I wanted to do all 25 puzzles in Rust, so I rewrote it.\nI made heavy use of iterators and `partition` to split the list of numbers into two based on the bit value in a given position. I could then compare the lengths of the two vectors and keep the largest or smallest.\n\n**[Day 04: Giant Squid](./day_04)**\n\n[Problem statement](https://adventofcode.com/2021/day/4)\n\nThis problem was fun! We had to play bingo against a giant squid on many boards and had to calculate the score of the winning board.\n\nI solved this using Rust's [`ndarray`](https://docs.rs/ndarray/latest/ndarray/index.html), which was my first time using it.\n\n**[Day 05: Hydrothermal Venture](./day_05)**\n\n[Problem statement](https://adventofcode.com/2021/day/5)\n\nThis problem has many overlapping lines and we have to determine the number of locations where move than two lines cross.\n\nMy solution parses the lines into a custom struct, then uses ranges to extract the points from these lines. I use the points to increment a point in the grid using [`ndarray`](https://docs.rs/ndarray/latest/ndarray/index.html).\nI am not thrilled with my solution, but it's what I had time for.\n\n**[Day 06: Lanternfish](./day_06)**\n\n[Problem statement](https://adventofcode.com/2021/day/6)\n\nThis problem models the exponential growth of lanternfish.\n\nPart one can easily be brute-forced. Since the growth is exponential,\ndoing the naive implementation of storing the age of each fish is very slow for large inputs (like part 2).\nI did not notice the clever trick to store the amount of fish at each age rather than the age of each fish (I think this is how you're \"supposed\" to do it), so I simplified my reduction and slapped a `#[memoize]` on it, and it's reasonably fast.\n\n**[Day 07: The Treachery of Whales](./day_07)**\n\n[Problem statement](https://adventofcode.com/2021/day/7)\n\nIn this problem, you have to align a bunch of crabs to the same horizontal position while expending the least amount of energy.\n\nI first tried the mean/median, but noticed this wasn't right, so I brute-forced it. I later found from reading the subreddit ([/r/adventofcode](https://www.reddit.com/r/adventofcode)) that the median is related to the correct answer, and that a more efficient approach would be to start at the median and work away from it until the correct answer is found. Brute force is still very quick for this small input though.\n\n**[Day 08: Seven Segment Search](./day_08)**\n\n[Problem statement](https://adventofcode.com/2021/day/8)\n\nAs an electrical engineer and electronics enthusiast, I loved the appearance of seven-segment displays in this puzzle.\nIn this problem, the wires for the segments of the display get scrambled.\nBy observing the display cycle through all 10 digits, we work out which segments are now connected to which wires, and decode the scrambled screen.\n\nI solved this by drawing the display and figuring out the steps I would use to decipher a digit manually.\nI represented the segments as sets and used intersections and superset tests to deduce the mapping.\n\n**[Day 09: Smoke Basin](./day_09)**\n\n[Problem statement](https://adventofcode.com/2021/day/9)\n\nThis was a clustering problem.\nWe had to find all the local minima in a 2D grid (part one),\nthen find all the points in that basin (part two).\nThankfully, there is no overlap between basins;\nthey are all separated by a wall of the highest possible value (`9`).\n\nPart one is pretty easy. I just loop through all the points in the grid and check if they're lower than their 4 immediate neighbours.\nIn part two, I implemented a pretty standard breadth-first search\nto discover and entire basin,\nstopping when I reach a `9`.\n\n**[Day 10: Syntax Scoring](./day_10)**\n\n[Problem statement](https://adventofcode.com/2021/day/10)\n\nToday's puzzle was about detecting and then correcting invalid lines of opening and closing brackets of different types (`[(\u003c{}\u003e)]`).\n\nI solved this by pushing and popping the braces to a stack. If the end of the line is reached and the stack is not empty, then there are unclosed braces. If a closing brace is met that does not correspond to the top of the stack, then it is invalid.\n\n**[Day 11: Dumbo Octopus](./day_11)**\n\n[Problem statement](https://adventofcode.com/2021/day/11)\n\nIn this problem, there is a grid of flashing octopuses (I don't know how that works).\n\nThe implementation is pretty straightforward. The only tricky part is to maintain a set of flashed octopuses to ensure they don't flash twice.\n\n**[Day 12: Passage Pathing](./day_12)**\n\n[Problem statement](https://adventofcode.com/2021/day/12)\n\nIn this problem, we are given a list of edges, and have to count the total number of different paths through it. The catch is that some nodes can be visited multiple times, but others cannot.\n\nThe implementation is pretty basic recursive depth-first search.\n\n**[Day 13: Transparent Origami](./day_13)**\n\n[Problem statement](https://adventofcode.com/2021/day/13)\n\nThis problem was pretty cool. A secret message is uncovered by repeatedly folding the problem input.\n\nI used [`ndarray`](https://docs.rs/ndarray/latest/ndarray/index.html) and `BitOr` with a bit of housekeeping,\nwhich wasn't too bad.\n\n**[Day 14: Extended Polymerization](./day_14)**\n\n[Problem statement](https://adventofcode.com/2021/day/14)\n\nI really enjoyed solving this problem. Specifically, I enjoyed my naive implementation taking forever for part two and figuring out a clever algorithm to make it faster.\nThere is a list of characters, and certain pairs of adjacent characters get a third character insert between them.\n\nFor part one, it is possible to do string hacking and brute force it, but the size blows up exponentially.\nFor part two, instead of tracking the exact sequence of characters,\nI track the count of pairs of characters.\nOn each iteration, I replace a pair of characters with two pairs:\nthe left character with the character that's supposed to be inserted between them, and this inserted character with the right character.\n\nI also attempted to place on the leaderboard for this problem.\nI did part one in Python in 12 minutes 46 seconds with a score of 1448,\nwhich is not bad for me.\n\n**[Day 15: Chiton](./day_15)**\n\n[Problem statement](https://adventofcode.com/2021/day/15)\n\nThis is a shortest-path problem.\nThere is a 2D grid of numbers, the cell's number being its weight.\n\nThis one actually took me a very long time. I tried desperately to make it immutable and recursive. In the end, it was extremely slow. The reason is that I used a simple list for the queue and I would traverse it each time to find the minimum. I eventually switched to a mutate-in-loop pattern using a priority queue.\nThis is much faster, as getting the next node is O(1) and enqueueing a node is O(log(N)) (before, it was O(N) and O(1)).\nIt is not recursive and immutable, but maybe sometimes that is better.\nThe mutability is at least all contained into the Dijkstra function, and it's more recognizably Dijkstra this way.\nAlso, it was a neat opportunity to learn [`PriorityQueue`](https://docs.rs/priority-queue/latest/priority_queue/index.html) in Rust.\n\n**[Day 16: Packet Decoder](./day_16)**\n\n[Problem statement](https://adventofcode.com/2021/day/16)\n\nAgain, as an electrical engineer, I enjoyed this packet parsing problem.\nIt is kind of weird that the same protocol specifies the subpackets in number of packets and number of bits,\nbut I guess that's part of what makes it challenging.\n\nFor this one, I developed a recursive decent parser, and got to use [`bitvec`](https://docs.rs/bitvec/0.22.3/bitvec/) for the first time.\n\nThis was obviously a very hard problem for everyone, because my times of 51:34 and 58:56 scored 1341 and 1016.\nThese are my two best scores.\n\n**[Day 17: Trick Shot](./day_17)**\n\n[Problem statement](https://adventofcode.com/2021/day/17)\n\nDay 17 is a (not very accurate) physics simulation. You have to calculate the path traced by a projectile and determine the starting velocity that results in the highest apex.\n\nPart one, I was able to calculate analytically. The horizontal and vertical axes are independent,\nso I determined expression that let me quickly calculate both to reach the maximum height.\nPart two was tricky. You have to list all the possible starting velocities that reach the target (including negative vertical velocities, which cannot be calculated in the same way as before).\nI brute-forced part two. It is still very quick.\nI have seen some discussion on [/r/adventofcode](https://www.reddit.com/r/adventofcode) of people calculating in analytically, and I would have liked to do that, but I have not had the time.\n\n**[Day 18: Snailfish](./day_18)**\n\n[Problem statement](https://adventofcode.com/2021/day/18)\n\nThis one was quite tricky, but also pretty fun.\nIt introduces a strange binary-tree-esque numbering system.\nIf the tree becomes too tall, the leftmost leaf explodes.\nIf a leaf has a value greater than ten, it splits.\n\nI implemented a binary tree and made a recursive function to handle all the explosions and splits.\nI did something new (to me) and uses exceptions for things other than `Error`\nto short-circuit (with `?`) when an explosion or split is detected and to pass that value up the tree.\nAnother tricky thing is that explosions need to go up the tree on each side of the explosion, but go down the tree on the other side.\nFor example, the left value goes up until it is coming from a right child, then that value has to be added to rightmost grandchild of the left child.\n\n**[Day 19: Beacon Scanner](./day_19)**\n\n[Problem statement](https://adventofcode.com/2021/day/19)\n\nThis is a point-cloud-joining problem.\nThere are many scanners in 3D space at unknown positions and rotations with overlapping detection ranges.\nThe problem is to discover where all of the scanners are and to get all of the detected points into the same reference frame.\n\nI first solve this in Python in the most naive way possible (7 nested loops) to get my head around it.\nActually, I started with a simplified 2D version to make visualizations easier, then did the example from the problem statement.\nThis naive version is the epitome of brute-force.\nFirs, the zeroth scanner is selected as the reference.\nThen, while there are still disconnected scanners,\nit loops through all the other scanners,\nloops through all the zeroth scanner's points,\nloops through all the current scanner's points,\nand loops through all the possible rotations of the current scanner (4x4x4).\nAll of the points of the current scanner are rotated and translated by the difference between these two points.\nIf the intersection between the zeroth scanner's points and these transformed points contains more than 12 elements (from the problem statement),\nthen position of this scanner is known.\nThis first part about \"while there are disconnected scanners, loop through all scanners\" is required in case the first selected scanner does not intersect with the zeroth scanner.\n\nThis was surprisingly not slow as molasses. Granted, it took more than a few seconds, but I expected worse.\nStill, I wanted to optimize this.\nI wanted a way to find the connections between the scanners without all this looping.\nI was thinking hashing, but I'm not sure how this would work.\nI then found that the distances between every pair of points in a scanner can act as a kind of key\nthat is independent of rotation and translation.\nI could calculate the sets of all such distances,\nand compare the intersections of these sets between scanners to build a graph.\nIndeed, if two scanners share 66 distances, then we can safely assume that they are connected\n(it is still possible that they are not and that this is just a coincidence).\nI use breadth-first search to build a connection tree\nthat represents the order in which to visit the scanners,\nwhere each scanner appears only once.\nThis also allows me to verify that the graph is connected.\n\nThe final step is to traverse this tree and transform all the points (and the scanner origins themselves) to the same reference.\nThis works bottom-up, transforming each scanner to its parent's perspective and merging all siblings until the root.\n\nThis is much faster, as we can directly know the connected scanners and even the matching points to use for translation.\nI still brute-force the rotations, but it now takes a small fraction of the time.\n\n**[Day 20: Trench Map](./day_20)**\n\n[Problem statement](https://adventofcode.com/2021/day/20)\n\nThis problem defines a binary image and an algorithm (bit sequence).\nYou must repeatedly convolve a 3x3 window over the image,\ninterpret the 9 bits from the window as an index to look up a value in the algorithm,\nand replace the window with that value.\nThis has the effect of shrinking the image by two (you also have to pad by 4 beforehand for edge effects).\nOne wrench thrown in the mix is that the image is \"infinite\",\nand while the example in the problem statement has a zero in the zeroth position of the algorithm,\nthe real input has a one.\nWhen the zeroth bit in the algorithm is a one,\nthe infinite grid is flipped on every iteration\n(every 3x3 cell of all zeroes becomes a one).\nThere are always an even number of iterations,\nso you will always have finitely-many bright pixels at the end.\nYou just have to be careful about the value with which you are padding.\n\nThis was surprisingly easy in Rust with [`ndarray`](https://docs.rs/ndarray/latest/ndarray/index.html),\neven though I had to copy-paste [a pad function from GitHub](https://github.com/rust-ndarray/ndarray/issues/823).\nIterating through 3x3 windows is built into `ndarray`, and then it's just a matter of converting 9 bits to an integer and indexing the algorithm.\n\n**[Day 21: Dirac Dice](./day_21)**\n\n[Problem statement](https://adventofcode.com/2021/day/21)\n\nThis problem is about a dice game with two players moving around in a circle by the sum of three dice rolls.\nEach turn, a player's score is incremented by their current position.\nIn part one, the die is deterministic and yields every positive integer in order.\nIn part two, the die is quantum and yields `1`, `2`, and `3` simultaneously.\n\nPart one was straightforward, but part two was more complicated.\nMy implementation is still kind of brute-force,\nbut by accounting for duplicate moves, it is doable.\nThe sum of 3 3-sided dice has duplicates\n(for example, there is only 1 way to roll 3, but 3 ways to roll 4 and 7 ways to roll 6),\nwhich limits the possibilities to 9.\nThis is required for the algorithm to terminate in a reasonable amount of time.\nThe alternative would be to consider all possible values of the three dice,\nwhich is 27 cases.\n\n**[Day 22: Reactor Reboot](./day_22)**\n\n[Problem statement](https://adventofcode.com/2021/day/22)\n\nThis problem has many overlapping cuboids\nwhich represent operations on the contained voxels.\nSome cuboids represent \"on\" operations, while the rest represent \"off\" operations.\nAll the voxels default to \"off\", can be turned \"on\" if they find themselves inside any \"on\" operation,\nand are turned \"off\" if they are inside any \"off\" cuboid.\nThe operations are processed in order,\nso a voxels value is determined by the latest operation it finds itself inside.\n\nIn part one, it is possible to brute-force.\nThe space is small enough that you can just maintain a hashmap of each voxel,\nloop through the operations, and turn \"on\" or \"off\" all the voxels inside each new cuboid.\n\nIn part two, the search space is far too big to flip voxels individually.\nI have seen people solve this using a tree,\nbut opted to represent the cuboids as ranges\nand perform boolean operations on them.\nAt the end, the total volume of all the cuboids is equivalent to the total number of \"on\" voxels.\nThe trick is to maintain a set of cuboids that never overlap.\nI only store \"on\" cuboids,\nand if the cuboid currently being processed is \"on\",\nI subtract it from all the existing cuboids, then add it to the collection.\nIf the incoming cuboid is \"off\",\nI simply subtract it from all the existing cuboids (no need to keep it).\nThis way, I will have a set of non-intersecting \"on\" cuboids.\nI have to ensure that no cuboids intersect because a voxel can only be \"on\" or \"off\".\nSetting a voxel \"on\" twice does not count for two.\nBy \"subtract a cuboid from a cuboid\", I mean replace the cuboid\nwith a set of cuboids representing the boolean difference between the two cuboids.\nIn other words, a set of cuboids whose union contains a voxel if and only if that voxel was inside the first cuboid (the cuboid being subtracted from) and not inside the second cuboid (the cuboid doing the subtracting; the cuboid currently being processed).\n\nI implemented a `struct Cuboid` with a `Sub` trait for this.\nLet's call the cuboid being subtracted from cuboid A and the cuboid being processed cuboid B.\nI first start with the X axis, and replace cuboid A with 3:\na cuboid with the same size in Y and Z as A but that goes from A's left face (the face parallel to YZ with the smallest value of X) to B's left face, a cuboid from B's left face to B's right face, and a cuboid from B's right face to A's right face.\nThen, I do Y.\nI replace this middle cuboid (B's left face to B's right face)\nwith a new 3 following the same procedure,\nbut this time the new cuboids are only span B's range in X, not A's.\nThen finally, I do the same for Z using the middle cuboid from Y\nand discard the middle cuboid from Z.\nThe result is 6 cuboids that do not overlap that represent the boolean operation A - B.\nIt is possible (even likely) that A does not totally surround B.\nTherefore, I filter out cuboids with negative volumes.\n\nFor example, in the left image below,\nsubtracting B from A will result in the 6 non-intersecting cuboids\n\"left\", \"right\", \"front\", \"back\", \"top\", and \"bottom\".\n![3D representation of boolean subtraction of 2 cuboids](./day_22/images/combined.png)\n\nThis problem took me a very long time.\nFirst of all, the cuboids do not follow intuition.\nTheir dimensions represent ranges of integer values (the voxels they encompass).\nIf a cuboid starts at 0 and ends at 10, you may expect it to be 10 units long\n(like on a real world ruler; the \"10\" mark is 10 units from the \"0\" mark),\nbut it actually contains 11 units including the zeroth and the tenth.\nThis means I had to do a lot of `+1` and `-1` in the boolean difference code,\nwhich is prime real estate for off-by-one errors.\nFurthermore, for the longest time, I had a bug because I did not realize that two cuboids could intersect\nwhile none of their corners are inside the other.\nThis is obvious in hindsight, but took me a very long time to debug.\nIn the end, I added a function to check if two rectangles are intersecting\n(which is much easier)\nand used the fact that two cuboids are intersecting if and only if all three of their projections in X, Y, and Z are intersecting.\n\n**[Day 23: Amphipod](./day_23)**\n\n[Problem statement](https://adventofcode.com/2021/day/23)\n\nThis is an optimization problem with the goal of moving amphipods of four different types to their assigned rooms.\n\nThe example from the problem statement is the following, where `.` is an empty space, `#` is a wall, and `A`-`D` are the four types of amphipod:\n```\n#############           #############\n#...........#           #...........#\n###B#C#B#D###  becomes  ###A#B#C#D###\n  #A#D#C#A#               #A#B#C#D#\n  #########               #########\n```\nThe format is always the same as this example, only the initial positions of the amphipods changes.\n\n**TLDR:** I used Dijkstra.\n\nFull story:\n\nI think this problem took me the longest. Either this or day 24.\nI solved part 1 in my text editor by cutting amphipods, counting keystrokes, and pasting them somewhere else.\nHowever, I did not trust myself to find the minimum cost doing this for part two.\n\nI first implemented this using a modification of Dijkstra that moved between adjacent states.\nThis implementation was pretty naive: it considers every move of an amphipod to an adjacent empty space for the next move.\nIt should in theory find the correct solution eventually, though.\nBy selecting the future state with the least cost continuously until a solution is found,\nit is guaranteed that if any solution is found, it will be the cheapest.\nThis implementation took 6 minutes for part one,\nbut when I let it run part two overnight,\nit ate all my RAM and crashed.\n\nI then reread the problem statement and found that I skipped over some important details:\n\n\u003e - Amphipods will never stop on the space immediately outside any room. …\n\u003e - Amphipods will never move from the hallway into a room unless that room is their destination room. …\n\u003e - Once an amphipod stops moving in the hallway, it will stay in that spot until it can move into a room. …\n\nWhy my solution technically finds the right answer, it is considering way too many moves (actually, there are even more \"invalid\" or \"stupid\" moves than these).\nHacking the first condition into my existing code was pretty easy;\nIf I detect that an amphipod is directly outside a room, I reject any next state that does not correct this.\nThe next constraint was also doable.\nHowever, the final criterion was very challenging to implement (into my terrible code).\nI added some state to track if an amphipod started moving in the hallway and would reject any moves that do not take this one home.\nThis code was horrible to look at and very buggy. What's more, it still took forever! It was clear I was going about this the wrong way.\n\nTherefore, I totally changed my strategy.\nFirst, I check if any amphipod can move to its room. If it has a clear path to its room, and its room contains no amphipods of another type, I force the next move to take this amphipod home.\nSecond, if there are no amphipods that can go to their room, I enqueue all the possible moves that take an amphipod into the hallway.\nUnlike before, I do this intelligently: I only consider valid waiting spots in the hallway (not directly outside a room), and I don't consider moving amphipods that are already in the right room and that aren't blocking another type.\n\nOne good thing about the first implementation is that I didn't have to do any fancy calculations to determine if a spot was reachable from another spot. I only consider adjacent moves to empty spaces. Furthermore, I don't have to calculate the cost of a given jump. I only move one spot at a time, so I increment the cost by 1 (times the cost of the current amphipod type).\nFor this new algorithm, I could not rely on this. I made a function to return `Some(cost)` of a jump if reachable, and `None` if it's unreachable (in which case, I know not to enqueue the move).\nI could have used some kind of search for this, but since everything is straight lines and there is only one path (that does not visit the same node twice) from a location to another, I calculate three (into the hallway, over, and down) ranges of points, loop through them, and check that they are all empty.\n\nThis did the trick. By limit the allowable moves like this,\nI can solve both parts in a fraction of a second.\nIt is also much easier to reason about than the terrible code to detect these disallowed states and reject future moves.\n\n**[Day 24: Arithmetic Logic Unit](./day_24)**\n\n[Problem statement](https://adventofcode.com/2021/day/24)\n\nThis problem was something else.\nIt was probably the one that was the least obvious of how to solve after the first read.\nThe problem defines a language reminiscent of assembly, but with only six basic instructions (talk about RISK!).\nThe problem statement only defines the language and that we are looking for the largest number that satisfies the program given as input (satisfies meaning that register z=0 when the program terminates).\n\nThe first thing I did was examine the input and work out what it was doing using pen and paper.\nI won't get into that because there are [better write-ups out there](https://github.com/dphilipson/advent-of-code-2021/blob/master/src/days/day24.rs).\nOnce I played around with it on paper (and snooped around the internet a bit),\nI eventually figured out how to solve this.\nThe program works like a stack machine, but when calculating the largest valid input,\nyou don't know ahead of time what to push onto the stack.\nThe pushed number depends on the current digit, and the valid range for this digit depends on some instructions from the digit that pops this value. That means guessing that the current digit is larger than its valid range results in some subsequent digit having to be \u003e9 in order for the z register to be zero at the end.\nTherefore, I made a recursive function that matches pushes to their corresponding pops.\nBy processing both simultaneously, we have all the information we need to directly calculate the maximum (or minimum, for part two) value of the first value (most significant) while respecting the valid range of the second value.\nThe function performs this matching of pushes and pops\nby reading one push instruction from the input (the first instruction is always a push, and the function ensures that all pushes and pops are removed together),\nthen calling itself recursively.\nThis has the effect of processing any pairs of instructions that come between this push instruction that we just read and its matching pop.\nIf the next operation is a pop, it returns immediately.\nAt this point, the corresponding pop instruction is taken off the input,\nand the digit for each is calculated.\nFinally, the function calls itself again to handle any remaining instructions,\nand all of these are chained together (the digit of the push instruction, all the digits in between corresponding to the first recursive call, the digit of the pop instruction, and all the digits from the second recursive call).\nFor a problem that is so slow to brute force (9\u003csup\u003e14\u003c/sup\u003e possibilities!),\nthis solution is incredibly quick at \u003c0.01s.\n\n**[Day 25: Sea Cucumber](./day_25)**\n\n[Problem statement](https://adventofcode.com/2021/day/25)\n\nEnding on an easy one!\nThis is the Biham-Middleton-Levine Traffic Model problem.\nThere are sea cucumbers in a 2D grid with PacMan physics (leaving from the right means reappearing on the left).\nSome cucumbers can only move down while others can only move right.\nThe challenge is to determine the first step where no sea cucumbers move (where they are all blocked).\n\nThis was pretty easy to solve.\nI don't know any way to do it other than just to simulate it.\nI used a recursive function to handle the repeated steps,\nand stored the position of the cukes in a hashmap (I got sick of `ndarray`).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarcelrobitaille%2F2021-advent-of-code","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmarcelrobitaille%2F2021-advent-of-code","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarcelrobitaille%2F2021-advent-of-code/lists"}