Advent of Code 2023 Day 2 Highlights
Day 2 was more of a warmup kind of challenge, and funnily enough was much faster to solve for me than the day 1’s task. I used Elixir with Livebook this time too, primarily because I didn’t feel like there will be much difference in writing this in any of my preferred languages, except for Rust perhaps. Another thing that was pretty typical about this day is that first 7-8 days of AoC are dominated by parsing the input. After that the algorithmic part becomes more complex, but here it was a usual situation of more code needed to parse the task than to solve it :)
Anyhow, here’s my solution’s code.
Talking about parsing, there are usually 2 ways people approach it for those tasks:
-
String.split
and similar tricks up to a full blown recursive-descent - Regular expressions
I don’t have a strong preference either way, usually. This particular input was easy to parse with a combination
of String.split
and pattern matching, so that’s what I went with.
I also find a bit of domain modelling very helpful when working on these tasks. I just modelled the input as two
structs: Game
(with the game id
and a list of rounds
) and Round
with red
, green
, and blue
cube counts.
The most interesting part wrt programming technique today was the parsing code still. Specifically, this code:
defmodule Round do
defstruct red: 0, green: 0, blue: 0
def parse(round) when is_binary(round) do
String.split(round, ", ")
|> Enum.map(fn entry ->
[n, color] = String.split(entry)
{String.to_existing_atom(color), String.to_integer(n)}
end)
|> then(fn fields -> struct!(Round, fields) end)
end
end
There are a couple of interesting things happening here. We use default values for the struct fields (0
),
and this saves us some typing here: we can split each "3 red, 4 green"
, etc. on the ", "
,
which gives us a list of strings that we can split on space, giving us a list of two elements
looking like ["3", "red"]
. The number is easy enough to convert with String.to_integer
, and the name of the color
is the name of the field we use, so we can just use String.to_existing_atom
to safely convert it to an atom.
Now, if we place those in a tuple of a form {:red, 3}
, the resulting list will be a prop-list looking like
[red: 3, green: 4]
. Here, struct!
from the Kernel
module
comes to our help: we can pass the name of the struct together with a prop-list of the fields with their values to it
to construct the corresponding struct, in our case Round
.
And, since the construction logic will be pretty much the same as if we were to write
%Round{red: 3, green: 4}
, the default value we set on the blue
field will also be used, giving us the result
we want.
There’s only one complication: struct!
expects the name of the struct as the first argument, but the pipeline
operator |>
will inject the first argument in the function, and in our case this is the prop-list. Thankfully,
in the same Kernel
module we have then
macro!
This macro accept a function (typically a lambda) and allows you to call another function from it
while placing the argument you received at any argument position you wish.
This is not necessary, of course: we could’ve just bound the prop-list and called struct!(Round, fields)
outside
of the pipeline. But it’s a neat trick to know.
The “main” functions for both part 1 and part 2 were placed in the Game
module. Round
struct turned out to be
a good enough representation of the max number of cubes of each color needed for the part 1, as well as a good way
to represent the minimal required number of cubes in part 2. Otherwise, it was just the usual Enum.map
, Enum.reduce
,
and co.
If you enjoyed this content, you can sponsor me on Github to produce more videos / educational blog posts.
And if you're looking for consulting services, feel free to contact me .