8
votes
Day 7: Laboratories
Today's problem description: https://adventofcode.com/2025/day/7
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>
Wow I burned about 2 hours way overengineering a solution that made Part 02 impossible to solve. Only to realize while writing a debug visualization that both are actually incredibly simple. And then did both parts in 5 minutes with a dozen lines each.
Don't assume a problem's hard folks! You might waste a lot of time writing a hard solution for something that's actually very simple. (Still have to relearn that lesson the hard way sometimes, even after a decade+ experience.)
Part 1
Part 1
Surprisingly easy problem - I'd thought we would be getting to the more involved ones by now. I liked the part 2 twist, though. The trick here is one that's been used in prior puzzles, so I figured it out pretty quickly, but I did have to scrap all my original part 1 code.
Solution (Lily)
Looking at this actually made me curious — why was an unconditional
beam_counts[i (+/-) 1]working? Did your input just not have a splitter on the far edges? And then I looked at my input and saw mine doesn't, either! It seems likely that's something that was forbidden in the input, so every input always leaves a blank space on either side at the bottom, but it isn't something I'd considered when I was writing my solution.I didn't think about that case, actually! A splitter on the left edge wouldn't crash my program, since Lily supports Python-style negative indexing on lists, but the answer would be wrong.
Another convenience of the input I noticed is that the starting location is always in the exact center of the first row. So, I could simplify that line by just dividing the size by 2 instead of manually searching for the first
Scharacter.When I saw the problem, I could smell what part 2 would involve from a mile ahead. I feel like the problem could have been made a bit more interesting if they also had beams going at an angle, like with a splitter that splits incoming vertical rays into two rays going at 45° and straighten incoming 45° beams.
I experimented with a bunch of small optimizations like swapping my two vectors instead of discarding one every time or keeping track of the range of values that might contain a ray for the inner loop, but neither lead to significant enough improvement to really justify the increased complexity.
Solution (Rust)
Pretty easy day for the problem itself, so I used the opportunity to try a couple new things: DCGs and "Association Lists" (why does every language have to have their own name for this?).
I'm not convinced using a DCG for this style of parsing made it any easier (it certainly didn't make it more succinct), but I can certainly see how it could be useful for more advanced parsing.
I have to say, while Prolog is growing on me, I'm still feeling silly writing ~9 lines to update a couple values in a data structure. It's not that it's that hard, I'm just hoping there's a more idiomatic approach I don't know about.
Prolog Solution
I agree with the rest of the commenters that this was a surprisingly easy day. I enjoy when it's feasible to do both parts in a single pass over the input!
I was honestly a bit confused at first on what the second part actually meant, because I couldn't make the numbers add up in my head. Why would the number of timelines be less than the number of splits? And then I realized I was being dumb and comparing the number of splits from my input to the number of timelines from the test input and yes, it was in fact exactly what I thought it would be. From there tacking part 2's logic onto my part 1 solution was simple and the whole thing comes in under 30 lines of code.
Time in release mode is ~20 µs, which is insane. One thing I always enjoy about Advent of Code is that it reminds me just how fast modern computers can be.
Rust Solution
Same! I really shot myself in the foot by thinking it couldn't be done in a single pass when I started, only to realize while writing a debug visualization, that the visualization was a single pass solution.
I didn't come up with a clever single-pass solution, but I can confirm it's feasible to solve with recursion with memoization. Still runs in about 70ms on my machine, which is good enough for me.
Solution part 2 (Crystal)
Elixir
I played this one with the big handycap of refusing to treat the obvious grid as a grid. This has made it much more lengthy and complex than it should've been, especially now that I look y'all's solutions.
My solution instead scans the input once, looking at two lines at a time. (which obviously would've been just as feasible with a grid too!)
Then, the chars above each other are compared to then generate a potential output.
So (top: |, bottom: ^) would generate (left: |, middle: ^, right: |).
These chunks are then recombined to create the new output line. (By priority: if any potential chunk has a ^, take that, then check for |)
For the second part, I do the same but also create all permutations of the new line value.
To make the program go fast enough, I then cull all timelines where the last line is the same and keep a running total of how many source-timelines exist for that state so the count is still accurate. This was clearly necessary to not end up in exponential-hell, memoization would've been an alternative solution.
I'm not too happy with the runtime, but it's fine after that optimization.
I'm getting tons of practice thinking in lists, recursion and such, but I don't really enjoy having to track little numeric counters and such through tuple types instead of mutating a sum somewhere.
Overengineered Code
Benchmarks
TypeScript
Yesterday was busy for me so I'm playing catch-up now. This was the first puzzle that tripped me up! I breezed through Part 1 with a nice single-loop solution but walked right into the trap of doing a naive recursive brute-force approach for Part 2 and hit my first OOM crash of the year.
I guess I've "done" dynamic programming in the past but never called it that. Usually I'm thinking in terms of managing React state and minimizing re-renders by memoizing expensive function outputs, etc. I'm not used to considering this kind of problem in the same way.
I don't think it's cheating to lean on ChatGPT a bit when you get stuck in AoC, as long as you're earnestly tackling the problem, and working through different approaches — not just generating a full solution in one go. So in full disclosure, that's the route I had to take for this Part 2. Seems like most of you figured out the right approach straightaway, but it was not intuitive to me all! Just as I predicted, lol.
Parts 1 and 2
Benchmarks: The median input formatting time over 50 iterations is 22µs on my machine. Part 1 is 74µs and Part 2 is 160µs.
Elixir
Pretty straightforward, just count the unique splitters encountered for part 1 and count the total number of unique paths for part 2. This was another good candidate for my new
StringGridstruct.Both parts
I went back and smushed everything into fewer lines for no obvious reason. (Certainly not readability...)
Benchmarks
Coming back to the days I hadn't completed because of Real Life Obligations.
This one seemed alarmingly easy. All of the edge cases were conveniently removed from the input, so no cases of side by side splitters or splitters on the grid edge that would make this single pass a problem. I think it would have been interesting if they did some kind of diamond shape with wrap-around requirements. If there were items on the grid edge, I would have just done the classic trick of grid problems where you just bump the grid size by one and ignore it later.
Parts 1 and 2