Advent of Code 2025 Day 4: Printing Department
"""Advent of Code 2025: Day 4 - Printing Department"""
from advent import parse, grid_to_dict, grid_neighbors, Generator
grid: dict[complex, str] = grid_to_dict(parse(4, list), elem_filter=lambda c: c == '@')
def is_accessible(pos: complex) -> bool:
"""Indicate whether the specified position is accessible i.e. it has less than 4 neighbors."""
return len(grid_neighbors(grid, pos)) < 4
def accessible_rolls(grid: dict[complex, str]) -> list[complex]:
"""Return a list of coordinates (as complex numbers) for all accessible rolls in the grid."""
return [pos for pos in grid if is_accessible(pos)]
def remove_rolls(grid: dict[complex, str]) -> Generator[int, None, None]:
"""Iterate removal of accessible rolls while yielding the number of rolls removed
for each iteration. Continue until there are no accessible rolls remaining."""
while rolls := accessible_rolls(grid):
for pos in rolls:
del grid[pos]
yield len(rolls)
removed_counts = list(remove_rolls(grid))
assert removed_counts[0] == 1564
assert sum(removed_counts) == 9401
Input Parsing
Our first grid puzzle of the year! Since grids come up so often in Advent of Code,my
advent
library includes grid_to_dict and grid_neighbors functions to convert the input to a
Python dict[complex, str] and return a list of neighbors for a position.
Using complex numbers to represent (x, y) coordinates is very handy.
In this particular case,
we only need a set, but it's more trouble than it's worth to convert the dict to a set,
since a dict is just as convenient for testing set membership, etc. In other cases, there may
be more than one type of character we're interested in.
The grid_to_dict function allows filtering and transforming both rows and elements. In this
case, we simply want to grab all the @ elements, so the following is all we need:
Solution
Again, the two parts are very similar. Part 1 removes accessible rolls once, and part 2 iterates until there are no more rolls to remove.
Since our dict only has entries for rolls, the is_accessible function indicates
whether a roll is "accessible", i.e. has less than 4 adjacent neighbors, by simply
counting the list of neighbors returned by grid_neighbors.
The accessible function iterates over all items in the grid, and filters by
whether they are accessible.
The remove_rolls function generates a list of the number of rolls removed for each
iteration. We store the resulting list in removed_counts.
The answer for part 1 is just the first item in removed_counts, and the answer for
part 2 is the sum of removed_counts.
