So you want to learn a new..what is Advent of Code?!
Well, perhaps you do not... or, perhaps you do, but you do not know it yet. Perhaps it's not so much learning a new programming language that has got your fancy as much as just improving at what you do. ...and perhaps you have heard of this thing—this curious tradition—that some developers keep talking about during the winter holidays, called Advent of Code. Go ahead, click that link, I promise it will not bite (at least not yet). It so happens that Advent of Code (AoC), is a great way to—do something else—practice your skills or new skills as a developer. Personally, I have been using it as a way to get back into the Rust programming language, and getting more comfortable solving problems using Rust as my tool.
But, AoC is difficult, or so I have heard
It is! Particularly this year, it seemed like the author decided to go hard from day 3. The good thing, though, is that through adversity you will come out a better person—cough—developer.
Really, AoC puts you in front of obstacles you might not normally encounter in your day-to-day work. It presents you with challenges that, when you solve them, provide you with an expanded utility belt, and very likely some newfound knowledge too boot (if not only the unnecessarily shoehorned complex story that the challenges tell).
There is no shame in looking at someone's solution if you are stuck, or asking some AI to aid you on your journey to solve a given challenge. Or, even just skipping it all together if you didn't like it. The important part is that you put yourself outside your comfort zone so that you can learn and improve. Skills, skills, skillissue.
Still not convinced?
How about we take a peek?
Advent of Code runs for 24 days, with a new problem posted every day. Each problem is split into two parts: Part 1 requiring you to solve some word challenge, and Part 2 often (depending on how you solved Part 1) requiring you to do some modifications to derive a different output.
As the puzzles get harder you will eventually encounter solutions that may take a while to run (as in your code should not only solve the puzzle, but also solve it efficiently, or in a way that circumvents O(n^2)
(or worse) runs, or large/multiple iterations).
Spoiler Alert! I will be presenting the solution to 2023's day 2 below
2023 Day 2 Part 2
What if we took a small dive into what it takes to solve an AoC challenge. How difficult could it be? Perhaps—and I hope—it will peak your curiosity just enough to attempt your hand at the rest of the challenges this year. Or—if not—perhaps next year's, or at a later point (as these challenges are open to be completed whenever after they are published).
Without further ado!
In Day 2: Part 1, We are asked by an intrepid elf with a bag of cubes to solve a game of his. The task involved finding valid games based on a given maximum of Red, Green and Blue cubes, and records of games where the elf pulled out a given number of cubes from his bag. The games would look like so:
Game 1: 3 blue, 4 red; 1 red, 2 green, 6 blue; 2 green
Game 2: 1 blue, 2 green; 3 green, 4 blue, 1 red; 1 green, 1 blue
Game 3: 8 green, 6 blue, 20 red; 5 blue, 4 red, 13 green; 5 green, 1 red
Game 4: 1 green, 3 red, 6 blue; 3 green, 6 red; 3 green, 15 blue, 14 red
Game 5: 6 red, 1 blue, 3 green; 2 blue, 1 red, 2 green
Where the ;
denotes a set (hand) of cubes pulled out of the bag.
E.g. In Game 1: The elf shows you a set of cubes in his hands three (3) times; the first time is 7 cubes, where 3 are Blue and 4 Red; the second time 9 cubes, where 1 is Red, 2 Green, and 6 Blue. And the final time where he only holds 2 Green cubes. (The elf puts the cubes back into the bag between each set).
A maximum is defined like so: 12 red cubes, 13 green cubes, and 14 blue cubes
.
Given this info we can see that Game 3, and Game 4, are invalid. In Game 3 the elf shows you 20 Red which is higher than the maximum defined above; and in Game 4 he shows 15 Blue, which is more than the 14 defined as maximum.
Having solved the first part (I dare you, try it!) We are now asked to find the minimum number of cubes—of each colour—that the bag could contain given a valid game. We then take the three numbers and multiply them to get the Power of that game. Finally, we sum the games together.
E.g. In Game 1 the different sets look like this
3 blue, 4 red; 1 red, 2 green, 6 blue; 2 green
. Given this information we can see that the largest amount of Blue cubes is 6; the largest amount of Red is 4; the largest amount of Green is 2. The power of 6*4*\2 is... you guessed it! 48
Now we have enough information to solve challenge. We have an input we can test with above, and a known solution 2286
.
Let's get crackin'! Step through the problem along with me.
Rust, here we go
First we want to define a type that I can use to help me reason about the values.
struct Set {
red: u8,
green: u8,
blue: u8,
}
Then—and this is a neat feature of Rust—We may want to implement some methods that come with this type. So that when we create values using it, those methods (one is an associated function, I know) act as helpers on the types.
impl Set {
// Small helping assoc. function
fn get_max(current: u8, new: u8) -> u8 {
if new > current {
new
} else {
current
}
}
// Stores the largest of current or new value in the given color
fn add_max(mut self, color: &str, count: u8) -> Self {
match color {
"red" => self.red = Self::get_max(self.red, count),
"green" => self.green = Self::get_max(self.green, count),
"blue" => self.blue = Self::get_max(self.blue, count),
_ => unreachable!(),
}
self
}
// Does the multiplication for us
fn get_output(&self) -> u32 {
self.red as u32 * self.green as u32 * self.blue as u32
}
}
Already, we are getting at the solution. The add_max
-method is what the star of the show (and it is very simple! And probably not great Rust code, but hey! I am learning.)
The final part is parsing the input, looping over the input, creating the types above, validating and distilling the output. It's actually not a lot of code (Yeah! I was surprised too).
fn solution(input: &str) -> u32 {
// Take the input as a string
input
// Iterate over the lines
.lines()
.map(|line| {
let mut iter = line.split(": ");
// We don't need the part that says `Game: ` so we toss it away
let _ = iter.next();
// Split into sets
let sets = iter.next().expect("We need a string of sets").split("; ");
// This is the start of what we return
// Map over the sets split them into colors and flatten we now have:
// ["4 green","6 red", "2 blue", "4 blue"] etc.
// This because we don't care about individual sets
return sets.map(|s| s.split(", "))
.flatten()
.map(|s| {
// We then need to parse those counts and colors
let mut count_and_color = s.split(" ");
let count = count_and_color
.next()
.expect("We need a count")
.parse::<u8>()
.expect("Count needs to be a number");
let color = count_and_color.next().expect("We need a color");
(color, count)
})
// Here we do some rust magic, equivalent to the Javascript `Array.prototype.reduce`.
.fold(
// This is the value we want to start with
Set { red: 0, green: 0, blue: 0, },
// And then we adjust the max of each color as we go
|set, (color, count)| set.add_max(color, count),
)
// Finally, get the sets output
// This is possible since `fold` gives us the `Set`-struct with the associated method: `get_output`
.get_output()
})
// Return the sum of each game
.sum()
}
Easy as that. No, really it is a deceptively simple problem; get all the values in a game, find the maximums of each colour; multiply those and return the sum of all games.
Time consume
AoC can be a bit time consuming, and winter holidays can be quite daunting as is. I truly recommend giving at least one problem a go, and seeing where it takes you. Who knows, perhaps you get hooked, perhaps you end up sitting up til late at night agonising over how to split this into a 2d grid:
467..114..
...*......
..35..633.
......#...
617*......
.....+.58.
..592.....
......755.
...$.*....
.664.598..
If you do end up down that road and need some inspiration. Perhaps I have already solved the problem. Perhaps not. Perhaps you solved it and now you can give me some pointers... Hah!
My Advent of Code lives here: https://github.com/aMediocreDad/aoc
Happy Holidays!