This year, several members of my team joined me in enjoying the Advent of Code. I was expecting to have a little fun writing a little code, but ended up being surprised at just how much I learned during the process and where the experience will take me as we enter 2023.
What is the Advent of Code, Anyhow?
If you’re not familiar with the Advent of Code, it’s an annual experience where a series of programming puzzles are presented daily to participants.
The puzzles can be completed in any programming language, and there generally is a cooperative spirit around solving the puzzles. Each programmer gets individualized input data, and the puzzle generally renders down to a single value that can be entered as an answer. This allows participants to validate their work while not requiring the server infrastructure needed to evaluate both the answer and run the participants’ code.
Puzzles generally have two parts, and the second part is accessed upon completing the first part. Days which are skipped or incomplete do not prohibit participation in future days.
There is a competitive leaderboard, but it has 100 entries per day, and the rest are not publicized. Most participants join in for the joy of working on difficult problems, not to compete.
Nuts and Bolts: How I Did It
On the first day of Advent of Code this year, I started a project in Xcode containing 25 individual command-line programs, and then simply worked on one per day.
I normally read the problem as it was released (midnight EST, 11pm in my time zone). Then I’d either spend an hour setting up data structures & input parsing, or start thinking about how I might do the same. I’d then wake up the next morning ready to tackle the project before heading into the office for the day.
By setting a repeatable pattern early in the game, I was able to avoid re-inventing the wheel of an overall structure on a daily basis. Some participants prefer to work from external input files (which is the norm), but I encoded the data as a multi-line string so I didn’t need to write file accessing code. It’s not so much that it’s difficult to do that, but I didn’t implement that on day one, so it seemed impractical to go in that direction on subsequent days.
Once Advent of Code is underway, participants should (in my opinion) not experiment with the infrastructure they’ve chosen for solving the problems (some slight variations from that are noted below). There’s more than enough work to do in building out solutions without needlessly taking on additional technical debt. That being said, I do have some plans for my participation next year that are likely to change my approach.
Falling Back in Love With Swift
During 2022, I spent most of my time thinking in two programming languages: Dart and C. Sprinkle in a little Rust, but primarily I was living in Dart and C.
I had honestly forgotten how much I enjoy the Swift language.
While the things I love about the Swift language are hardly unique, a few of the features I re-engaged with during advent of code were:
- Strong typing of data: I occasionally used plain tuples to work with data temporarily, but by and large I used strongly typed data structures.
- Ease of building multi-file projects: While makefiles aren’t terribly difficult in C, and most modern languages handle multi-file projects with ease, I enjoyed how little I thought about these things in Swift and Xcode.
- Value and reference types being orthogonal to one another: there were several times where I took advantage of the capabilities in using one or the other type. While my general philosophy in writing Swift code is “use value types until the last possible moment you can’t use them any longer”, I had several occasions to take this into account on a smaller scale than I normally work at.
Swift’s New RegexBuilder
I also had a chance to engage with a couple of new features in Swift. One feature released earlier this year was RegexBuilder. This is a language component that lets us move past terse regular expressions into more easily reasoned-about expressions. There’s an apocryphal saying in programming, “I had a problem to solve. I chose to use regular expressions, and now I have two problems to solve”. I’ve mostly avoided regular expressions whenever possible, because the complexity involved in getting them built correctly goes beyond what I generally want to invite into my life.
However, the new RegexBuilder genuinely makes creating and working with regular expressions a joyful, if not care-free, experience. The new syntax is validated while editing, meaning that you don’t have to run the project multiple times through a debugger you’re satisfied with the outcome. Most problems are identified before your code is ever built.
As an example, one of the input file formats this year had the following structure:
(but significantly longer)
This data could certainly be imported in many ways, but I welcomed the ability to engage with the new RegexBuilder for this. Writing a traditional regular expression for this could be tricky, but the code to interpret this using RegexBuilder is fairly straightforward:
You might even notice that this has the benefit of being composable, where an element can exist separate from the overall structure. Bob Martin discusses this approach in this article, though the Swift implementation really is a full-fledged Domain Specific Language (DSL) instead of being a wrapper around traditional regular expression code. Using a DSL like this allows for not only expressiveness, but additionally ensures that the process can be handled at compile-time rather than runtime.
The Algorithms Package
One other tool I found valuable when approaching the problem was the Swift Algorithms package. This is a group of algorithms that can be run against various Swift Collections. These have suitably generic implementations, and as a result are applicable to a wide variety of problems.
One of the algorithms I used most were the Chunked algorithm, which breaks a collection into a set of lazily-evaluated sub-collections, given a particular size. While it’s pretty easy to chunk collections into pairs using the Swift general Collection protocol, chunks of arbitrary size, or on a chunk delimiter, are less simple. By simply using the Swift Chunked algorithm, I was able to arrange my data quickly and with relatively little effort.
Another member of the Swift Algorithms package that I made use of was the Windows, which provides slices of the collection that are overlapping groups. This isn’t a particularly difficult problem to solve, but not having to solve it at all made my life simpler.
I always advise my junior programmers to look in this algorithms collection when they have an odd transform that they need to run across elements of a collection, and it didn’t let me down for Advent of Code.
A Software Engineering Approach
More than anything, my approaches in solving the puzzles in Advent of Code used a Software Engineering approach. This meant that every time I solved a problem, I was writing significantly more code with longer variable names than someone going for a purely quick solution.
However, this also meant that I could re-use portions of my solutions across multiple projects where applicable, and that my solutions were understandable by other developers – even developers who don’t work in Swift.
In any project, starting by identifying data structures and then processing flows is essential to having a solution that can be understood and used in the long term. I am a believer in the concept that how one approaches any problem is the same as how one approaches their most important problems; ensuring that I used good programming practices even in these trivial, short-term projects showed to myself and to my coworkers the value of keeping true to a Software Engineering approach.
Swift Playgrounds vs Command Line Apps
Working with other iOS developers on solutions for the Advent of Code revealed a fairly startling observation: Xcode’s playgrounds can be slow.
Swift Playgrounds are great for experimenting in code, but don’t really allow for breakpoints the way I’d like. They’re also more complicated to bring existing packages into than a simple command-line application.
One even larger consideration is that an iOS playground runs significantly slower than a macOS playground. This is a choice that one makes when initially creating the playground, and cannot be changed once the playground has been created.
Command-line apps are harder to build upon (if one wanted to create a SwiftUI representation, for example), but are always fast and the debugger works properly. Additionally, it can be built and run from the console, which means that these apps can execute even faster than debug versions. Finally, one can use Instruments to find where the program is spending most of its time; while this is rarely an issue, when you need this capability you really need this capability.
Quiet, Fan-Free Computing
One wonderful thing about participating with my M1 macs this year is that these devices run cool and quiet. Even on days when I used a brute-force approach to solving a problem, with runtimes in multiple minutes, my laptop never got warm nor it start making noise from the fan (and my M1 Air doesn’t even have a fan). I rarely notice things like this, but when I do something where I know my computer would otherwise been howling, it’s a joyful experience.
One thing I learned during Advent of Code 2022 is that I do not fully grok graph theory as far as implementing search algorithms and the like. Graph theory is a way of approaching a data structure as a set of nodes connected to one another via edges. While I was generally able to set up my data in these structures, I did not succeed in solving problems using edge traversal through the graph.
By showing me this gap in my knowledge, I’m now able to set aside time for personal growth next year by digging more deeply into graphs.
If you’re working through Advent of Code and have trouble with a puzzle, or group of puzzles, the best thing I can recommend is to try and understand the underlying problem, and then set up a learning path that will address the knowledge gap.
You can even think of it as a month-long skills check, giving a concrete example of how well you know various things. I promise you won’t be disappointed in the results if you allow yourself to critically examine the quality of your various solutions.
Libraries for the Future
Even though I took a Software Engineering approach to Advent of Code this year, I didn’t fully embrace reusability across days. I will change this as I approach Advent of Code in 2023, building up a fresh library of data structures and techniques that will help me solve problems more effectively but even more importantly will allow me to implement solutions with little overlapped work.
Building a Local Community Around AoC
I mentioned that I worked with other developers in my organization on this project; we created a slack channel to discuss the problems. We posted solutions after our lunch hour (so as to encourage each other to work independently), but did enjoy talking about the general shape of our solutions before posting them.
What was fascinating to me was that even when two developers worked in the same language, the solutions would end up being wildly different. It’s a testament to the quality of the puzzles that there isn’t a one-size-fits-all solution, and also that they stood up to deep scrutiny.
If you’re intrigued by this and would like to participate next year (or even just right now), there are a few fundamental skills you want to make sure you have under control.
In addition to just plain knowing the language you’re going to use, you should know:
- how to use a debugger: using `print` statements to evaluate program flow and execution is not a substitute for being able to inspect the program as it runs to understand what’s happening.
- how to profile your program: you should be able to examine how quickly your program is running without having to rely on a stopwatch or any such foolishness. While most Advent of Code puzzles can take seconds at most to run, it’s also very easy to use a sub-optimal solution. Knowing what parts of your program is essential to move into a “good” execution time.
It can be fun to use Advent of Code to explore a new language or toolchain, but in my opinion that’s an approach better suited for your second or subsequent attempt at participating. During your first year, you’ll be sufficiently challenged just trying to keep up with the puzzle part of the puzzles.
What’s Coming in 2023?
In 2023, we’re going to continue our Advent of Code (now “Programming Puzzles”) slack channel, with weekly rather than daily challenges. This will help many of our team members hone their skills and try out different things in a way that can build them up to next year’s Advent of Code.
I love helping my teammates learn their craft more deeply, and so Advent of Code this year was a great way for me to help work alongside them and learn from them.