11
votes
Day 5: Print Queue
Today's problem description: https://adventofcode.com/2024/day/5
Please post your solutions in your own top-level comment. Here's a template you can copy-paste into your comment to format it nicely, with the code collapsed by default inside an expandable section with syntax highlighting (you can replace python
with any of the "short names" listed in this page of supported languages):
<details>
<summary>Part 1</summary>
```python
Your code here.
```
</details>
This one was pretty fun! I managed to squeeze everything into one loop through the input. Part 2 was pretty easy, since the ordering rules can just be used as a comparison procedure (that's the "correct" thing to call functions in Jai... I'm not used to it...). There's probably a far more efficient way to solve it than that, but oh well.
Solution (Jai)
This one really tripped me up.
When I read the challenge, the first thing that popped into my head was "oh! A graph problem!" and I set to work adding an interface to the petgraph library.
After finishing it, getting all the data inserted with pages as nodes and rules as edges, I did a topological sort on the graph to get an ordering of all pages so I could just check that each page in an update has a higher index than the previous one.
I ran it on the example input and it worked perfectly!
Then I ran it on the actual input and it errored!
There was a cycle somewhere!
I tried using a few functions to find the page cycle (the error only returns the node it was at when it found a cylce), but couldn't figure it out.
However, while looking at the input, I realized that I didn't even need to use a graph to begin with.
The rules had relations between relevant pages directly, I didn't need to check transitive rules.
I'd already gone through the trouble of building a graph so I went ahead and used it for the simpler problem of checking if two pages have a rule.
Rhai Solution
Edit: I found out from a reddit post why the cycle caused me issues with topological sort, but not in the other solution.
Excerpt from the challenge:
The cycle never appears among the pages for a given update.
I'd also posted a visualization on Reddit showing the graph structure and the cycle in my input. (I'd tried topo sort on all the rules for my first approach and, like you, failed due to the cycle.)
Python: Step-by-step explanation | full code
List comprehensions and
all
made short work of part 1 again. For part 2 I was ready to bust out a Kahn's implementation when I saw that, of all things, Python'sgraphlib
contains aTopologicalSorter
class (and only that class)! Using as much of the stdlib is one of my goals, so it seemed silly not to use it if it's available.Resolving dependency was something I've had strong feelings about ever since I described my perfect task app, so it's fun to see a similar problem pop up in AoC.
Y'all, I way over engineered my solution and even after cutting back on a lot of it, it's still much longer than some of the other solutions here. I think my brain hurts from a long day at work and then trying to figure this one out.
Part 1 | Part 2
A neat Python type you might like is
collections.defaultdict
. That will let you access and manipulate a dictionary without checking whether a key is already present. If it's not, it gets created with some user-chosen default value (such as an empty set). E.g.ruleset = defaultdict(set)
will let you doruleset[after].add(before)
without having to checkif after not in ruleset
.Incredible, I will remember this for next time. Thanks!
My code is pretty slow, it's probably possible to speed things up with a hash table of rules instead of just using a big list, but I was in a lazy mood.
Part 1
Part 2
Sigh... I spent probably a third of my time on parsing because when I copied the sample data it just used carriage returns but my actual data used carriage returns and line feeds.
I was glad I caught that the ruleset was complete. I almost started working out how to order based on a chain of rules (I think that was part of a previous year's puzzle) but correctly guessed that was overkill for day 5.
Pharo Smalltalk Solution
I've been doing 2015 problems here and there lately in my spare time, and one thing I've noticed is that a lot of the input data just uses
\n
instead of\r\n
, which screwed up my input parsers (I'm using C# on Windows). Last year I didn't have that problem for some reason. After tweaking my tools to just always remove\r
from any and all input files, I haven't had an issue since. (Hopefully it doesn't come back to bite me later...)Once again, input sizes are small enough that I don't have to be clever. My Python solutions:
Part 1
Part 2
Edit: I went back and used a closure returning a lambda to clean up that awkward
cmp
function. Before it relied onrules
being global, sincefunctools.cmp_to_key
functions take exactly 2 arguments.Like some of you, I was tempted to over-engineer this one. The actual solution was pretty simple though!
Spoilers
My solution leans on JS's
Array.prototype.sort()
. I built a predicate function that takes the two page numbers being compared and looks for a rule that includes both of them (in either order). Then it returns either1
or-1
depending on the rule. Since it's only concerned with two page numbers at a time, and only a single rule that governs those two specific numbers, it's quite fast.For Part 1 I sorted every update and compared the sorted list against the original. This worked in my favor for Part 2, which needed to count sorted (corrected) updates, so I made a single shared function to do the heavy lifting and just pass it a
group
flag to indicate whether the already-correct or newly sorted previously-correct updates are wanted.Parts 1 and 2 (TypeScript)
Elixir
Pretty straightforward, but it did take some up-front data structure planning. I ended up parsing the rules into a map of
%{page => set(pages_that_come_after_it)}
. Originally I was storing two sets under each key—pages that came before the key and pages that came after. But it turned out I only needed one set.One tricky case was the rightmost page, e.g. 13 in the example input. I had a bug at first that didn't create a map entry for that page, since it never appeared on the LHS. I fixed this by creating a
page => empty_set
entry for each RHS page, only if an entry didn't already exist, as I built the map.My solution runs pretty fast. I believe it's close to the optimal pure-Elixir solution, without doing things like single-pass parse + solve that sacrifice readability.
Both parts
I decided to use a lot of comprehensions for this one, for no particular reason.
Like many others here, I was initially very worried about part 2 but I managed to hack my way through it. AWK solutions:
Part 1
Pretty straightforward. I iterate through each page then go through its rules to see if I already encountered anything that is defined to come after it.
Part 2
As I have mentioned quite a few times here already, AWK doesn't provide any sorting option, much less an advanced one where you can provide custom rules or weights, so I was initially worried about this. However, I decided to try the naive approach of simply swapping the pages in each violation until they were sorted and it worked surprisingly well.
I got part 1 fairly easily, though my solution is a bit convoluted, but literally had no clue where to even start on part 2 until I looked here. I still wasn't sure how to easily implement it but then I realized there's a graph library that can topographically sort graphs for racket, so I just used that.
Racket (just part1)
Racket (updated with both parts)