Advent of Code 2024, Day 2
Advent of Code day 2 learnings and pseudo solution with link to the actual solution in Rust.
Part of the AoC 2024 series
- Day 1 post
- Day 2 post (You are here! 📍)
Today’s blog post is brought to you by cargo clippy
. Absolutely love it to bits! I still kind of imagine Microsoft Clippy when I run it.
It seems like you’re struggling. Would you like to not suck at Rust? 👁️📎👁️
What I find fascinating about these exercises is that they’re simple. So simple in fact that you’re going to have a hard time solving them. Okay, I’m having a hard time solving them in Rust. In return I get to learn how vectors should be handled Correctly™.
So here’s what I learned. As always, there be spoilers.
Spoilers below
Part 1
Once you get over the tedium of reading a file, where I learned I can use the magic function .lines()
and not have to manually split by \n
, and then making a vector of vector of integer 32s, the next step is to iterate over each vec of vec, and then for each number, check whether they fit the “strictly monotonic increasing / decreasing with at most 3 difference between adjacent items” kind of list, and if they do, excellent, count them up.
I did it weird, because I thought I’ll be smrat (sic!) and figure out the direction the current list of numbers is going based on the first two numbers, and then only do the work for that direction. Which is clever, but speed wise doesn’t matter one bit.
Part 2
Ho boy I was trying to be so clever, and ultimately the brute force solution worked.
First I thought if I encounter a number that would cause the list to break the safety of the list, I’ll just set a fuse variable to blown, and if it does it again, then I’ll mark the list as unsafe.
Sadly after writing some super clever woefully nested for for if if if
loops, I lost the plot, got pissed off, and thought “nah, fuck this, I’m just going to brute force it.”
And then wrote the brute force code in 15 minutes which is significantly simpler, cleaner, fewer tests that still achieve good coverage, completes in a fraction of a millisecond, and, most importantly, gives me an actual correct answer!
I briefly got annoyed at the borrow checker because this thing gives me a pointer to a number rather than a number, which makes sense, but is a pain:
for (i: usize, n: &i32) in list.iter().enumerate() { ... }
So I did what every new person to Rust would do: I tried to put a &
somewhere that would make it go good:
for (i: usize, &n: i32) in list.iter().enumerate() { ... }
The brute force part was inspired by the actual wording of the problem:
Now, the same rules apply as before, except if removing a single level from an unsafe report would make it safe, the report instead counts as safe.
What if, and bear with me, we took that literally and removed a single level (number) from the list? So if I have a list of [1, 2, 3, 4, 5]
, I’ll just generate all the possible other lists that I can get by removing a single number from it, and then try them one by one: [2, 3, 4, 5]
, [1, 3, 4, 5]
, [1, 2, 4, 5]
, [1, 2, 3, 5]
, [1, 2, 3, 4]
.
I did sneak in some optimizations though, here’s the pseudocode:
// while checking the full list
if check_increasing(list) || check_decreasing(list) {
// unchanged list is safe, return early
return true;
}
let vecs = generate_vecs(list);
for vec in vecs {
if check_increasing(vec) || check_decreasing(vec) {
// this changed list is safe, early exit, do not
// check the rest of the changed lists
return true
}
}
I lost so much time trying to be clever, juggling comparisons, which number do I need to subtract from the other one if it’s decreasing, and so on. At least I didn’t fight Rust itself much. Clippy is awesome.
Let me fix the clever code in Part 1, and then I’ll hit publish on this.
Yellow Nissan Photo by Kevin Bhagat on Unsplash