|
| 1 | +"""Day 4: Printing Department |
| 2 | +
|
| 3 | +This module provides the solution for Advent of Code 2025 - Day 4. |
| 4 | +
|
| 5 | +It processes a grid of paper rolls to determine which are accessible based |
| 6 | +on the number of adjacent rolls. A roll is accessible if it has fewer than |
| 7 | +four neighbors. |
| 8 | +
|
| 9 | +The module contains a Solution class to calculate the number of initially |
| 10 | +accessible rolls (Part 1) and the total number of rolls that can be removed |
| 11 | +iteratively (Part 2). |
| 12 | +""" |
| 13 | + |
| 14 | +from typing import ClassVar |
| 15 | + |
| 16 | +from aoc.models.base import SolutionBase |
| 17 | + |
| 18 | + |
| 19 | +class Solution(SolutionBase): |
| 20 | + """Determine accessible and removable paper rolls from a grid layout. |
| 21 | +
|
| 22 | + This solution scans a grid representing paper roll locations ('@'). A roll |
| 23 | + is deemed accessible if it has fewer than four adjacent rolls (including |
| 24 | + diagonals). |
| 25 | +
|
| 26 | + Part 1 counts the number of rolls that are initially accessible. |
| 27 | + Part 2 simulates the process of removing all accessible rolls, re-evaluating |
| 28 | + accessibility, and repeating until no more rolls can be removed, counting |
| 29 | + the total number of removed rolls. |
| 30 | + """ |
| 31 | + |
| 32 | + DIRECTIONS: ClassVar[list[tuple[int, int]]] = [ |
| 33 | + (-1, -1), # up-left |
| 34 | + (-1, 0), # up |
| 35 | + (-1, 1), # up-right |
| 36 | + (0, -1), # left |
| 37 | + (0, 1), # right |
| 38 | + (1, -1), # down-left |
| 39 | + (1, 0), # down |
| 40 | + (1, 1), # down-right |
| 41 | + ] |
| 42 | + |
| 43 | + def find_open_spaces(self, grid: list[list[str]], rows: int, cols: int) -> list[tuple[int, int]]: |
| 44 | + """Find all accessible paper rolls in the current grid. |
| 45 | +
|
| 46 | + An accessible roll is one marked '@' with fewer than four adjacent '@' rolls. |
| 47 | +
|
| 48 | + Args: |
| 49 | + grid: The current grid of paper rolls and empty spaces. |
| 50 | + rows: The number of rows in the grid. |
| 51 | + cols: The number of columns in the grid. |
| 52 | +
|
| 53 | + Returns |
| 54 | + ------- |
| 55 | + list[tuple[int, int]]: A list of (row, col) coordinates for accessible rolls. |
| 56 | + """ |
| 57 | + positions = [] |
| 58 | + for r in range(rows): |
| 59 | + for c in range(cols): |
| 60 | + if grid[r][c] != "@": |
| 61 | + continue |
| 62 | + |
| 63 | + count = 0 |
| 64 | + for dr, dc in self.DIRECTIONS: |
| 65 | + nr = r + dr |
| 66 | + nc = c + dc |
| 67 | + if 0 <= nr < rows and 0 <= nc < cols and grid[nr][nc] == "@": |
| 68 | + count += 1 |
| 69 | + |
| 70 | + if count < 4: |
| 71 | + positions.append((r, c)) |
| 72 | + |
| 73 | + return positions |
| 74 | + |
| 75 | + def part1(self, data: list[str]) -> int: |
| 76 | + """Count the number of paper rolls that are initially accessible. |
| 77 | +
|
| 78 | + This method parses the input grid and counts how many paper rolls ('@') |
| 79 | + have fewer than four adjacent rolls. |
| 80 | +
|
| 81 | + Args: |
| 82 | + data: A list of strings representing the grid layout. |
| 83 | +
|
| 84 | + Returns |
| 85 | + ------- |
| 86 | + int: The total number of initially accessible paper rolls. |
| 87 | + """ |
| 88 | + grid = [list(row) for row in data] |
| 89 | + rows, cols = len(grid), len(grid[0]) |
| 90 | + return len(self.find_open_spaces(grid, rows, cols)) |
| 91 | + |
| 92 | + def part2(self, data: list[str]) -> int: |
| 93 | + """Count the total number of rolls that can be removed. |
| 94 | +
|
| 95 | + This method simulates the iterative removal of accessible paper rolls. |
| 96 | + In each step, it finds all accessible rolls, adds them to a total count, |
| 97 | + removes them from the grid (by changing '@' to '.'), and then repeats |
| 98 | + the process until no more rolls are accessible. |
| 99 | +
|
| 100 | + Args: |
| 101 | + data: A list of strings representing the initial grid layout. |
| 102 | +
|
| 103 | + Returns |
| 104 | + ------- |
| 105 | + int: The total number of paper rolls that can be removed. |
| 106 | + """ |
| 107 | + grid = [list(row) for row in data] |
| 108 | + rows, cols = len(grid), len(grid[0]) |
| 109 | + positions = self.find_open_spaces(grid, rows, cols) |
| 110 | + |
| 111 | + count = 0 |
| 112 | + while (c := len(positions)) > 0: |
| 113 | + count += c |
| 114 | + for y, x in positions: |
| 115 | + grid[y][x] = "." |
| 116 | + |
| 117 | + positions = self.find_open_spaces(grid, rows, cols) |
| 118 | + |
| 119 | + return count |
0 commit comments