So, did I manage to learn rust doing this?
In case you didn't read my previous writeup, the context you need is that I decided
to learn rust this month by solving the advent of code problems with it. Considering,
that by the end of the challenges I was still running my face into the rake that is the
magic of .into
and still not really understanding why sometimes I got the
incantation incorrect for getting iter().map().collect()
to become the right
type, I'd argue I haven't learned rust very well yet at all. But I did manage to get pretty
comfortable writing up while let Some(node) = vecDequeue.pop_front() {}
and
whipping up BFS and DFS at the drop of a hat, so maybe I learned something after all. At
the very least, I think I managed to refresh some fundamentals that had atrophied for a
while doing "real"1 software
engineering for work over the past decade.
Back to the point, before advent of code started I had read 6 chapters of the rust book. So, up through the enums chapter and past the first guessing game sample project. By the end of advent of code? I had read chapter 7 because for Day 15's project I finally got tired of copying and pasting my file loading methods over and over again. And so I read through how to make a module so that I could do this.
// boilerplate.rs use std::{ env, fs }; fn get_filename_from_args() -> Option<String> { let arguments: Vec<String> = env::args().skip(1).collect(); if arguments.is_empty() { return None; } let mut arguments = arguments.iter(); arguments.next().cloned() } pub fn get_sample_if_no_input() -> Result<String, std::io::Error> { match get_filename_from_args() { None => fs::read_to_string("sample.txt"), Some(filename) => fs::read_to_string(filename) } }
Then just pub mod boilerplate;
in my main.rs file to then make
each of my little methods do the same sort of thing:
fn main() { let raw_data = crate::boilerplate::get_sample_if_no_input(); if let Err(ref problem) = raw_data { println!("Could not read input data: {:?}", problem); return; } let data = raw_data.unwrap(); part_1(&data); part_2(&data); }
I also skipped ahead to Chapter 11 to learn about how to write tests when I did Day 15's problem. Mainly because I had some really odd bugs to troubleshoot, and as any one who's managed to learn a lick of sense while doing time at a business knows, a regression test today will save you hours of time later on when you inevitably introduce a bug while working on something seemingly unrelated. For whatever reason, computing blocks pushing each other around when they span multiple places was harder than expected. But tests like this made it a lot easier to figure out what was ultimately wrong with my code.3
#[test] fn moves_a_stair_case_down_correctly() { let start_state = " ############ ##.....@..## ##....[]..## ##...[]...## ##..[][]..## ##........## ############ ".replace(" ", ""); let end_state = " ############ ##........## ##.....@..## ##....[]..## ##...[]...## ##..[][]..## ############ ".replace(" ", ""); let commands: Vec<RoboMoves> = "v".chars().filter_map(|c| RoboMoves::from(c)).collect(); let mut w = parse_large_warehouse(&start_state); let end = parse_large_warehouse(&end_state); for command in commands { w.update(command); } for row in 0..w.map.len() { for col in 0..w.map[row].len() { if w.map[row][col] != end.map[row][col] { w.print_map(true); } assert_eq!(w.map[row][col], end.map[row][col]); } } }
While I'm still left wondering if there's anything like JUnit's ParameterizedTest
and MethodSource
for Rust, I can explore that another time. For the majority of
the coding season, I was learning just enough rust to be dangerous, and considering I managed
to blue screen my computer once during day 254
I think I accomplished my mission.
But, I still haven't read more of the rust book. Which may surprise you considering
that for every single one of the problems, I didn't use an external crate to get a
solution 2. I did read a
lot of documentation on rust-lang
for types like Vec and such. The search on the site was good enough that when I thought
to myself, "Ah, I need a queue", I was able to typically find what docs I needed.
Though, I imagine that being able to use the random implementation of Dijkstra's algorithm on the binary_heap
struct's page, isn't quite the same as truly understanding what all that syntax
around impl Foo for Bar
is. I'd like to of course, but I think that's
going to be a matter of sitting down before the new year and finishing the book itself.
Which is hard to do when the desire to write a blog post about advent of code has seized my attention. Speaking of. Let's talk about the highs and lows of that.
The highs
Quite frankly, the writing, amusing situations, and methods in which the historians get us to solve everything from simple list comparisons, to NP-Complete problems is damn impressive.
I now know that AoC has been going on for 10 years, and after seeing Day 14's part 2, I realize that I did, at some point, know about it. But I must have forgot, or just had other things on my mind at the time since I've never participated before. Here's a list of the problems I really enjoyed solving, in chronological order:
- Day 3. While easy to do with regular expressions, I dropped that library dependency and wrote a small parser combinator myself for fun. Programming for programming sake is fun.
- Day 6. I really loved the "here's a spec, simulate it" type problems, and day 6 was the first of those. I had so much fun with the idea itself, that I ended up whipping up some animation code to render the guard's journey to the terminal as he stepped through the maze. His journey is on youtube here. I don't think I've thought about terminal codes for clearing and flushing the buffer since I wrote up a little battleship game in C++ back in high school. It was a ton of fun to do and felt really great to be inspired to do something fun like this.
- Day 7. While simple, my approach to the code for this one was in line with the domain driven approach that I read about in an F# book recently. Representing the operands as enums, taking the extra time to place the input parsing at the edge of the code, and keeping the logical side purely in my own defined types resulted in code that was extremely easy to expand for the second part of the problem. A super simple task, and one that took a very small amount of time, but it's really nice when you get to apply something you read in a book and see it actually hold up to its promise of maintainability.
- Day 9. While I struggled with this one, and ended up finishing the challenge after work, I just liked the memory defragmentation scenario because Tim Cain had recently put out a video about memory tricks he'd used in video games in his career and had mentioned how they had a custom memory allocator that manipulated memory blocks. A very satisfying problem to fiddle with, even if the elve's requirements to only move the blocks once made it a bit more challenging and less sensical.
- Day 11.
The day that signals the end of simple brute force solutions always working. The scenario itself
was fun, I like the idea of stones doing something new everytime you blink, and another day for good
DDD approaches making code easy to manage and maintain. Then, part 2 comes along and demand you
either know what Dynamic Programming is, or at the very least, have had to optimize and cache long
running operations in a system before. Assuming you know your language well, that optimization was pretty
straightforward to do once you realized you could do it since there was a repeating pattern
in the calculations you could take advantage of. But of course, I don't know rust well! So I had to
figure out how to actually throw a mutable hashmap around between functions without the borrow checker
getting upset with me. It took a bit to get the compiler to agree with
me since
v: &mut Hashmap
is not the same asmut v: Hashmap
! - Day 12. I've never done a flood fill algorithm before! Not to mention, geometry is not my strong suit, so I suppose it's no surprise that it took me until 2:30am to get part 1, and then another 2 hours after work later that day to get part 2. If I had added in some unit tests I think I probably could have figured out part 2 faster, since the edge detection part was the thing I seemed to struggle with the most. Still, another day down and it felt good to fiddle with unfamiliar problems and still get to the end ok.
- Day 14. More robot simulations! Day 6's animation code came in handy during part 2 of this one. The silly question of when do the robots make this shape was so out of left field! How do I detect such a thing? I was initially irritated with the question itself because up until this point the requirements for each part, poorly worded or not, had still been given. But "find when the robots arrange into this complex shape" without defining what the complex shape even meant was... less than thrilling. As I said though, Day 6's code was quite handy, since I had a way to animate frames, I just dumped all of them and then scrubbed back and forth across the grids to figure it out. Searching by repeated # pounds signs worked as well, and for the programmatic approach to determinining when the shape first appeared was simple enough to find by looking for a high density of robots.
- Day 15. More simulation! I liked this problem for two reasons. One, it reminded me of Javidx9's sliding block video that I had watched a few years ago. And two, I got to write unit tests and get the fun dopamine hit of all of them passing once I hunted down the last remaining glitch in my pushing logic in part 2 when things got trickier. While part 1 only took an hour and a half, part 2 took me until 4:30pm later that day to finally get it right. Still, this day goes into the highs despite taking a while because I didn't feel frustrated at all while working on it, just happy to be able to bounce ideas off people in a discord server when I got stuck and really fulfilled once I got that second gold star.
- Day 17 Part 1. I'll be specific here, part 1 was a high of implementing a spec and enjoying myself. The instructions were relatively clear, the system itself was kind of neat I thought, and it was fun to make a little program tick along. That said, the 2nd part I'll count among the lows of the challenge.
- Day 19.
I enjoyed the towel arrangement problem because it let me write another parser for fun
to get the input into nice little domain models. And secondly because I got to fiddle
around with a Trie. Tries are pretty neat data structures, but their use cases are relatively
limited in applicability to my day job. I'm glad that I started watching the Primeagen a
year or so ago, I'm pretty sure the first time I really heard about a Trie being applied
out in the wild was through his netflix stories about their big Trie that could take down
production. Also, here's an excerpt from my git log:
Part 2: port python solution from earlier to rust
As you can see, today was a win for learning how to write things in Rust, even though it felt a bit depressing and flustering to have to write the thing out in a high level language first, then basically copy it into rust and then fight the compiler for a while.
I took some time during a break at work to get a Trie in python that worked out fine, figured it would take some time to make in rust and so waited a bit but submitted the answer earlier. This was easier than I thought besides the fact that I think I _was_ definitely going in the right direction with my previous code, I just had the data structure not setup so correctly.
For the time being the Trie is indexed by char rather than one of my towel stripes because I didn't want to think about how I might need to modify the way the slices work and such to look for a length of the enum. Though I think maybe it would have worked anyway though but eh... it works and it works so that's good for now. - Day 21. Day 20 took up a ton of time, and I didn't finish it until 1:57am in the morning, at which point I initialized and started working on Day 21. By 3am I had my basic framework code in place and all I had to do was actually write the method to determine which button pressess one should do to get the robot pressing buttons on a robot pressing buttons on a robot to unlock a door to save an elf stuck on a spaceship. I called it a night, yawning quite heavily, and went to bed. After I had finished my usual Saturday activities, I got into solving the actual problem and it didn't take too long. I enjoyed the representation of the problem, specifically in that by foregoing the idea of a grid, and instead using the fact that the input space was small, I could model out all the directions at each step by hand. This let me solution never have to care about the bad empty space on the keypad causing paths to be invalid, because there simply was no way to represent the invalid state during a traversal in the first place! Luckily for me, since I wrote my first part in a general sense of going through N robots to get to the last one, it was trivial to go from solving 2 robots to solving 25 layers of indirection with the same code.
- Day 25 was fun! It was such a relief for part 1 to be so simple. Just find out which locks match with which keys. It was really just a parsing problem and once you were past that, you were homefree. I was really nervous about the second part being something that was going to suck away all my time from opening presents with family or something like what had happened the day before, but then. I was pleasantly surprised at part 2. The ending to the challenge was here and I felt relieved, accomplished, and maybe a bit lacking in my skills and like I should do some more to learn more things.
Of course, the highs were great. But... that feeling that I need to brush up on things I used to know, I need to explore more problems, and all of that obsessive ugly need of mine was powered by the lows of the challenge.
The Lows
Most of the low points during the challenges were powered by my own frustration or inability to translate how I know something could work, into a way the computer actually understands.
- Day 4 Part 1. Conceptually simple to collect rows, columns, and diagonals, but my inexperience with rust, the borrow checker, and referencing versus borrowing not clicking all the way yet made this first part absolutely awful. I ended up asking ChatGPT to give me examples of various iterations to compare against until I finally got my code working. Which, by the way, made me feel really stupid. Should I feel bad about not being able to write up collecting an ever decreasing diagonal first try? Probably not, but I did. Thankfully, Part 2 was way simpler than Part 1 and I got that done in less than 5m, which restored my confidence.
- Day 8.
This was the first day that the problem in the coding challenge wasn't so much one of
programming, but rather of reading comprehension. And when you're up at 3:48am on a workday
the desire to praise someone for their clever trickery in wording is not forthcoming. The
sentence that provided the description of antinodes and their setup was not clear:
In particular, an antinode occurs at any point that is perfectly in line with two antennas of the same frequency - but only when one of the antennas is twice as far away as the other.
I read this to mean that antinodes could only appear when the two antennas that were producing them had a distance that was a multiple of two from the other antenna. This is not what this meant. The distance this is referring to is the distance from the antinode to the two antennas, and the twice as far meant to say that if the distance from Antenna A to antinode 1 is 2, then the distance from Antenna B (which is in line with A) must be 4. Thus making the antinode be twice as far away from antenna B as it is to antenna A. Confusing? To me, yes. This made part 1 take nearly 4 hours to finish. Though thankfully, part 2 took a mere 5 minutes and I was able to go to bed and wake up for my 9am day job just fine. - Day 13.
As far as things I remember, math from middle or high school is not one of them. And that unfortunately,
made this day extremely painful. Unlike prior days, I fell asleep before midnight so I didn't
start in on it at the usual time. Instead, I started working on it at about 5am when I woke up.
Within a half hour I had the claw machines parsed out into nice little objects and was happily
adding methods to them to represent the various operations the problem was talking about. This,
unfortunately, was a bad idea, because 20 minutes later after I computed the GCDs to figure out
the answer to part 1, the second part came along and punched me in the face.
Due to a unit conversion error in your measurements, the position of every prize is actually 10000000000000 higher on both the X and Y axis!
Day 11 had signalled brute force would no longer work, and this one really hammered it home. The "trick" of course, was recognizing that the coordinates of where a prize for each particular claw machine was could be seen as a simple intersection of two lines. This is much simpler to handle that the many different ways one could press the buttons on a machine to move a claw around by a fixed amount. On a hint from an IRC user, I tried to find a linear algebra library to solve equations, and spent at least half of my lunch break refreshing myself on matrix operations before ditching the whole thing and just writing out an intersection method that did the job. This... didn't feel good. In fact, it made me feel very dumb that I had forgotten so much basic math. It also felt really stressful and frustrating seeing people post that they were finished in the server I was in. How are they so fast? What am I doing wrong? Why haven't I figured it out yet? There must be a simple solution I'm missing. What am I missing? Am I just too stupid? Such thoughts were rampant on Day 13 unfortunately.
...
Unfortunately, it will take many more than 100 presses to do so. - Day 16. Despite the fact that I implemented A star search from a wikipedia article, I still found this day to be a bit frustrating. I had fun when solving part 1, but it quickly drained away for part 2 because the usual familiar problem is to find the best path through a maze. Not find All the best paths. For part 2, I ended up just ditching my A* and doing a simple depth first search instead which was conceptionally simple to do, but maybe because I'd been staying up every day for the last 2 weeks and getting 4 to 5 hours of sleep per night, my brain was a bit tired and doing that was harder than it should have been.
- Day 17 Part 2. Like I said above, I really liked part 1. But reverse engineering what the computer's program was doing was really hard. It's not a skill that I've exercised much before since I don't really do any disassembly type stuff or any modding. It didn't help that the bit manipulations and blocks of octals weren't readily apparent to my eyes either. Spending a decade working in high level languages doesn't really prep you for dropping down into the bits and bytes that well. But, well, there was a bit of frustration with myself during this too since I knew and figured out pretty early that I had to work backwards from the output to the input in order to create a program that would print itself. With a lot of things, I generally can see the path to implementation, with this day's problem, it wasn't clear how to tell the computer what to do. Ultimately, I did get the answer, but I did look up hints and felt a bit bummed about that. Despite the fact that my goal was to learn rust doing all these AoC problems, I had become a bit lost along the way and it felt frustrating if I couldn't get the answer entirely on my own.
- Day 20.
Considering my final commit message for finishing this day is:
These are reading comprehension tests, not programming challenges.
You can probably tell I was a bit flustered by this one. At first blush, it felt like we were getting another graph problem, which at this point, I and most people I knew doing this, were tired of. Oh great, another problem to throw BFS at, lovely. And yet, despite having the previous days work to build off of, this dang problem managed to throw all of us off, by the fact that it wasn't actually a maze: It just quacked like one. This day, and day 17 were the only days that took me more than 24 hours to submit an answer for both parts. And I felt so silly afterwards. I don't know why I give myself a hard time, but the fact that there are people who can do these problems in less than an hour and speed code their way to success faster and more correctly than I can impresses me as much as it makes me feel bad about not being able to keep up. I suppose a slice of humble pie is good sometimes, but it's really hard to just tell yourself it doesn't matter if you have to look up hints when you generally think of yourself as being a pretty decent programmer. Granted, I would like to think that most of us aren't writing programs to help people cheat races, so maybe it's best if we all aren't so great at problems like this. - Day 24. Part 1 was easy. Part 2 was suffering. This was similar to day 17, in that it was more reverse engineering. Except, this time I didn't even bother feeling bad about looking for hints. Part 2 clearly stated that the logic gates were meant to add, so it was off to look at how half adders, adders, and ripple adders work and look like. My issue, and what took up a bunch of time that I should have been spending with my brother during my visit to his home on Christmas Eve, was that I was thinking I had to actually fix the circuits given in order to answer the question and move on to the next day. But... that wasn't true. We just needed to know which gates were incorrectly setup, which can be determined by looking at the way gates should be setup and checking the various gates for rulebreakers. Once I had that then it was easy to get my answer. But, the obsessive nature I developed with this challenge prevented me from properly spending time with family while I still had one of these problems hanging over my head. That wasn't really a problem for previous days since I had been at my home, but after traveling to my folk's place I couldn't help myself and ended up feeling bad about it later. Wups. Definitely behavior to curb if I do this again.
Despite generally feeling like I was beating myself up mentally, I think the highs still outweighed the lows. After all, this was my first ever advent of code, and I managed to finish every single problem! I shouldn't feel that bad about myself since I managed to do this.
The future...
So, will I do this again?
We'll see. I want to give an emphatic yes because it was fun. I really enjoyed finding a small community of people striving to do these challenges, sharing jokes, solutions, hints, and just generally encouraging each other to do their best. I think it was a really good way to get a crash course into a part of a new language, but the fact of the matter is that a lot of the problems and challenges boil down into
- Parse the input
- Model the data
- Apply an algorithm
- Repeat for part 2
Which doesn't expose you to a wider berth of needs to be addressed. So, like I said, I got 7 +1 chapters of the rust book read to solve the problems. Would I have had an easier time if I had read more? Maybe. Chapter 8 is apparently all about standard collection libraries and no doubt it would have helped. But with the ever present "solve the problem and don't fall behind" feeling I had for 25 days looming over head, I didn't actually feel like I had time in my day to actually read and learn more about the language itself.
Would I have had more time if I gave up something during that time? Maybe. I have trouble letting go of things though, I want to stream 2-3 hours a night of video games, I want to make sure my colleagues at work are setup for success during the day and I sometimes stay a bit late to do so. I want to watch 20+ shows of anime a season and get my wide net of amusement full of Japanese fish. And I don't want to give up any of those. So... fitting in 2-4 hours of programming into the day really pushed my sleep schedule back into the college days.
I didn't mind it. If anything, I felt alive again, even if I was tired and yawning a lot. It felt really good to burn the candle at both ends. I even discovered people at my work who had made a dedicated slack channel to the advent of code. It was really nice and I think if one of the goals of advent of code is to provide programmers across the world an opportunity to connect with each other, then it really is the perfect time of year to do it.
Thanks Eric, thanks everyone, I hope you all had a Merry Christmas and programmer advent.