So despite the lack of updates in this thread as of day 12, I’ve actually completed 6 of the days on the calendar. Here’s a quick rundown of the puzzles so far:
I had intended on doing some work prior to December to make this year go a bit smoother. Inevitably, none of that happened. I was planning on the following:
Updating the CI pipeline at gitlab to be more flexible so that it was ready to handle the 2022 puzzles on day 1. I ended up doing this mostly on day 1 instead of earlier, but it’s pretty much done. Whenever I push code to the remote repo, it automatically runs my unit tests and then runs the main program for years 2021 and 2022. I’ll need to go back and implement earlier years at some point, but it should be trivial to set it up for 2023 before next year.
Creating a set of yeoman templates to automate some of the repetitive boilerplate tasks such as setting up the new folder for each year as well as setting up files for each day’s puzzles (description, tests, input, solution). I started this last year after the calendar was over, but stalled out over the course of the year. It is currently not done, but it sure would be handy if it was…
Once I finally got around to starting the first day’s puzzles, it was a pretty straightforward path to a solution.
Python itertools makes it easy to group the input data by elf (whenever there’s a blank line.) Then it’s just a matter of getting the max (part 1) or the sum of the top 3 (part 2, using islice.)
This set of puzzles game me more trouble that it should have (particularly part 2.) Most of my problems were self-inflicted though, which is kind of a running theme. The key struggle I was having was that my logic was oriented from the perspective of the intended player, whereas the input data is more oriented around the opponent. This led to me essentially doing the logic backwards which meant I was getting essentially the opposite result to what was expected. A strong indicator of whether I had more issues with one puzzle over another is the number of test cases there are. Most days only have two, one for each part. This day’s puzzles have eight.
This was a bit tricky. I ended up solving part 1 by splitting the input into two sets, and then finding the intersection between both. Then it’s a simple matter of getting the sum of the calculated priority. For part 2, it was about setting up the groups properly, and then getting the intersection between three sets instead.
My initial thought on this was to generate lists based on the number ranges, and then test whether the lists overlapped or one was completely contained in the other. That probably would have worked, but since the ranges were always contiguous, it’s not necessary to go through the trouble of creating collections when a few simple if statements will work. The only real difference between part 1 and part 2 ends up being whether there’s an and or an *or between the if statements.
I had two main problems solving the puzzles for Day 5. The first was learning how to implement a stack in python. I ended up using a list of deques to represent the columns of the cargo hold, and then used pop() and append() to move items around from stack to stack. For part 2, I just made the crane itself a stack so that it could collect the items from one stack and then place them back in the same order. I actually ended up creating a base class that contained all of the functionality except for the move method, and then created two subclasses that implemented that method in different ways. I’m pretty proud of that. The other issue I ran into, which had me stuck for around a day, was another case of a self-inflicted problem. The way that I implement a day’s solution is to create a class to represent the thing in the story that’s relevant for that day. In this case, I careted a CrateMover class to represent both the cargo hold as well as the crane to move crates around. When I solve part 1 of a day, I don’t discard my solution and rewrite it for day 2, I either extend it to cover the additional cases, or refactor it so that I end up with something that can solve both parts with (hopefully) minimal additional code. As part of that, I tend to not just reuse the input, but specifically the input object(s). This is generally fine, because I’m usually writing code that doesn’t have any side effects, but in this particular case I failed to do so. Further confusing my diagnosis was the fact that I had apparently not run both test cases in sequence, which would have displayed the issue. Instead, I had wast looked like code that would pass my test cases, but then fail on the actual input. This is a scenario I’ve run into before, and it usually means that I need to add some more test cases to cover scenarios that are in the input data but not the sample data. I spent quite a bit of time refactoring my code to make it more testable so that I could test smaller units, only to discover that I was calling reverse() on the cargo data in order to make it easier to load into the stack. There’s nothing inherently wrong with that, except that it modifies the collection in place rather than returning a new collection like most other operations. This meant that if I ran my part 1 solution or my part 2 solution independently, it would work correctly, but as soon as I ran both of them in sequence the input data for part 2 would be upside down.
This was probably the fastest one for me to complete so far. I ended up using a collections.defatultdict of ints to count the number of each letter in the substring (either 4 or 14 long), and then returned the position when the max count was 1. However, when I was reviewing my solution for day 3 above, I realized I could create a simpler solution using a set and then validating whether the set was the length I was looking for.
On to Day 7!