|
4 | 4 | # |
5 | 5 |
|
6 | 6 | import sys |
| 7 | +from collections import deque |
7 | 8 |
|
8 | 9 | from aoc.common import InputData |
9 | 10 | from aoc.common import SolutionBase |
10 | 11 | from aoc.common import aoc_samples |
11 | | -from aoc.geometry import Direction |
12 | 12 | from aoc.grid import Cell |
13 | | - |
14 | | -Input = InputData |
15 | | -Output1 = int |
16 | | -Output2 = int |
17 | | - |
| 13 | +from aoc.grid import CharGrid |
18 | 14 |
|
19 | 15 | TEST = """\ |
20 | 16 | ..@@.@@@@. |
|
29 | 25 | @.@.@@@.@. |
30 | 26 | """ |
31 | 27 |
|
| 28 | +Input = CharGrid |
| 29 | +Output1 = int |
| 30 | +Output2 = int |
| 31 | +ROLL, EMPTY = "@", "." |
| 32 | + |
32 | 33 |
|
33 | 34 | class Solution(SolutionBase[Input, Output1, Output2]): |
34 | 35 | def parse_input(self, input_data: InputData) -> Input: |
35 | | - return input_data |
36 | | - |
37 | | - def part_1(self, inputs: Input) -> Output1: |
38 | | - grid = list(inputs) |
39 | | - ans = 0 |
40 | | - for r, row in enumerate(grid): |
41 | | - for c, ch in enumerate(row): |
42 | | - if ch == "@": |
43 | | - cnt = 0 |
44 | | - cell = Cell(r, c) |
45 | | - for d in Direction.octants(): |
46 | | - n = cell.at(d) |
47 | | - if ( |
48 | | - 0 <= n[0] < len(grid) |
49 | | - and 0 <= n[1] < len(row) |
50 | | - and grid[n[0]][n[1]] == "@" |
51 | | - ): |
52 | | - cnt += 1 |
53 | | - if cnt < 4: |
54 | | - ans += 1 |
55 | | - return ans |
56 | | - |
57 | | - def part_2(self, inputs: Input) -> Output2: |
58 | | - grid = [list(row) for row in inputs] |
59 | | - ans = 0 |
60 | | - while True: |
61 | | - new_grid = [list(row) for row in grid] |
62 | | - for r, row in enumerate(grid): |
63 | | - for c, ch in enumerate(row): |
64 | | - if ch == "@": |
65 | | - cnt = 0 |
66 | | - cell = Cell(r, c) |
67 | | - for d in Direction.octants(): |
68 | | - n = cell.at(d) |
69 | | - if ( |
70 | | - 0 <= n[0] < len(grid) |
71 | | - and 0 <= n[1] < len(row) |
72 | | - and grid[n[0]][n[1]] == "@" |
73 | | - ): |
74 | | - cnt += 1 |
75 | | - if cnt < 4: |
76 | | - new_grid[r][c] = "." |
77 | | - ans += 1 |
78 | | - if new_grid == grid: |
79 | | - break |
80 | | - grid = new_grid |
81 | | - return ans |
| 36 | + return CharGrid.from_strings(list(input_data)) |
| 37 | + |
| 38 | + def is_removable(self, grid: CharGrid, cell: Cell) -> bool: |
| 39 | + return ( |
| 40 | + grid.get_value(cell) == ROLL |
| 41 | + and sum( |
| 42 | + grid.get_value(n) == ROLL |
| 43 | + for n in grid.get_all_neighbours(cell) |
| 44 | + ) |
| 45 | + < 4 |
| 46 | + ) |
| 47 | + |
| 48 | + def part_1(self, grid: Input) -> Output1: |
| 49 | + return sum(self.is_removable(grid, cell) for cell in grid.get_cells()) |
| 50 | + |
| 51 | + def part_2(self, grid: Input) -> Output2: |
| 52 | + q = deque( |
| 53 | + cell for cell in grid.get_cells() if self.is_removable(grid, cell) |
| 54 | + ) |
| 55 | + seen = set[Cell]() |
| 56 | + while len(q) != 0: |
| 57 | + cell = q.popleft() |
| 58 | + if cell in seen: |
| 59 | + continue |
| 60 | + seen.add(cell) |
| 61 | + grid.set_value(cell, EMPTY) |
| 62 | + for n in grid.get_all_neighbours(cell): |
| 63 | + if self.is_removable(grid, n): |
| 64 | + q.append(n) |
| 65 | + return len(seen) |
82 | 66 |
|
83 | 67 | @aoc_samples( |
84 | 68 | ( |
|
0 commit comments