Skip to content

Commit a857a88

Browse files
committed
chore: more descriptive docstrings
1 parent ce302f0 commit a857a88

File tree

2 files changed

+56
-35
lines changed

2 files changed

+56
-35
lines changed

_2025/solutions/day09.py

Lines changed: 48 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
1-
"""Boilerplate solution template for Advent of Code daily challenges.
1+
r"""Day 9: Movie Theater
22
3-
This module provides a template class for solving Advent of Code puzzle problems.
4-
It includes a base structure with two method stubs (part1 and part2) that can be
5-
implemented for specific day's challenges.
3+
This module provides the solution for Advent of Code 2025 - Day 9
64
7-
The template follows the SolutionBase pattern used across the Advent of Code solutions,
8-
allowing for consistent handling of input parsing and solution execution.
5+
It finds the largest axis-aligned rectangle that can be formed in a tile grid
6+
using red tiles as opposite corners (Part 1), then extends the search to allow
7+
rectangles that include both red and \"green\" tiles (Part 2).
8+
9+
The module contains a Solution class that inherits from SolutionBase and
10+
implements coordinate compression, flood fill, and rectangle validation
11+
to efficiently search the space of candidate rectangles.
912
"""
1013

1114
from collections import deque
@@ -14,43 +17,48 @@
1417

1518

1619
class Solution(SolutionBase):
17-
"""Solution template for Advent of Code daily puzzle.
20+
"""Find largest valid rectangles in a movie theater tile grid.
1821
19-
This class provides a standardized structure for implementing solutions to
20-
daily Advent of Code challenges. It inherits from SolutionBase and includes
21-
method stubs for part1 and part2 of the puzzle.
22+
The input is a list of coordinates where tiles are red. Part 1 treats the
23+
rest of the grid as unconstrained and finds the largest possible rectangle
24+
using any two red tiles as opposite corners. Part 2 connects the red tiles
25+
into a loop of red+green tiles and flood-fills the interior to mark all
26+
green tiles, then searches for the largest rectangle that only contains
27+
red or green tiles.
2228
23-
Subclasses should override these methods with specific implementation logic
24-
for parsing input and solving the puzzle requirements.
29+
Coordinate compression is used to keep the grid small, and a brute-force
30+
rectangle search is combined with a grid mask to validate candidate areas.
2531
"""
2632

2733
def part1(self, data: list[str]) -> int:
28-
"""Solve the first part of the daily puzzle.
34+
r"""Find largest rectangle area using two red tiles as opposite corners.
35+
36+
For every pair of red tiles that differ in both x and y, this method
37+
computes the area of the axis-aligned rectangle they define and tracks
38+
the maximum. The actual grid size does not matter due to the use of
39+
red tile coordinates only.
2940
3041
Args:
31-
data: List of input strings to be processed
42+
data: List of \"X,Y\" strings representing red tile positions
3243
3344
Returns
3445
-------
35-
int: Solution for part 1 of the puzzle
46+
int: Largest rectangle area using two red tiles as opposite corners
3647
"""
3748
tiles = [tuple(map(int, line.split(","))) for line in data]
3849

3950
if len(tiles) < 2:
4051
return 0
4152

42-
# Coordinate compression ("space distortion")
53+
# Coordinate compression (space distortion)
4354
xs = sorted({x for x, _ in tiles})
4455
ys = sorted({y for _, y in tiles})
4556

4657
x_to_idx = {x: i for i, x in enumerate(xs)}
4758
y_to_idx = {y: i for i, y in enumerate(ys)}
4859

49-
# Compressed positions of red tiles
5060
compressed_tiles: list[tuple[int, int]] = [(x_to_idx[x], y_to_idx[y]) for x, y in tiles]
5161

52-
# Group tiles by compressed row/column if you want small pruning later
53-
# but the brute-force over all pairs is already fine for AoC sizes.
5462
max_area = 0
5563
n = len(compressed_tiles)
5664

@@ -79,35 +87,45 @@ def part1(self, data: list[str]) -> int:
7987
return max_area
8088

8189
def part2(self, data: list[str]) -> int:
82-
"""Solve the second part of the daily puzzle.
90+
r"""Find largest rectangle area using only red and green tiles.
91+
92+
First, the red tiles are connected in input order with axis-aligned
93+
segments forming a loop; these segments become green tiles. Then, a
94+
flood fill from the outside marks all empty tiles reachable from the
95+
boundary as \"outside\". Any remaining empty tiles inside the loop are
96+
also marked green. Finally, the method checks all red-opposite-corner
97+
rectangles and keeps the largest whose interior contains only red or
98+
green tiles (no empty tiles).
8399
84100
Args:
85-
data: List of input strings to be processed
101+
data: List of \"X,Y\" strings representing red tile positions in loop order
86102
87103
Returns
88104
-------
89-
int: Solution for part 2 of the puzzle
105+
int: Largest rectangle area that uses red tiles as opposite corners
106+
and includes only red or green tiles inside
90107
"""
91108
tiles = [tuple(map(int, line.split(","))) for line in data if line.strip()]
92109

93110
if len(tiles) < 2:
94111
return 0
95112

113+
# Coordinate compression
96114
xs = sorted({x for x, _ in tiles})
97115
ys = sorted({y for _, y in tiles})
98116
x_to_idx = {x: i for i, x in enumerate(xs)}
99-
y_to_idx = {y: i for i, y in tiles}
100117
y_to_idx = {y: i for i, y in enumerate(ys)}
101118
compressed = [(x_to_idx[x], y_to_idx[y]) for x, y in tiles]
102119

103120
w, h = len(xs), len(ys)
104-
grid = [[0] * w for _ in range(h)] # 0 = empty, 1 = red, 2 = green
121+
# 0 = empty, 1 = red, 2 = green
122+
grid = [[0] * w for _ in range(h)]
105123

106124
# Mark red tiles
107125
for cx, cy in compressed:
108126
grid[cy][cx] = 1
109127

110-
# Draw green boundary segments between consecutive reds (wrap)
128+
# Draw green boundary segments between consecutive reds (wrap around)
111129
n = len(compressed)
112130
for i in range(n):
113131
x1, y1 = compressed[i]
@@ -123,19 +141,22 @@ def part2(self, data: list[str]) -> int:
123141
if grid[y1][x] == 0:
124142
grid[y1][x] = 2
125143
else:
126-
raise ValueError("Non axis-aligned segment in input")
144+
err_msg = "Non axis-aligned segment in input"
145+
raise ValueError(err_msg)
127146

128147
# Flood-fill outside empty cells
129148
outside = [[False] * w for _ in range(h)]
130149
q: deque[tuple[int, int]] = deque()
131150

151+
# Seed flood fill from outer boundary
132152
for x in range(w):
133153
if grid[0][x] == 0:
134154
outside[0][x] = True
135155
q.append((x, 0))
136156
if grid[h - 1][x] == 0:
137157
outside[h - 1][x] = True
138158
q.append((x, h - 1))
159+
139160
for y in range(h):
140161
if grid[y][0] == 0:
141162
outside[y][0] = True
@@ -158,6 +179,7 @@ def part2(self, data: list[str]) -> int:
158179
if grid[y][x] == 0 and not outside[y][x]:
159180
grid[y][x] = 2
160181

182+
# Search for largest valid rectangle (only red/green inside)
161183
max_area = 0
162184
n = len(compressed)
163185

_2025/tests/test_09.py

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,18 @@
1-
"""Test suite for Advent of Code daily challenge solution.
1+
"""Test suite for Day 9: Movie Theater
22
3-
This module contains the test cases for the solution to the Advent of Code
4-
daily puzzle. It uses the TestSolutionUtility to run tests for both parts
5-
of the challenge.
3+
This module contains tests for the Day 9 solution, which finds the largest
4+
rectangles in a movie theater tile grid using red tiles as opposite corners.
5+
The tests verify:
66
7-
The template provides a structure for defining tests for a specific day's
8-
solution, ensuring that the implementation meets the expected outputs for
9-
given test inputs.
7+
1. Part 1: Largest rectangle area using any two red tiles as opposite corners
8+
2. Part 2: Largest rectangle area using only red and green tiles
109
"""
1110

1211
from aoc.models.tester import TestSolutionUtility
1312

1413

1514
def test_day09_part1() -> None:
16-
"""Test the solution for Part 1 of the daily puzzle.
15+
"""Test finding largest rectangle using red tiles as opposite corners.
1716
1817
This test runs the solution for Part 1 of the puzzle against the
1918
provided test input and compares the result with the expected output.
@@ -28,7 +27,7 @@ def test_day09_part1() -> None:
2827

2928

3029
def test_day09_part2() -> None:
31-
"""Test the solution for Part 2 of the daily puzzle.
30+
"""Test finding largest rectangle using only red and green tiles.
3231
3332
This test runs the solution for Part 2 of the puzzle against the
3433
provided test input and compares the result with the expected output.

0 commit comments

Comments
 (0)