❄️ - 2023 DAY 10 SOLUTIONS -❄️
Day 10: Pipe Maze
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
🔒 Thread is locked until there's at least 100 2 star entries on the global leaderboard
🔓 Unlocked after 40 mins
24 replies
Rust Solution
Used Shoelace Algorithm to get the interior area and then Pick's Theorem to get the number of interior points based on the area and the points along the boundary loop.
It's so humbling when you've hammered out a solution and then realise you've been paddling around in waters that have already been mapped out by earlier explorers!
Dart
Finally got round to solving part 2. Very easy once I realised it's just a matter of counting line crossings.
Edit: having now read the other comments here, I'm reminded that the line-crossing logic is actually an application of Jordan's Curve Theorem which looks like a mathematical joke when you first see it, but turns out to be really useful here!
The squeezing component in part 2 made this really interesting.
I had a thought on a naïve solution consisting of expanding the input grid, painting all the walked pipes, and then doing a flood fill from the outside of the expanded map. There are a lot cleverer ways to do it, but the idea stuck with me and so...
The code's a bit of a mess, but I actually kind of like the result. It visualizes really well and still runs both parts in under 8 seconds, so it's not even particularly slow considering how it does it.
E.g;
:::spoiler Ruby A snippet from the expansion/flood fill;
:::
With the fully expanded map for the actual input it ends up working a 420x420 tile grid, and it has to do both value lookups as well as mutations into that, alongside inclusion testing for the search array (which could probably be made cheaper by building it as a set). It ends up somewhat expensive simply on the number of tests.
The sample I posted the picture of runs in 0.07s wall time though.
Maybe you are adding the same point multiple times to to_visit. I don't know ruby but couldn't see a check for visited points before adding, and to_visit appears to be an array instead of set, which can store the same point multiple times.
There's a
next if [...] to_visit.include?(off_p), and I also only visit points that haven't been flood filled yet (next unless %w[. I].include? val), so there shouldn't be any superfluous testing going on.Went back and did a quick test of thing, and yep, converting the to_visit array to a set pulls execution time down to ~600ms. But the code becomes much messier.
Going to move the mutation of the map down to the point where I pick a point for visitation instead, so I can re-use the check for already flooded tiles instead.
Thanks for that state machine. I was a bit lost in part 2, but after reading this, it totally makes sense.
Raku
My solution for today is quite sloppy. For part 2, I chose to color along both sides of the path (each side different colors) and then doing a fill of the empty space based on what color the empty space is touching. Way less optimal than scanning, and I didn't cover every case for coloring around the start point, but it was interesting to attempt. I ran into a bunch of issues on dealing with nested arrays in Raku, I need to investigate if there's a better way to handle them.
View code on github
Edit: did some cleanup, added some fun, and switched to the scanning algorithm for part 2, shaved off about 50 lines of code.
:::spoiler Code
:::
C# I had fun with this one and overbuilt it somewhat. Disappointed I didn't get/need to implement Dijkstra's algorithm.
For part two I did a x2 expand, so the example input expanded and found the middles like this (0s for the targets as their easier to distinguish):
::: spoiler Part 1
:::
Part 2 is too big to post... see pastebin link
Scala3
forgot to post this
Rust
This one was tricky but interesting. In part 2 I basically did Breadth-First-Search starting at
Sto find all spots within the ring. The trick for me was to move between fields, meaning each position is at the corner of four fields. I never cross the pipe ring, and if I hit the area bounds I know I started outside the ring not inside and start over at a different corner ofS.Part 2 runs in 4ms.
Nim
I got a late start on part 1, and then had to sleep on part 2. Just finished everything up with a little time to spare before day 11.
Part 2 probably would have been easier if I knew more about image processing, but I managed:
.tiles.tiles that are connected to the outside edges of the grid withOtilesOtile at each step. Stopped when one was found, and recorded whether it was to the left or right of the path..tiles withI, and counted themCode:
btw if you put the url to nim as /c/[email protected] I dont think the url bot will trigger since it does the same thing the ! format does
Nim
Edit: yeah looks like it didnt reply to me so this format works
Unfortunately then it'll be broken for kbin users. I can do it anyway though if the bot is too annoying, just lmk.
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]
I always felt I was one fix away from the solution, which was both nice and bad.
Walking the path was fine, and part 2 looked easy until I missed the squeezed pipes. I for some silly reason thought I only had to expand the grid by x2 instead of x3 and had to re-do that. Fill is hyper bad but works for <1 minute.
::: spoiler Python import re import math import argparse import itertools from enum import Flag,Enum
:::
huh... expand x2 worked for me. I wonder if I had a nice input set
Nim
I have zero idea how this functions correctly. I fear to touch it more than necessary or it would fall apart in a moment.
I got second star after 8 hours (3 hours for part 1 + 4 hour break + 1 hour part 2), at that moment I've figured out how to mark edges of enclosed tiles. Then I just printed the maze and counted all in-between tiles manually. A bit later I've returned and fixed the code with an ugly regex hack, but atleast it works.
Code: day_10/solution.nim
Language: Python
Github
Decided to use a graph to solve (which expanded the size). Part 1 was cycle detection, and part 2 was flooding of the outside.
Kotlin
Github
Double expansion seams like a nice approach, but I didn't think of that. Instead, I just scan the lines and “remember” if I am in a component or not by counting the number of pipe crossings.
Nim
This was a great challenge, it was complex enough to get me to explore more of the Nim language, mainly (ref) types, iterators, operators, inlining.
I first parse the input to Tiles stored in a grid. I use a 1D seq for fast tile access, in combination with a 2D Coord type. From the tile "shapes" I get the connection directions to other tiles based on the lookup table
shapeConnections. The start tile's connections are resolved based on how the neighbouring tiles connect.Part 1 is solved by traversing the tiles branching out from the start tile in a sort of pathfinding-inspired way. Along the way I count the distance from start, a non-negative distance means the tile has already been traversed. The highest distance is tracked, once the open tiles run our this is the solution to part 1.
Part 2 directly builds on the path found in Part 1. Since the path is a closed loop that doesn't self-intersect, I decided to use the raycast algorithm for finding if a point lies inside a polygon. For each tile in the grid that is not a path tile, I iterate towards the right side of the grid. If the number of times the "ray" crosses the path is odd, the point lies inside the path. Adding all these points up give the solution for Part 2.
Initially it ran quite slow (~8s), but I improved it by caching the tile connections (instead of looking them up based on the symbol), and by ditching the "closed" tiles list I had before which kept track of all the path tiles, and switched to checking the tile distance instead. This and some other tweaks brought the execution speed down to ~7ms, which seems like a nice result :)
Part 1 & 2 combined
::: spoiler Condensed version:
:::
Well, star one is solved. I don't love the code, but yet again, it works for now. I don't love the use of a label to continue/break a loop, and the
valid_stepsfunction is a mess that could probably be done much cleaner.Upon looking at star 2 I don't even have the slightest idea of where to start. I may have to come back to this one at a later date. Sigh.
https://github.com/capitalpb/advent_of_code_2023/blob/main/src/solvers/day10.rs
If you're still stuck on part 2, have a look at my comment which shows an unreasonably easy approach :-)