advent-of-code/2024/day5
Markus Brueckner 7abbc37a65 - Day 5
2024-12-05 21:54:39 +01:00
..
src - Day 5 2024-12-05 21:54:39 +01:00
Cargo.lock - Day 5 2024-12-05 21:54:39 +01:00
Cargo.toml - Day 5 2024-12-05 21:54:39 +01:00
README.md - Day 5 2024-12-05 21:54:39 +01:00

Solution Day 5

Slightly more complex file format this time. It consists of two parts: "rules" or the form x|y and "updates", that look like x,y,z,.... Luckily for parsing we can just go through the .lines() iterator one by one, check whether the line contains a | and, if so, parse it into a rule struct. If not, we just continue splitting it into Vec<u32>. We can do this the simple way consuming each line to look for the |, because there's an empty line between the blocks, that we can just throw away.

After everything is parsed, the "real" work starts: writing a rule-interpreter, that checks, whether the updates are correct by the rules. The solution is rather simple: for each element we check, whether any element, that should come after it, actually comes before and the other way around. If this isn't the case, the update is correct.

While the rule-checker is reasonably simple, it does need to go through all rules twice for each element of the update. This could be improved by indexing the rules in a HashMapbased on their constituents and do an O(1) lookup. The runtime is so fast, however, that this is not worth the effort.

Task 1

This task is easy: find all correct updates (just a filter with the rule interpreter) and sum up the middle elements. Since all updates are of odd length, the middle element always exists, so .reduce() can do quick work.

Task 2

Slightly more complex: the incorrect rules should be retrieved and fixed, before the middle element is supposed to be summed up. Took me a while, but then I had a bright idea: the rules all together are nothing more than an ordering. If you interpret all rules against a pair of update entries, you can tell which way around they are supposed to go, i.e. which one is less than the other. So we can use the .sort_by() function from the standard library with a custom comparison function, that interprets all relevant rules and returns the ordering. After sorting the elements, summing up the middle is the same as in task 1.

Note: This assumes, that the full set of rules represents a total order. If it were to be a partial order, the resulting order would be unspecified. Luckily for us, the designers made the ruleset a total order.

This could again be more efficient, by indexing the rules once first and then do an O(1) lookup for all relevant rules instead of checking all rules for each call to the comparator. Again, the runtime is so quick, that this seems unnecessary optimization.

Also, there's a .clone() for the update vector in there, because I couldn't figure out how to tell the borrow checker, that I would be happy to fully consume the input while checking it. There's no need for me to touch any update twice, so borrowing isn't actually necessary. I just didn't find the right combination of mutable vectors and IntoIterators to make this clear to the borrow checker.