π - 2025 DAY 9 SOLUTIONS - π
Day 9: Movie Theater
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)
- You can send code in code blocks by using three backticks, the code, and then three backticks or use something such as https://topaz.github.io/paste/ if you prefer sending it through a URL
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
Uiua
Part 1 was easy, part 2 is ...less so...
::: spoiler a hint that might help you visualising the data reveals some interesting patterns. Follow link to do so. :::
Any way, here's my Part 1 while I'm still thinking about this.
Part 2
This is basically me thinking out loud, so it's untidy, brutal, repetitive and slow. (20s in the pad) link if you care
Using un-csv is smart :o
Much nicer to look at than the double partition I've been using so far
That part 2 scares me though :P
Thereβs so many great features hidden behind βunβ.
Iβm sure that part2 could be simplified significantly but I was fed up by then.
Javascript
I generally like trying the brute force approach first (though in the latter days it just wastes time). After exhausting 32GB of RAM I changed approaches.
Admittedly I don't really understand these algorithms as well as I would like, and I also will admit I inadvertently was LLM assisted :(. When I was trying to figure out what algo I should use and the AI summary in Google's search spoiled me. But I did learn alot.
One interesting observation, unrelated to the solution itself, is the solutions runs about 30% faster in Firefox than it does via Node (25.2.1 and 24.11.1) on my machine.
::: spoiler Code
:::
That is surprising about Firefox - you'd have thought something that literally just runs JavaScript would be able to beat Firefox with all the UI, sandboxing, etc. 30% is a huge margin.
C++
Correct answer in under 2 seconds, ie. about a hundred times longer than the rest of the puzzles so far combined. Can't think of a more efficient way to do this; no doubt it'll come to me at two in the morning, like usual.
Part 2 implementation breaks down the 'green tiles outline' into a series of line segments and then uses the even-odd winding rule to decide whether a point is inside or outside. Tracing the outline of the potential boxes to see whether the entirety is inside allows selection of the largest. We can skip tracing any potential box that wouldn't be bigger than what we have so far, and since the tests are easy to parallelise, have done so.
My initial attempt on even-odd got me in trouble with the outline, as given, being 1 unit wide, not zero-width. I had to compute normals to find out outline around the tiles. You write "on the line -> outside" but don't many legal rectangle candidates sit on the edge?
On the line -> inside. The calculation I've done is a bit tricksy - vertical lines include the endpoints but horizontal lines exclude them, so that the even-odd rule is followed properly if you're on the same row as corners. No lines overlap.
I'd considered using the dot product to work out whether the line turned left or right, but there's a lot of cases to consider and get right. Fair play on computing the normals.
How would you then distinguish between the situations in these two examples?
Haskell
This is pretty ugly. I got rather fed up after trying out various heuristics when the test case passed but actual data didn't.
Go
Oh my god. I'm calling: tomorrow will probably be the day I fail lmao. It has been so hard to get a correct result. Then I spent a solid half hour to optimize the solution so that :
It seems that using a
int8to encodecolorinstead of the defaultint64was the right idea lmao. I'm also happy to have understood how to multithread a Go application and I'm totally bought by the simplicity. This language really helped me by abstracting everything in goroutines and call it a day.I have also been able to create small goroutines which job was to generate values one by one and abstract the real control-flow in order not to allocate a whole big array at once, reducing further my memory footprint.
This is the second time in my life I want to say I loved using Go. Last time was when I waited to pretty print json files, my colleague told me how to write a wannabe-oneliner in Go to do it myself. Never looked back.
::: spoiler day09.go
:::
Futhark
First, formatting the input with an unreadable sed script:
::: spoiler formatting script
:::
Then, the actual program. main is the default entrypoint, part one is trivially solved in the preparations for part two. In part two, the faster check is to look for any point inside the current rectangle. If this can't find any, it'll have to check whether any edge crosses through the rectangle with a simple range check. I'm not happy with the performance, I feel like I left a lot on the table.
::: spoiler As always, wonky syntax highlighting
:::
Kotlin
My solution can't be very good because it took over a minute to run part 2. I used a ray casting algorithm to determine if a point was in the main polygon. I did that for the 2 corners of each rectangle that weren't given as input. If both of those corners were in the polygon, then I checked each point of each edge of that rectangle.
My first stab at this didn't consider the edge points of the rectangle, and after looking at some visualizations of the polygon, I could see where I was going wrong. I wouldn't have considered a rectangle with all 4 corners in the polygon could have an edge that goes outside the polygon without looking at a visualization.
Part 2 code:
Rust
The problem today just flustered me. Sometimes I'm not convinced I've gotten any better at programming since 2022 (yes, I still used that exact hack of a
combine_rangesfor this problem) Runs on my Thinkpad P1 Gen2 in 180.9 +- 0.9 ms on external power (or 726.8 +- 2.1 ms on battery; can't remember how I configured performance/battery saver honestly lol) ::: spoiler solution:::
Guile Scheme
Runs in 3.2 seconds.
I quickly got part 2 two to work on the example input, but i struggled a lot with bugs in the polygon-test code before getting the right result. To find my bugs, i made a visualization using raylib. It's not part of the snippet, but you can find the full code here.
Yes I used a polygon library so what... I did eyeball the shape and guessed what the result should have been like but still more pleasing to write something that applies generally. Although I did put an area threshold (eyeballed) which subsets the corners to test, it only reduces run time to about 1/2 (from ~10s to ~5s) so that is not necessarily very critical. If I hadn't used this library, I would probably do sth along the lines of defining my own rectangle classes with unions, intersections etc so wouldn't be a more clever approach but much more time consuming.
Even with using
geoin rust, i am still struggling, so no shame in using the library. I did get the solve in 2m32s, but I dont feel like this is optimal.Surely there is a better solution somewhere.
I solved with
geoas well. Brute force to all possible rectangles from the polygon points. Tried to run it but after 30 seckilled it and just importedrayon. It's under 3 sec. There is no shame using libraries, it's part of the puzzle to know whics one is useful πI find it helpful to use a library to get the solution, and then work backwards to replace the library. I got rid of geo and got mine down to milliseconds.
Python
Part 2 is only partially optimized (runs in ~7.5s).
The optimization I did do is to compress the grid coords by recording all red tiles and their adjacent cells only (thanks Everybody,Codes #15)
The part which needs further optimization is my approach to getting the largest rectangle in the polygon. I did it by flood-filling the area outside the polygon, then walking through all cells of every pair of red tiles to make sure their rectangle is within the polygon.
::: spoiler click to view code
::: ___
Rust
This one broke me, I couldn't do it without looking at some of the solutions in this thread. In the end, basically all that was necessary for part 2 is to check for every candidate rectangle if:
Plus a bunch shifting numbers around by 1. The code is not pretty at all, but at least it turned very efficient, solving part 2 in just 4.3ms. I have no idea how generalized the solution is. It definitely assumes that any larger rectangle is spanned inside the area and that the path doesn't cross over itself. Also of course that there are no two corners next to each other forming a 180 degree turn.
View on github
::: spoiler Code
:::
Rust
pt2: 71ms.
Wow. What a step up in difficulty. Using the
geolibrary got me the right answer, and quickly in terms of code, but run time was terrible (2m+). Switching back to simply checking if a wall is entirely outside the rectangle got me a much faster win, and the code isn't too bad either.::: spoiler Code and algorithm description (A wall is outside if its entirely to the left OR entirely to the right of the rect) OR (entirely above OR entirely below).
:::
Uiua
My silly part one solution, because I have no idea how to do part two (this is usually the difficulty at which I bow out, but I want to try actually expanding my knowledge this year!).
C
Well this one got me stumped for a bit but after a few false starts, lots of debugging and some cleanup I'd say I have a pretty good solution now! The points being tiles rather than zero-width corners was the tricky bit here.
Basic working: it walks through the points, keeping track of normal vectors (the "outside" direction). With those, it generates the zero-width outline around the points, and from there on it's pretty much old fashioned line intersection checks.
The initial nasty solve took 1.2 seconds on my Raspberry Pi 400, but some optimization (like sorting the edges list) brought that down to 0.08s on the Pi, and 0.02s on my 10-year old PC, and I'm quite happy with the code now.
::: spoiler Code
:::
Haskell, part 2
I broke down the outline into a set of boxes by scanning over them.
The fiddly bit was handling all the cases for shape subtraction. I don't give it here because it's just a slog, but the gist is this:
The idea: take a bounding box that's just large enough to cover all coordinates. From that, subtract the set of boxes above. You get a set of boxes that are in the "outside", ie, illegal region. [I did it this way because managing shape subtraction from the set of "inside" boxes is just more work.]
Then for each candidate rectangle, if it overlaps with any of the "out-of-bounds" boxes, it's not a solution.
Kotlin
This one was a journey.
Part 1 is just a greedy search over all the corner pairs to find the largest area.
I began part 2 with just using Java AWT, which worked but was slow. I didn't want to implement a proper polygon intersection algorithm and found a few patterns in the input.
Basically: All lines are axis aligned and all lines are orthogonal to the lines before and after them. This allows me to easily partition and sort the lists of lines. Because all the lines are axis-aligned and the lines are allowed to be on the edges of the rectangles, a smart check against all the lines can be constructed. Horizontal lines above and below and vertical lines to the left or to the right of the rectangle can be skipped entirely. This cuts the time down tremendously.
I needed to implement a custom binary search, though. The standard lib only supplies a search that gives any element that matches the target, not specifically the first one.
Code on GitHub ::: spoiler Code
:::
Uiua
Part 1 only for now, probably not the most optimal solution (calculating all possible rectangles) but at least it's short ^^
Run with example input
::spoiler Code
:::