2024
Code can be found in my github repo
How to read my code
My code follows some ideas. First, i have a single main-function inside of main.rs, which handles everything for this aoc year. You can run all days with cargo run/test -p adventofcode24 ..
inside of Rust/
folder. Replace ..
with the day you want to run e.g. 6
for day 6. The main-function will take care to run all days in parallel, and provide the results in a structured way, but the days do not be in parallel.
Every day follows the same structure, so it has a single day
-function which is public and it returns a String with the results for part1 and part2. In the code, this are the entrypoints for you to look for code. See day1.rs as an example. Most times, i try to follow the rustacean way to code, so you will see more code in comparison to other solutions. So if you want to look at the code, you should start with the part1
or part2
functions at the bottom of the day-files and start from there your investigation. Do not try to understand the structs at the top of the file first. This will help you to understand, what is going on and why the structs or functions are the way they are.
In most cases the code is not optimized or restructured after finish, so there can be some ugly stuff in it. But mostly it should be readable for rustaceans or for people who want to become one. Sometimes i am trying to implement stuff from std
-library by myself in context of an aoc day, so do not be too confused, if you see more code then needed.
Day 1 - Wrapper structures are good for "separation of concerns"
This was a pretty easy and straight forward solution. No hard things to think about. In my solution, i go the hard "rust" parser way with FromStr
-Trait, but it is a much cleaner implementation. I am using this approach for some AoC now. It helps a lot to split implementation details.
Also, you can find a good use case for wrapper structures in rust. For each step in my implementation i am using a separate structure to make it more cleaner to work with intermediate steps. I implemented UnsortedLocations
, SortedLocations
and SimilarityLocations
. With this approach, you cannot call get_score
for part2 without the needed steps taken.
Day 2 - Bruteforce can win, too... sometimes
Okay... Part 1 was easy and not much to talk about. After some time (2 hours...) Part 2, too. But after this 2 hours, i take a different approach as the first one and using the brute force approach. In this 2 hours, i tried to find the smart-boy solution, but there is no such thing for me. The brute force approach is already fine for the sample set. After this lightbulb i got it done very quick. Use your approach for part 1 and remove every element in your array and you are done. Not much to mention or learning from.
Day 3 - No Regex, more compiler stuff
This day was a little more typing work. On reddit, there was some spoilers about regex. After reading the task, it comes to my mind, that the second part would do something that would break regex and my study of compilers comes closer for my solution. A quick look into my notices and it comes clearer. Now you can see a small lexer and parser (2 step compiler) in my solution. First i parse the text into Items, but do not throw an error on unwanted characters. This will help us later to Parse the logic and runs over the items and handle appropriate.
Some typing work later, part 2 becomes an easy task, which would be much harder with regex. Both parts runs in 647.7µs with release profile, so i am very happy with it. :)
Obviously, i could skip the Parser part where i transform LexItems to Tokens, because it uses a windows on iterator... But i wanted to train my compiler knowledge stuff.
This was implemented in a previously version: learning_tasks/Rust/adventofcode24/src/day3.rs at 92da2670849dd3d20ae1b675ac36d004018a25f3 · Heiss/learning_tasks · GitHub
For a much closer solution to normal compiler theory, i implemented a much more native and basic lexer and so the parser does not need a window on the iterator anymore. So this could be the starting point for a real compiler or interpreter. day3.rs Funny thing: it is mostly, but not exactly as fast as the windows solution, but more generic. The only ugly stuff is, that i repeat myself and that do
and don't
are very similar, so they are sharing some code. But no magic needs to be done, except some else-case handling. :)
-later the day: After some scrolling around on reddit-
Okay, regex have also a great and easy solution. But i am not here to do easy things. :) I had fun to implement a small interpreter again. This regex stuff is also very easy, but stands on bright shoulders. So mostly it is an easier approach on the same idea.
Also i am happy to not be the only one, which takes this approach.
Day 4 - First hard day
Okay, now we are getting serious. This needed some more attention on my side. At the end, the logic is pretty simple, but my first approach was a little bit to biased for window iterators, so i implemented it by myself for 2d-vecs... But this ignored the fact, that a single X can be the starting point for multiple words and that i count the same XMAS twice or more.
So i need a new approach, which looks for X as the starting point. But with the iterator approach, i had a lot work done already and so it was pretty easy to adjust to this. Same thing for the part2, because i took the same approach and looks for the A as the starting point. With a little pattern recognition stuff (and code explosions), this is a pretty good solution, also for other use cases or input.
And i learned how to implement my own iterator and windows in rust. So yeah, it is a win for me.
Day 5 - HashMap and invert the logic to the rescue
Okay, this was the first one which is easy in part 1 and a little bit (i mean, by a lot) harder in part2. Until today, this years was pretty biased for part 1 on my side. part 1 was pretty straight forward and easy going, i inverted the provided logic from the task text with the help of a hashmap and so i can find the numbers in false order pretty fast. After some thought for part 2, skipping the first approach with swapping around the elements or using a power set, i found the approach to sort the vec with a pretty simple Ordering with the help of the hashmap, created for part 1. And so... it does not escalated like day 4.
The only thing i rushed today, was the data struct, which are pretty ugly. But i used a wrapper struct for better usability, so i take it as a win.
Day 6 - When your mass in code hinders you to achieve the goal
What a trip... I worked approx. 12 hours on this one. Not part 1... This was easy and pretty straight forward. Part 2 is easy to understand, but my code was optimized for part 1 and part 2 was incompatible to it, so it got a mass and my debugging was very frustrating. But a sleep later, i got it fixed. Obviously you have to remember, when checking for loops, where the new obstacle was placed. And that it does not get placed on a field, the guard visited already to come to the current position. This 2 conditions was violated by my code. The loop recognition is done via a hashset and a check if the current field can be saved as visited. If this cannot be done, you were there already and so it is a loop.
Day 7 - A heart between mathematicians and computer scientist
My first approach was to implement a simple power set algorithm with calculating all bits variations and so i can figure out all operation combinations. And after this, calculate all results. This is the brute force stuff with a cool approach to generate the operator sets.
But the mathematician inside of me recognizes pretty fast, that there have to be a "skilled approach" and so i take a sheet of paper and a pen and calculates a little bit... But made a mistake and so got a false result. But after day 6 i looked for a quick win and go back to my first approach.
After got it done, i thought a little bit and saw, that every multiplication has to be a divisor, which needs to be at the right side of numbers. This seems to be the right solution, because if the divisor is not on the right side, it has to be a subtract operation. When no number is on the right side and the left value is not 0, it has to be not true.
And for part 2 it would be better to calculate directly the solutions, instead of generate every combination. Maybe i will find the time to make this happen.
After solving day 8, i found the energy to revisit day 7 and try to find the better approach (the first solution takes 44 seconds to complete). So i took a look at the math side of this task. And without any headache, i found out, that concatination is a mathematical function. With this insight, it was easy to write a solution on paper. But... i did not accomplish the same result in Rust. Somewhere was an error inside of my writing. So I took a look into reddit and there was a solution to copy, which works immediatily and so i take this also as a win, because it follows my logic, described above.
Day 8 - A day for maintenance and frustration
Today was a simple day, but my first approach does not work as expected. So i sat there and try to debug it for several hours. At last, I implemented the discret way and this works immediatily. Sad, because my inner mathematician would like to write linear functions and no discret loop stuff.
But okay, it is fast enough. And after found out an optimized solution for day 7, also this was satisfied.
Day 9 - DRY and pattern matching
Mh. Okay a second day in a row, which is not very hard but fun to implement. In my implementation, i tried to reuse my structs from part 1 and using wrapper structs. On this day, you need to learn how to access an array without violating the borrow checker. But working on arrays with rust feels a lot like C and the borrow checker is not so rigid as in other situations.
I am a little bit proud of my solution, maybe not the fastest one. But i think it is pretty straight forward and you can learn a lot about the pattern mechanics of Rust and see the if let whirlpool to swoosh. :)
Day 10 - First pathfinding problem
As every year, there is a pathfinding problem. And on this day, i solved part 2 before part 1. This seems also a thing for mostly of active redditors, because the memes were real. There is not much todo, except the lookup logic and finding the next steps. Part 1 wants to know, how many niners you can reach from a specific starting point. Part 2 asks for the alternative routes. But part 2 is the basic approach for part 1. So in my solution, it is mostly a copy-paste. Maybe i will DRY my code. But not today.
Day 11 - Memorization and dynamic programming
Yep, like the day before, we need a classic approach to complete this task. First, I implemented the naive approach. The second part asks for the same but for more iterations. So, you need a memoization approach. Try not to calculate all stones everytime. Instead count they up, because you know that every stone changes on blink. And the next iteration could show up numbers, you already saw. So there has to be some loops. With the help of a table, you do not have to calculate such loops, only the unique ones. In fact, with memoization you group up all stones by there numbers and only unique ones have low numbers. All looping numbers count up quickly, but do not need a lot cpu work, because they are grouped. The code is pretty straight forward and well structured. So this is a very good task to test your dynamic programming skills.
Day 12 - Clustering
Okay, i am a big late to the party, because i was on vacation. So I was happy to see, that this task was a clustering one. It is heavy typing work, but not so much head work. So mostly time went into the cluster algorithm. At first, i create a hashmap to prepare the cluster finding algo. Now it assumes, that all characters are a single cluster. With a simple neighbor check, cluster becomes bigger, if the neighbors have the same char
. Because of rust, i used the take
and swap
trick to not relocate around and satisfy the borrow checker. The algorithm is done, if nothing changed anymore.
After this, we can do the dirty work to finish the task. The parameter is pretty simple, because a field is a parameter on a side (it has a maximum of 4 sides), if a neighbor is not in the cluster. If it does not have any neighbor, it is a parameter on all 4 sides. Area is counting.
For part 2, you need the idea, that any side equals the corners. How you can find the corners? Right, you look for diagonal neighbors, so you have an interior corner (L shaped one). Also you have to check if the current point is in the edge of such an L, so you are the corner itself: Then it is an exterior corner. After you get this idea, it is pretty fast to implement on a grid or a list with contains
method.
Day 13 - Math task
Today, i had no problems to solve this task. First you need understand, that you have two unknown variables and two equations. So you can do homework like in school and find an equation to solve both variables. Next you need to parse the input (i do it the hard way). Part 1 was harder then 2, because i had some small issues with my datatypes (obviously division is different for float and integer). After fixing this, i solved both pretty quickly.
Day 14 - Cheated day
Part 1 was pretty simple after recognizing, that i have to use the euclidean remainder. Part 2 was tricky, so i spoiled my self after an hour without an idea.
Day 15 - A hell for typing errors
This day was the worst by far this year. I needed 3 complete rewrites for part 2 until i made no mistake with the first approach (i knew that it was the correct one, because it was BFS to find any obstacles in a path). And furthermore i do not know, where my error was. The code was very equally. Maybe a litttle bit frustrated after a code loss cause of misusing git. Not so friendly as the last days.
Day 16 - Dijkstra to the rescue
Yep, it is this day... We all know, it would come. Pathfinding is here. Part 2 is a modified dijkstra, so be aware to understand, what dijkstra really does. It needed some attempts to get it correct, but after figuring out, that valid paths does not need to have the same distance, it was straight forward. Mostly typing work. You should know about dijkstra and track the looking direction, because it will affect the next distance. Also be aware, that you do not skip essential positions (offset by one).
I tried to make the code readable through functions and wrapper types. So functions are only usable after calling a specific other function, so you cannot misuse it.