Advent of Code 2022 starts in 8 days!
I've been participating in AoC since 2017. My highlight was in 2020 when I solved all the puzzles and received 50/50 stars ⭐️ (though that didn't happen in December 2020 but only much later in 2021).
Since 2017, I've learned a lot. I'm sharing some tips/notes to self for the upcoming AoC 2022.
Here are eight tips for AoC 2022:
Let's start with the most boring yet important tip.
Remember to keep it fun! Don't stress if you can't solve a puzzle in a day. Don't let it ruin your Christmas! Christmas time can be stressful enough even without AoC.
Also, don't stress about the leaderboard. People who are on the leaderboard have practiced these kinds of coding puzzles for years.
First "real" coding tip:
Using the right data structure might make solving a challenging puzzle a piece of cake.
Let's take an example:
[0, 0]
is 1
0
What would be a proper data structure for this?
At first, by intuition, you might think about vectors in a vector (like a 2D array) for an XY-coordinate system. But things get tricky due to the other requirements, i.e., infinite size.
A perfect data structure for this is a map from XY-points to values.
;; Initial value for 0,0 is 1 (def grid {[0 0] 1}) ;; Fast random access and an initial value for everything else than 0,0 is 0 (get grid [0 0] 0) ;; #=> 1 (get grid [12 34] 0) ;; #=> 0 (get grid [-56 78] 0) ;; #=> 0 ;; Infinite size (def new-grid (assoc [100 100] grid 100)) ;; #=> {[0 0] 1, [100 100] 100}
Another useful data structure is a set
. You'll find plenty of
opportunities to use sets in AoC puzzles.
memoize
AoC puzzles have two parts.
A typical puzzle pattern is this: The first part is easy to solve. You can easily craft a naive, brute-force implementation that does the trick.
The second part is such that you need to change the first part only a bit. It looks effortless until you run it and notice that it takes forever to finish.
For example, in the first part, you need to define a step function (a function that takes as an input the output of the previous step function execution) and run the step 100 times for a given input. Running the step function 100 times takes couple of seconds.
In the second part, you need to run it 1 000 000 times. You notice that this will take hours to complete.
How to make your solution run faster for the second part?
One helpful strategy is memoization: Make your function pure (as we default in Clojure) and throw some memoize in the mix. That itself might make the function fast enough.
Not all solutions can be memoized. In those cases, you need to figure out something else.
One thing you can look for is to find a repeating pattern.
Say you have a step function, and you've been asked what the result of running that step function 549027529283424023592234980235235 times (or some other ridiculously high number) is. You can't just repeatedly run the function that many times, or it will take years to execute.
It's possible that you can find a repeating pattern where the output of, say, the 12514th execution is the same as the output of the first run. You can now calculate 549027529283424023592234980235235 modulo 12514 and figure out that it is enough to run the step function only 8633 times to get the result.
Each AoC puzzle will provide examples that are way smaller than the actual puzzle input.
Make sure you test your implementation with _all_ of the provided examples before even trying to run your solution with the actual input.
resources/
I organized my AoC project so that I put the inputs and examples in
the resources/
directory and .gitignore
it.
I think it's helpful to have the examples also in
the resources/
so they are easy to use from there.
Also, it helps if you have a
helper function to read the file lines from the resources/
.
Because AoC puzzles have two parts, it's a good idea to keep your solution of the first part modular and split it into small functions that you can replace for the second part.
I've noticed that I often use threading macro ->
. A typical
puzzle solution might look something like this:
(-> (read-input-from-resources "name-of-the-file") (parse-input) (calculate-one-thing) (calculate-another-thing) (calculate-the-final-checksum))
Many times the requirements for part 2 are such that you need to change only one of these functions. A typical solution for part 2 looks something like this:
(-> (read-input-from-resources "name-of-the-file") (parse-input) (calculate-one-thing) (calculate-another-thing-a-bit-differently-for-part-2) (calculate-the-final-checksum))
Final yet crucial tip (repeating the tip #1 a bit)
Don't feel bad if you can't solve a puzzle. It doesn't mean that you're a terrible coder. More likely, it means that you need to practice solving puzzles.
When we do our work as software engineers, we aren't solving puzzles on a daily basis. We are solving real business problems. Solving puzzles is very different from solving business problems and requires different skills. And you'll learn those skills by solving more puzzles.
I've witnessed this myself. When I started doing AoC, I managed to do only a few days and then got stuck. The puzzles just felt way too tricky.
And this felt bad. I thought I knew how to code!
But in 2020, when I completed all the puzzles, many of the puzzles' problems felt familiar and easy. The same patterns repeat, year after year, with only slight changes. I recognized how to solve a problem because I'd seen that same problem in earlier years.
So, I'm repeating myself, but this is important: Keep it fun, and don't feel bad if you can't solve all of them.