Yesterday was officially the first day I failed to complete the puzzle on the day, due to a busy schedule. I'm still planning on getting caught up and saving Christmas, but now I've got a bit of a hill climb. How caught up are you?
The Puzzle
In today’s puzzle, the elves have cooked up some sort of graph-based regular expression tree, and they need help parsing through it to get messages from their satellite in order to get pictures of a sea monster! These elves sure do spend a lot of time coming up with their own custom interfaces and protocols.
The Leaderboards
As always, this is the spot where I’ll plug any leaderboard codes shared from the community.
Ryan's Leaderboard: 224198-25048a19
If you want to generate your own leaderboard and signal boost it a little bit, send it to me either in a DEV message or in a comment on one of these posts and I'll add it to the list above.
Yesterday’s Languages
Updated 07:10AM 12/19/2020 PST.
Language | Count |
---|---|
JavaScript | 2 |
Haskell | 2 |
Java | 1 |
F# | 1 |
Perl | 1 |
Rust | 1 |
Merry Coding!
Top comments (8)
Spent more time on today's than any previous one. I of course saw it as a parser combinator problem, whereas I think most people will see it through a regex lens. So I spent a lot of time trying to make it both backtracking and error-reporting before realising the error reporting isn't part of the problem. This simplification fixed it for me then a few refactoring iterations later I've got it to the following. My solution is general for all tail-recursive sequences, not specific to the problem posed as suggested in the text. No string copies, as few
Vec
copies as I can get away with, and I'm actively looking into reducing the final ones as I have a similar problem in another project I'm working on and want to make as efficient as possible.I managed to avoid most of the Vec copies by building an iterator structure for the whole thing. This means almost the whole matcher is now lazily evaluated. Just in the tail recursion part I need to iterate the results twice so there's no choice but to collect into a temporary Vec. Lots of lifetime annotations needed to make it work as it's a big old complex data structure in memory, but it's cool to see this sort of thing can be done if needed with compile time memory management.
My Ruby solution.
For the first part, my approach was to generate all valid strings and count the messages that appeared there. This runs in a few seconds since the number of valid strings is relatively small.
Part 2 was really scary, but once I noticed that the modified rules were at the end of the chain (near rule 0), it was easier. Did the first approach to get the matching messages, then did some simplification of the rules + regex matching on each remaining message to find valid ones.
Ah today was fun... I did have to stop and think for part 2 as I'd decided to go down the path of parser combinators rather than regex and it was clearly to late to backtrack! Anywhy managed to handle them directly, I guess it's a bit of hack, but worked out fine.
I had a slightly odd approach. Having used PEG in at least three earlier challenges for various parsing needs (including yesterday's), I immediately noticed that the rules are worded identically to PEG. So I thought... what if I just string-manipulate these rules into PEG and use it directly? Turns out this works
The
convert()
function converts a line of rules provided by challenge input into a line of PEG for the parsimonious library. The rest is just about using this grammar to parse messages, and catching the parse errors.Part 1 code:
yuuup... didn't think that would work, but it does
Part 2 code isn't that much more, just replace rules 8 and 11, and catch parse exceptions on rule 31, which is a thing they explicitly mention.
My Haskell solution. I realized regex would be useful, but I didn't find any built-in regex in Haskell, so I went for the parser approach instead. Using a parser library like Parsec would also have been an option, but I couldn't bother installing and spending time learning it just for this. So I made my own shitty parser combinator. Then a little hacky solution for part 2.
It's not pretty. It's not elegant. I'm sure there's some Regex theory that would have made this puzzle easier than it was. But I got it done, and that's all that counts. Hand-decoded Rules 0, 8, and 11 to decouple the infinite looping from the rule resolution algorithm, so I ended up being able to reuse most of my work from part 1. :) Don't look at the code for part 2. Just don't.
My JavaScript walkthrough:
The second part scared me, but in the end it went great 😅