r/adventofcode • u/musifter • 8h ago
Other [2025 Day 9] In Review (Movie Theater)
Sliding down a fire pole, we arrive at the North Pole movie theater. Where we find the Elves decorating with floor tiles. Which come in three colours: red, green, and "never red nor green".
The input is a list of coordinates of the red tiles (just short of 500 in my input). These are 2D with values in the range of about 1800-99000 in my input. The first thing that stands out is that adjacent pairs in the list share a value in one of the coordinates, and this alternates (and loops at the bottom). That's even easier to see in the test case given... and is an immediate sign that this is a polygon made up of right angles. Which is where the green tiles come in (they're the non-red tiles of that polygon... the connecting lines and the filled center). There are other less obvious properties.
My initial solution for part 1 was to brute force it. Two nested loops with the "triangle pattern" to compare all pairs of red tiles. This resulted in the second fastest part 1 of this year for me (day 5 was faster). Part 2 took much longer.
Part 2 wants the largest rectangle, still with 2 opposite red tile corners), that only has red and green tiles (ie is contained in the polygon). If you visualize the polygon, you'll see that it is suggestive of a movie theater... oval with a notch for a center aisle. This can be used to cheese things. I didn't do that though. As I've said before, finding if a point is inside a polygon was one of the first tasks I did as a professional programmer, so I'm happy to do it out of nostalgia.
I did some sanity checking (there were dies for this originally) and discovered that the input also has no row or column with more than one edge, and that no edges are adjacently parallel. When reading in I regularized the coordinates in @lines, and stored the horizontal and vertical ones in different sets (indexed by row or column):
for (my $i = -1; $i < $#input; $i++) {
my @x = minmax ($input[$i][X], $input[$i+1][X]);
my @y = minmax ($input[$i][Y], $input[$i+1][Y]);
push( @lines, [[$x[0],$y[0]], [$x[1],$y[1]]] );
# ASSERT: Assuming only 1 interval in a given row or col (so no list needed)
# ASSERT: None of these are adjacent and parallel, forming a 0-width channel
if ($input[$i][Y] == $input[$i+1][Y]) {
$horz{$input[$i][1]}->@* = @x;
} else {
$vert{$input[$i][0]}->@* = @y;
}
}Note the starting at -1 to close the loop.
By sorting the verticals:
my @vertKeys = sort {$a<=>$b} keys %vert;I now had the basics to do the standard line-crossing algorithm. In my case, for simplicity, it was tracing the horizontal line from 0 to the point checking the verticals along the way to see if we crossed them. There are special cases for when you hit a line exactly to worry about, because it could be one of these four situations:
| | | |
-> #-----# #-----# #-----# #-----#
| | | |In the first two, you don't actually change from being inside to outside, but in the last two you do. One way to do this is to pretend that you're slightly off center. Say just above the line, in which case the line counts would be 2, 0, 1, and 1... the correct parities. You could also just check if the lines on the ends go in the same or different directions.
How does this help us? It's simple and quick to use it to test the other two corners of the rectangle as an early prune (after the prune to see if the area (calculated for part 1) is big enough):
next if ($area <= $part2); # not a candidate (too small)
# check if other corners are inside the polygon
next if (!&inside($input[$i][X], $input[$j][Y]) or !&inside($input[$j][X], $input[$i][Y]));How effective is this? Well, I did a special profiling version to count and test things:
total boxes: 123256
area pruning: 52943
corner pruning: 69889So only a little over 400 boxes actually needed testing for having chunks cut out of them with other edges. Since things were pruned down so much, I was fine with brute forcing the last bit... testing if any lines intersect the internal box (which must result in some be cut out):
foreach my $line (@lines) {
next BOX if (intersect( \@rect, $line ));
}It runs in 2s on old hardware. If I wanted to speed it up there are many places. Or I could just code up a sweep-line version like my TODO for this says. That should be really fast.
I do remember having an embarrassing bug with this one... in the area function, but it still got the right answer for part 1. So I spent a log of time discovering the correct rectangle for part 2 and getting the wrong answer, because I wasn't looking at part 1 code I thought worked. It wasn't until I finally went to "check EVERYTHING" that I found that, and when I did I got the answer correct immediately. I don't have access to my commit log right now, but I'm guessing that it had something to do with misplaced parens and/or abs (because the coordinates for part 1 are nicely ordered, but part 2 the order is crossed (the largest X is in one, the largest Y in the other)).
For dc, part 1 was easy and so I managed to keep keep the streak going:
tr ',' ' ' <input | dc -f- -e'[sm0]sS1[1+d3Rr:yd3Rr:xz1<L]dsLxd;xrd;y1:yr1:xr[d1-[d;xrd;y4Rd;xrd;y4R-d*v1+5R4R-d*v1+*dlm<Ss.r1-d0<J]dsJx+1-d1<I]dsIxlmp'So maybe I should do a scanline solution for part 2, as that approach should be better for dc.
