Skip to content

Commit 8580fd3

Browse files
committed
2025/09 cleanup
1 parent 3d4b21d commit 8580fd3

1 file changed

Lines changed: 36 additions & 114 deletions

File tree

2025/Day09/Solution.cs

Lines changed: 36 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -5,140 +5,62 @@ namespace AdventOfCode.Y2025.Day09;
55
using System.Linq;
66
using System.Numerics;
77

8-
record Segment(Complex a, Complex b);
9-
record Rectangle(Complex top, Complex left, Complex bottom, Complex right);
8+
record Rectangle(long top, long left, long bottom, long right);
109

1110
[ProblemName("Movie Theater")]
1211
class Solution : Solver {
1312

1413
public object PartOne(string input) {
1514
var points = Parse(input);
1615
return (
17-
from p1 in points
18-
from p2 in points
19-
select Area(p1, p2)
20-
).Max();
16+
from r in RectanglesOrderedByArea(points)
17+
select Area(r)
18+
).First();
2119
}
2220

2321
public object PartTwo(string input) {
24-
// The input looks like a circle with a slot in the middle of it:
25-
//
26-
// xxx
27-
// xxxxxxxxx
28-
// xxxxxxxxxAx
29-
// xx
30-
// xxxxxxxxxBx
31-
// xxxxxxxxx
32-
// xxx
33-
//
34-
// To speed things up we find the corners A and B first. Based on
35-
// visual observation one of them must be a corner of the final rectangle.
36-
//
37-
// Then go over the 'red' points in the upper and lower halves of
38-
// the picture and find the greatest rectangle.
39-
4022
var points = Parse(input);
41-
var segments = Segments(points);
42-
var shape = Draw(points);
43-
44-
var res = 0L;
45-
46-
var slot = Segments(points)
47-
.OrderByDescending(s => Area(s.Item1, s.Item2))
48-
.ThenBy(s=>s.Item1.Imaginary)
49-
.Select(s => s.Item1.Real > s.Item2.Real ? s.Item1 : s.Item2)
50-
.Take(2)
51-
.ToArray();
52-
53-
var upper = from p in points where p.Imaginary >= slot[0].Imaginary select (p1: slot[0], p2: p);
54-
var lower = from p in points where p.Imaginary <= slot[1].Imaginary select (p1: slot[1], p2: p);
55-
56-
var reactanglesByArea = (
57-
from ps in upper.Concat(lower)
58-
let p1 = ps.p1
59-
let p2 = ps.p2
60-
let top = Math.Min(p1.Imaginary, p2.Imaginary) * Complex.ImaginaryOne
61-
let bottom = Math.Max(p1.Imaginary, p2.Imaginary) * Complex.ImaginaryOne
62-
let left = Math.Min(p1.Real, p2.Real)
63-
let right = Math.Max(p1.Real, p2.Real)
64-
orderby Area(top+left, bottom + right) descending
65-
select new Rectangle(top, left, bottom, right)
66-
).ToArray();
67-
68-
foreach (var r in reactanglesByArea) {
69-
if (Inside(r.top + r.left, shape) &&
70-
Inside(r.top + r.right, shape) &&
71-
Inside(r.bottom + r.right, shape) &&
72-
Inside(r.bottom + r.left, shape) &&
73-
points.All(p => !InRect(p, r))
74-
) {
75-
res = Area(r.top + r.left, r.bottom + r.right);
76-
break;
77-
}
78-
}
79-
80-
return res;
23+
var segments = Boundary(points).ToArray();
24+
return (
25+
from r in RectanglesOrderedByArea(points)
26+
where segments.All(s => !AabbCollision(r, s))
27+
select Area(r)
28+
).First();
8129
}
8230

83-
84-
IEnumerable<(Complex, Complex)> Segments(Complex[] points) {
85-
return points.Zip(points.Prepend(points.Last()));
31+
IEnumerable<Rectangle> RectanglesOrderedByArea(Complex[] points) =>
32+
from p1 in points
33+
from p2 in points
34+
let r = RectangleFromPoints(p1, p2)
35+
orderby Area(r) descending
36+
select r;
37+
38+
IEnumerable<Rectangle> Boundary(Complex[] corners) =>
39+
from pair in corners.Zip(corners.Prepend(corners.Last()))
40+
select RectangleFromPoints(pair.First, pair.Second);
41+
42+
Rectangle RectangleFromPoints(Complex p1, Complex p2) {
43+
var top = Math.Min(p1.Imaginary, p2.Imaginary);
44+
var bottom = Math.Max(p1.Imaginary, p2.Imaginary);
45+
var left = Math.Min(p1.Real, p2.Real);
46+
var right = Math.Max(p1.Real, p2.Real);
47+
return new Rectangle((long)top, (long)left, (long)bottom, (long)right);
8648
}
49+
8750
Complex[] Parse(string input) => (
8851
from line in input.Split("\n")
8952
let parts = line.Split(",").Select(int.Parse).ToArray()
9053
select parts[0] + Complex.ImaginaryOne * parts[1]
9154
).ToArray();
9255

93-
long Area(Complex p1, Complex p2) {
94-
return (long)(Math.Abs(p1.Real - p2.Real) +1) *
95-
(long)(Math.Abs(p1.Imaginary - p2.Imaginary) + 1);
96-
}
97-
HashSet<Complex> Draw(Complex[] points) {
98-
var res = new HashSet<Complex>();
99-
var segments = Segments(points);
100-
foreach (var line in segments) {
101-
var a = line.Item1;
102-
var b = line.Item2;
103-
var d =
104-
Math.Sign(b.Real - a.Real) +
105-
Complex.ImaginaryOne * Math.Sign(b.Imaginary - a.Imaginary);
106-
for (var p = a; p != b; p += d) {
107-
res.Add(p);
108-
}
109-
res.Add(b);
110-
}
111-
return res;
112-
}
113-
114-
bool InRect(Complex position, Rectangle r) {
115-
return
116-
r.left.Real < position.Real && position.Real < r.right.Real &&
117-
r.top.Imaginary < position.Imaginary && position.Imaginary < r.bottom.Imaginary
118-
;
119-
}
120-
121-
// Check if position is inside the loop using ray casting algorithm
122-
bool Inside(Complex position, HashSet<Complex> shape) {
123-
// Imagine a small elf starting from the top half of a cell and moving
124-
// to the left jumping over the borders it encounters. It needs to jump
125-
// over only 'vertically' oriented pipes leading upwards, since it runs
126-
// in the top of the row. Each jump flips the "inside" variable.
127-
if (shape.Contains(position)) {
128-
return true;
129-
}
56+
long Area(Rectangle r) => (r.bottom - r.top + 1) * (r.right - r.left + 1);
13057

131-
var inside = false;
132-
position -= 1;
133-
while (position.Real > 0) {
134-
if (shape.Contains(position) && shape.Contains(position + Complex.ImaginaryOne)) {
135-
inside = !inside;
136-
}
137-
138-
position -= 1;
139-
}
140-
return inside;
58+
// see https://kishimotostudios.com/articles/aabb_collision/
59+
bool AabbCollision(Rectangle a, Rectangle b) {
60+
var aIsToTheLeft = a.right <= b.left;
61+
var aIsToTheRight = a.left >= b.right;
62+
var aIsAbove = a.bottom <= b.top;
63+
var aIsBelow = a.top >= b.bottom;
64+
return !(aIsToTheRight || aIsToTheLeft || aIsAbove || aIsBelow);
14165
}
142-
143-
14466
}

0 commit comments

Comments
 (0)