🎄 - 2023 DAY 8 SOLUTIONS -🎄
Day 8: Haunted Wasteland
Megathread guidelines
- Keep top level comments as only solutions, if you want to say something other than a solution put it in a new post. (replies to comments can be whatever)
- Code block support is not fully rolled out yet but likely will be in the middle of the event. Try to share solutions as both code blocks and using something such as https://topaz.github.io/paste/ , pastebin, or github (code blocks to future proof it for when 0.19 comes out and since code blocks currently function in some apps and some instances as well if they are running a 0.19 beta)
FAQ
- What is this?: Here is a post with a large amount of details: https://programming.dev/post/6637268
- Where do I participate?: https://adventofcode.com/
- Is there a leaderboard for the community?: We have a programming.dev leaderboard with the info on how to join in this post: https://programming.dev/post/6631465
Scala3
this is still not 100% general, as it assumes there is only one Z-node along the path for each start. But it doesn't assume anything else, I think. If you brute force combinations of entries of the zs-arrays, this assumption can be trivially removed, but if multiple paths see lots of Z-nodes, then the runtime will grow exponentially. I don't know if it's possible to do this any faster.
Nim
I like how if you have an error in your calculations, you end up wandering the haunted desert for eternity. Very flavorful.
My solution for part 2 is pretty much the same as for part 1, just modified to work on a sequence of nodes instead of a single node. However, it doesn't find an answer in the time that I was willing to wait for it. I thought about trying to optimize it to run faster, but figured that if it was taking this long on Nim, then interpreted languages would have no chance, so that couldn't be the right approach.
I suspected that maybe the ghosts arrived at the Z locations at predictable intervals. I added some code to output the step count each time each ghost reached a Z (see commented code), and my suspicion was correct. Just needed to calculate the least common multiple of the 6 cycle lengths.
Hi there! Looks like you linked to a Lemmy community using a URL instead of its name, which doesn't work well for people on different instances. Try fixing it like this: ![email protected]
APL
I'm using this year's AoC to learn (Dyalog) APL, so this is most likely a pretty terrible solutions. I would've liked to use
⍣instead of of my imitation of a while loop with a recursive function, but I couldn't figure out how to get to the number of iterations ⍣ performed to arrive at the destination. If someone here knows how to do that (or has other suggestions for improvement) I'm open for suggestions.Rust
As others have shown, part 2 can be pretty simple if you allow one assumption: The distance from a start point to the nearest end point is always the same as cycling from that nearest end point back to itself. Under that assumption you can just take the lowest common multiple of these distances. And honestly, who can claim to understand ghost navigation and what you can and can't assume? Empirical evidence suggests that this is how ghosts travel.
Personally, I'm not a fan of requiring analysis of the individualized input to reach the correct (sufficiently efficient) solution for part 2. Or maybe I'm just resentful because I feel like I've been duped after writing an generalized-to-the-puzzle-description-but-insufficiently-efficient solution. 😔
These quantum ghosts need to come back down to reality.
Yeah I got annoyed too. Because this is not implied in the problem statement and it definitely doesn't hold up in general...
Perhaps there's a mathematical way to prove that this assumption will actually always happen despite the input? I wanted to test this assumption, and edited the map and randomly changes the destinations for keys ending in Z, and it looks like the matches are still at consistent intervals. Is it possible to have an input map which breaks the assumption?
I can prove the opposite for you. The assumption that Gobbel2000 describes is wrong in general. For example, take
the first Z is reached after 3 steps, but then every cycle only takes 2 steps.
The matches are still at consistent intervals, but you can easily find a counterexample for that as well:
now the intervals will be 2, 1, 2, 1, ...
However, it is easy to prove that there will be a loop of finite length, and that the intervals will behave somewhat nicely:
Identify a "position" by a node you are at, and your current index in the LRL instruction sequence. If you ever repeat a position P, you will repeat the exact path away from the position you took the last time, and the last time you later reached P, so you will keep reaching P again and again. There are finitely many positions, so you can't keep not repeating any forever, you will run out.
Walking in circles along this loop you eventually find yourself in, the intervals between Zs you see will definitely be a repeating sequence (as you will keep seeing not just same-length intervals, but in fact the exact same paths between Zs).
So in total, you will see some finite list of prefix-intervals, and then a repeating cycle of loop-intervals. I have no idea if this can be exploited to compute the answer efficiently, but see my solution-comment for something that only assumes that only one Z will be encountered each cycle.
Thank you for this, it really helped me understand this more.
I crafted a simple counter-example (single letters for brevity). The way the sequence goes totally depends on the instructions, and we don't have any guarantees on that. It could be anything. Of course, looking at the input data we could find what the instructions are, but the assumption doesn't hold in general.
Here the distance of Z cycling back into itself could be 2 or 4, depending on what the instruction string is doing.
re [^1]: yeah, but that may explode the runtime again. Do you have any idea if this is possible to solve without brute forcing the combinations?
I kind of agree, props for getting a general solution. I actually realized this issue only after I got both stars and didn't feel like changing it again.
Ruby
[email protected]
Man I did not understand how to solve this one (part 2, p1 was easy). After I understood it and looked at other people's solutions I was able to extend mine quite easily...
https://github.com/snowe2010/advent-of-code/blob/master/ruby_aoc/2023/day08/day08.rb
Language: Python
::: spoiler Part 1
First part was very straight-forward: read the input in and simulate the navigation. Taking advantage of itertools.cycle made cycling through the instructions very easy :]
:::
::: spoiler Part 2
The second part was also straight-forward: locate the ghosts, and then navigate from each ghost until you hit an element that ends with a 'Z'. The trick to avoid brute-forcing is to realize that the ghosts will cycle (ie. repeat the same paths) if they all don't land on an element that ends with a 'Z' at the same time. To solve the problem, you just ened to calculate the steps for each ghost to reach an endpoint and then compute the lowest common multiple of those counts. Fortunately, Python has
math.lcm, so not much code was needed!:::
GitHub Repo
Dart
Part 1 was easy enough. For Part 2 I took a guess that the cycles were all well behaved and I could get away with just calculating the LCM, and it paid off.
Crystal
![email protected]
part 1 was made way simpler after I made it a function for part 2
also I accidentally saw someone elses code, so I can't take full credit for figuring out how to do part 2
C#
Day 8
First part was simple enough. Second part was easy logically, but after running the brute force solution with a parallel iterator from
rayonand maxing out all 12 cores of this CPU, it was still taking forever. I always get tripped up by these ones that need fancy math, because although I was always good at math, I've never been good at looking at these problems and figuring out what kind of formula would apply. So I cheated and looked at other people's comments for their solutions, and saw the least common multiple mentioned. This made sense to me, so I implemented it and got a result almost instantly. I hate having to look at other comments to solve these things, but I never would have came to that conclusion myself.The code still isn't the cleanest, and I'd love to tidy up the parsing, but it works and I'm happy.
https://github.com/capitalpb/advent_of_code_2023/blob/main/src/solvers/day08.rs
[language: Lean4]
I did not make the assumption that the distance from the start to the goal is the same as the cycle length. This made the problem a bit harder, but I'm pretty sure my solution is actually generic. As always, I'll only post the direct solution. Helper functions (like, in this case, xgcd) are in other files, you can find them together with the full solution on github.
Since my solution contains a ton of comments (that outline my thought process), I'll again split it in several posts here.
:::spoiler Part1
:::
Actually, all my comments in Part 2 make it too big for a single post. The full solution consists of two functions (that could be combined with a bit more work...).
The first function is a brute-force approach, that has a similar termination condition to the first part. It halts, once all paths are walking in circles. This function does not find a solution yet. Then there is a second function that looks for solutions that appear later, while all paths are already walking in circles. That function writes all goals as equations of the form
x = start + n * period. The target condition is that all paths have the same value forx. This was then solved by looking at all permutations between two paths, and solving the Diophantine equations, creating a new, combined, path.In the end, the closest resulting valid goal was also the final result.
:::spoiler Part2, the Brute Force function
:::
And last, but not least, here's the part that actually finds the result: :::spoiler Part 2, the part for after the brute force approach fails (don't be alarmed, it's mostly comments, very few actual code lines)
:::