Advent of Code 2024, Week 1
Advent of Code
Advent of Code is a yearly Advent calendar of programming puzzles that's run since 2015, with 25 two-part problems each year, each part rewarding one "star". For 2024, after completing every previous year, and even making the global leaderboard last year, I'm making a weekly blog post about that week's problems, containing my thoughts on them and how I solved them; these, of course, will contain spoilers. For the code, see:
2024 ยท personal library.
Day 1: Historian Hysteria (puzzle, code)
Solve Times: 02:19 / #445, 03:40 / #345
This problem is pretty standard, difficulty-wise, for a Day 1 problem, in contrast to the unusually difficult problem from last year's Day 1. This problem presents two lists of integers, which, in part 1, need to be sorted and compared, number for number, to calculate the sum of differences between each pair of numbers. In part 2, the challenge is instead to take each number in one list, and multiply it by the number of appearances it has in the other list.
My solution reproduced the simplicity of the problem itself in its structure. For part 1, all I needed to do was sort each list, and then iterate through the length of the list to find the difference between each pair of numbers, then sum them. For part 2, I used Python's built-in collections.Counter()
data structure, a very convenient version of a dictionary that can convert an iterable (like a list) to a dictionary of how many times each element appears in the list. With that, it's simple to solve the problem with another loop over the first list, multiplying by the Counter
's value and adding it to a running total.
Day 2: Red-Nosed Reports (puzzle, code)
Solve Times: 04:00 / #249, 06:45 / #311
This puzzle is another fairly simple challenge on par with what you'd expect from the first few days of AoC. In this problem, there's a list of lists of integers, and the goal is to count the number that are "safe": from number to number, the changes either all fall in the range [1, 3] or the range [-1, -3]. The second part is a simple expansion on the problem, which asks how many can be made "safe" by removing up to one chosen number from the list.
To solve it, I used a for loop over each of the lists in the input, counting the number that are safe. To determine if a list is safe, I first made a second list of the differences between consecutive elements, and then used Python's all()
to check the two aspects of the condition: having the same sign, and having absolute values between 1 and 3. Although I didn't realize this at the time, this could be simplified further to all(1 <= i <= 3 for i in i2) or all(-1 >= i >= -3 for i in i2)
. For part 2, I moved the is_safe
check from the main loop to a separate function, and then used Python's list slicing features to check each index in the list, testing if removing it made the list safe: is_safe(l2[:idx] + l2[idx + 1:])
, and adding it to the running count of safe lists if it passed.
Day 3: Mull It Over (puzzle, code)
Solve Times: 08:38 / #3432, 10:01 / #1082
This problem involves searching text for particular substrings, which isn't one of my strong suits, and I also lost time due to not realizing that the input was split across multiple lines, and not just wrapping in my editor. The input in this problem is a stream of random characters, some of which are of the form mul(x,y)
, and the goal is to extract those mul
s, multiply the two numbers contained in each, and sum them all together. In the second part, two new substrings are added, do()
and don't()
, which toggle whether mul
s should be ignored or not.
My approach to this problem was to use a lot of clunky substring logic to find each mul
in the character stream, confirm it has the correct format, and extract the digits if it does. The much better way to accomplish this would've been to use a regex to match the right strings, but I couldn't remember how to use the Python regex library off the top of my head, and so hand-rolled the logic instead. I iterated through each index, checking for the string "mul("
beginning at that index, and if so, tried to match a mul
instruction from there and incorporate it into the running total. For the second part, I added a boolean flag for whether to count mul
s or not, and checked for do()
and don't()
at each index as well, in order to toggle the flag as appropriate.
Day 4: Ceres Search (puzzle, code)
Solve Times: 05:47 / #696, 12:13 / #596
This puzzle is somewhat similar to the previous one, in that both involve searching for strings of text, with the major difference that this one is two dimensional. The input given is a large grid of the letters X, M, A, and S, and the goal is to first search for all occurrences of the string "XMAS", in all eight possible directions, and count them. The second part of the puzzle instead asks for the number of occurrences of "X-MAS", meaning an X shape formed by two of the string "MAS" in any possible direction, which is a fairly similar challenge to the first part.
Although I did decently well on this problem, my solution is not pretty. I might've benefited from using my lines library to streamline it, but didn't remember it enough to do so in the rush of trying to code quickly. The code I actually wrote uses eight different if statements to check the eight directions for part 1, relying on a try-except to deal with out of bounds issues, and with a bounds check I had to add when realizing that Python's support for negative indices led to false positives. Part 2's solution is similar, although with only 4 branches needed instead of 8.