9
votes
Day 18: Snailfish
Today's problem description: https://adventofcode.com/2021/day/18
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>
Me, enjoying this day's puzzle, coming into this thread after solving it
That being said, it's far from a pretty solution. Another homework assignment to those interested in Rust: clean up my mess. :D
Parts 1+2, Rust
This is super long compared to other days because I didn't clean it up yet, but I'm posting it because I'm sure someone wants to see how an answer with an idiomatic representation may look like. I tried to confine the confusing parts to the recursive
_explode
method.I spent about an hour wondering why my adding tests weren't passing before I realized I copy-pasted the wrong input for my tests. (Speaking of which, this module's test suite is about as long as the code, solely because so many were provided.)
Uhhhhhhhh... What the actual fuck is this problem?
Now that I've finished it:
This is conceptually an interesting puzzle, but personally the complexity of it made debugging absolute hell for me. I had a few small errors that took literal hours to track down and find because I basically had to do the calculations by hand until I found something that wasn't right. So definitely a mixed bag in my case.
Day 18 Part A - Python
Lots of regex and string parsing. I broke each operation into its own function, with explode being the bulk of it. I used regex to detect the top level pairs, then did a depth calculation for each pair in order. If it was greater that 4 it went boom. The biggest trick here was keeping them in order. I was hoping it wouldn't be necessary within the explode operation, but it definitely was. The OTHER major mistake I made was how I added numbers to the left of an explosion. I decided the easiest way to find the first number in that direction was to reverse the string and do a number search with regex. That worked fine, but I didn't account for 2 digit numbers having their values flipped. That took a reallllllly long time to track down and basically required me stepping through the calculations one by one.
Day 18 Part B - Python
After the hell that was part A, part B was basically trivial. I replaced the end of my program with this and made my magnitude function so that it output an int instead of a string
Tips
There's a lot going on in this one. Definitely break the problem down into manageable chunks so you can tackle it one piece at a time
Read through the problem descriptions a few times to make sure you understand them
Order does matter, this is one where you can't really simplify it. A + B is not equal to B + A in snailfish math. Same with reducing the numbers, The order you perform the operations in is important, even if it's just running the same operation twice. If there are multiple candidates, order can make a huge difference
Watch carefully when your're writing your explode operation, carefully consider how you want to find the first number to the left of a pair. I didn't consider 2-digit numbers and got burned when I tried to do some fancy string reversal
If you're having trouble finding an error, add some debugging code and try walking through the operations one by one to find your mistake. Hopefully whatever's wrong will jump out at you when you get to it
The details really matter in this one, not only will a small change cascade out of control, it'll also be hard to track down where things went wrong. The way that the numbers move around and transform makes it so they obfuscate the source of any errors. At least that was my experience.
I'm glad I'm not the only one having trouble, hah.
It seems like it's got to boil down to some kind of clever tree traversal/balance sort of deal, but I gotta admit I'm running out of steam on it.
"Explode" really seems like the sticking point for me, and I've got a working implementation that covers all but the last big example, and it breaks for reasons I've not figured out yet.
I suspect the biggest trick lies in how you move the "exploding" values back up and then down the tree to the correct place. I almost want to just flatten the tree back into a string or something and manipulate it that way.
I think I'm going to have to concede this one for the night and come back to it tomorrow.
I'm making slow but steady progress. My explode segment is substantially longer than any of my other sections, but I'm pretty sure it works as long as there is only one pair that needs to explode at a time. I used a combination of regex and "normal" string manipulation to get where I needed, but in the process I lose track of the order of the top-level pairs, so I'm relying on there only being one that needs to go boom
I think I'll be able to finish part A soon, but I have no idea what surprises are waiting in part B
Edit: OH HEY LOOK THAT ASSUMPTION I MENTION ISN'T VALID asdgasdgaghrjkaehtgjklhsaegrklhawrg FFFFFFFUUUUUUCCCCCCCKKKKK
Maybe spoilers
JFC, I had the explode correct this whole time. /facepalmMy problem was the order of operations for the "split" step; each iteration of
split
should only process one (the leftmost) value. After that it should return to try an explode step before splitting any additional values.And with that out of the way, p2 is easy enough. I'll come back for the writeup tomorrow.
For anyone else stuck on this one, make sure you've got your order of operations correct, don't get ahead of yourself or make any more changes for a given step than exactly what's specified.
My split code worked fine almost immediately, my explode code is a pile of hot garbage that is really fucking with me and this problem is really hard to debug
Edit: So I did some string reversal to find the 'first' number when you go to the left during an explosion, but I didn't account for reversing the digits of a 2-character number. So 17 + 8 became 79. Fuck.
Edit 2:You were right, part 2 was basically trivial
Day 18 (Rust)
This is definitively my least-favorite puzzle so far. On Day 16 it was at least possible to write a proper parser, but this seems to depend on the order of inputs in a serialized string. I could probably have jury-rigged something with tree-traversal (in-order? pre-order? post-order?), but it was simpler this way.
Overall it felt like tedium for tedium's sake (well, it was homework after all...)
Data structures
Parsing
Helper functions
Solving
Alright, here's my messy unoptimized solution:
Core logic
Split
"Split" was the "easiest" part because it seemed like the most straight-forward. Unfortunately, it was "too easy" because I quickly implemented a recursive split function that actually introduced a subtle bug that only became apparent when the solutions required snailfish numbers that had multiple "splittable" values at once.
The specification requires that you only ever split one number at a time, and then check for "explodable" numbers before splitting the next number.
So here's the (much less elegant) recursive "split-leftmost" piece (plus some supporting helpers):
This also introduces the theme of complex return values that include some information about the state of the recursive operation that influences the work further up the stack. This came more naturally after my horrific solution for "explode".
Explode
When reading the problem description, my first thought was "hey, this moving numbers left and right deal looks tricky to solve in a tree, maybe it'll be simpler if I just try to keep it as a flat string and work it that way?", and my second thought was "nahh, surely part 2 will be something fiddly that relies on tree traversal, I'll just do it as a tree, how hard could it be?". After banging my head on the wall for hours, solving it with a tree, and then going back and looking at the other string-based solutions, I definitely should've just gone with my first instinct.
Anyway, here goes:
This isn't really as bad as my first take, even with the overly complex return value hashes, but being super explicit about absolutely everything helped me think through it a bit. For a while I kept thinking/worrying that I was supposed to apply explode to both branches at once and somehow merge the "going_left" and "going_right" values back into the siblings, and it was tracking that down that finally helped me realize that each iteration of "explode" must only explode one pair. This ensures that it's fine to stop recursing as soon as I find one pair that does explode.
For the longest time I kept trying to wrap my head around how best to find the next/previous simple number in the tree and propagate up and down the tree in some kind of clean recursive style. I think I might've ended up needing a structure with parent-pointers or something to do that properly. Instead, I went with this complex return value hash that indicates whether the node exploded and what values should be moved left or right, and that can propagate back up the call stack until the number lands somewhere.
Reduce
Magnitude
Thankfully, this one is actually as straightforward as it looks
Part 1 Ruby
With all that in place, the actual solution is pretty easy (the input being valid list notation for use with
eval
is a nice plus).Part 2 Ruby
And this is quite simple too, we just leverage the stdlib
combination
method and check each of(a+b), (b+a)
.Sweary Rant
Fuck everything about this question and especially his shitty way of explaining what he wants you to do for a problem. Don't make what is actually quite an interesting task into a monumental brainfuck by using such a difficult, convoluted and tangled explanation. That not what makes a puzzle interesting. We get enough of this ambiguous hard to understand language at work and in our daily lives.Today was almost as bad as the goblin game.
Especially fuck the shitty code I wrote and the 10s run-time.
I'll revisit this abomination sometime later now that I actually understand what the problem is asking.
Part 1
Part 2
I didn't like today's puzzle, but I don't think that's fair. Re-reading the explanation after I cooled off from my own frustration, I think it is quite well done – it's just the puzzle itself that is convoluted and thus needs a long explanation.
The explanation is very condensed; every single sentence must be read, related to the others, and understood, in order to solve the puzzle. Skipping around or otherwise letting a sentence slip by results in mistakes, but thankfully the examples were plentiful and exhaustive.
That said, today 1/3rd of my code is unit tests, and writing those takes time you don't have if you compete for the leaderboard...
I'm glad you replied. This is one thing that sets tildes apart - elsewhere you'd just have downvoted and run away. Please don't take this as argumentative, I just want to point out a couple of things:
First I won't ignore or suppress my feelings. I was just blowing off steam when those feelings are still their most raw. I concede it might have been somewhat hyperbolic :) - I very much like doing these puzzles.
Maybe your own frustration and all the other comments about how poorly people understood the problem on their way to solving it are evidence that it isn't well explained. You feel it's a good explanation for a concept that you (now!) fully understand. I still think it's convoluted and obfuscated and I maintain it's a poor explanation for a concept you don't understand.
I'll turn down the hyperbole in future!
Python
Finally finished solving this one. It was a real struggle.
Python
Elixir
I thought this one was fun! I had a few bugs to iron out before I got it working, but the abundance of examples to test against really helped make them easy to find.
Both parts