|
5 | 5 |
|
6 | 6 | import sys |
7 | 7 | from collections import defaultdict |
| 8 | +from typing import Callable |
| 9 | +from typing import Iterator |
8 | 10 |
|
9 | 11 | from aoc.common import InputData |
10 | 12 | from aoc.common import SolutionBase |
11 | 13 | from aoc.common import aoc_samples |
12 | 14 | from aoc.geometry import Direction |
13 | 15 | from aoc.graph import flood_fill |
14 | 16 | from aoc.grid import Cell |
15 | | -from aoc.grid import CharGrid |
16 | 17 |
|
17 | | -Input = CharGrid |
| 18 | +Input = InputData |
18 | 19 | Output1 = int |
19 | 20 | Output2 = int |
20 | 21 |
|
| 22 | +CORNER_DIRS = [ |
| 23 | + [Direction.LEFT_AND_UP, Direction.LEFT, Direction.UP], |
| 24 | + [Direction.RIGHT_AND_UP, Direction.RIGHT, Direction.UP], |
| 25 | + [Direction.RIGHT_AND_DOWN, Direction.RIGHT, Direction.DOWN], |
| 26 | + [Direction.LEFT_AND_DOWN, Direction.LEFT, Direction.DOWN], |
| 27 | +] |
21 | 28 |
|
22 | 29 | TEST1 = """\ |
23 | 30 | AAAA |
|
63 | 70 |
|
64 | 71 | class Solution(SolutionBase[Input, Output1, Output2]): |
65 | 72 | def parse_input(self, input_data: InputData) -> Input: |
66 | | - return CharGrid.from_strings(list(input_data)) |
67 | | - |
68 | | - def get_regions(self, grid: CharGrid) -> dict[str, list[set[Cell]]]: |
69 | | - p = defaultdict[str, set[Cell]](set) |
70 | | - for plot in grid.get_cells(): |
71 | | - p[grid.get_value(plot)].add(plot) |
72 | | - regions = defaultdict[str, list[set[Cell]]](list) |
73 | | - for k, all_p in p.items(): |
74 | | - while all_p: |
75 | | - r = flood_fill( |
76 | | - next(iter(all_p)), |
77 | | - lambda cell: ( |
78 | | - n |
79 | | - for n in grid.get_capital_neighbours(cell) |
80 | | - if n in all_p |
81 | | - ), |
82 | | - ) |
83 | | - regions[k].append(r) |
84 | | - all_p -= r |
85 | | - return regions |
86 | | - |
87 | | - def part_1(self, grid: Input) -> Output1: |
88 | | - regions = self.get_regions(grid) |
89 | | - d = list[list[int]]() |
90 | | - for plant in regions: |
91 | | - for region in regions[plant]: |
92 | | - dd = list[int]() |
93 | | - for plot in region: |
94 | | - c = 4 |
95 | | - for n in grid.get_capital_neighbours(plot): |
96 | | - if grid.get_value(n) == plant: |
97 | | - c -= 1 |
98 | | - dd.append(c) |
99 | | - d.append(dd) |
100 | | - return sum(len(dd) * sum(dd) for dd in d) |
101 | | - |
102 | | - def part_2(self, grid: Input) -> Output2: |
103 | | - def count_corners(plot: Cell, region: set[Cell]) -> int: |
104 | | - ans = 0 |
105 | | - DO = [ |
106 | | - [Direction.LEFT, Direction.LEFT_AND_UP, Direction.UP], |
107 | | - [Direction.UP, Direction.RIGHT_AND_UP, Direction.RIGHT], |
108 | | - [Direction.RIGHT, Direction.RIGHT_AND_DOWN, Direction.DOWN], |
109 | | - [Direction.DOWN, Direction.LEFT_AND_DOWN, Direction.LEFT], |
110 | | - ] |
111 | | - for d in DO: |
112 | | - o = list( |
113 | | - filter( |
114 | | - lambda n: n in region, map(lambda dd: plot.at(dd), d) |
| 73 | + return input_data |
| 74 | + |
| 75 | + def solve( |
| 76 | + self, input: Input, count: Callable[[Cell, set[Cell]], int] |
| 77 | + ) -> int: |
| 78 | + def get_regions(input: Input) -> Iterator[set[Cell]]: |
| 79 | + plots_by_plant = defaultdict[str, set[Cell]](set) |
| 80 | + for r, row in enumerate(input): |
| 81 | + for c, plant in enumerate(row): |
| 82 | + plots_by_plant[plant].add(Cell(r, c)) |
| 83 | + for all_plots_with_plant in plots_by_plant.values(): |
| 84 | + while all_plots_with_plant: |
| 85 | + region = flood_fill( |
| 86 | + next(iter(all_plots_with_plant)), |
| 87 | + lambda cell: ( |
| 88 | + n |
| 89 | + for n in cell.get_capital_neighbours() |
| 90 | + if n in all_plots_with_plant |
| 91 | + ), |
115 | 92 | ) |
116 | | - ) |
117 | | - if len(o) == 0: |
118 | | - ans += 1 |
119 | | - if ( |
120 | | - plot.at(Direction.LEFT_AND_DOWN) not in region |
121 | | - and plot.at(Direction.LEFT) in region |
122 | | - and plot.at(Direction.DOWN) in region |
123 | | - ): |
124 | | - ans += 1 |
125 | | - if ( |
126 | | - plot.at(Direction.LEFT_AND_UP) not in region |
127 | | - and plot.at(Direction.LEFT) in region |
128 | | - and plot.at(Direction.UP) in region |
129 | | - ): |
130 | | - ans += 1 |
131 | | - if ( |
132 | | - plot.at(Direction.RIGHT_AND_DOWN) not in region |
133 | | - and plot.at(Direction.RIGHT) in region |
134 | | - and plot.at(Direction.DOWN) in region |
135 | | - ): |
136 | | - ans += 1 |
137 | | - if ( |
138 | | - plot.at(Direction.RIGHT_AND_UP) not in region |
139 | | - and plot.at(Direction.RIGHT) in region |
140 | | - and plot.at(Direction.UP) in region |
141 | | - ): |
142 | | - ans += 1 |
143 | | - if ( |
144 | | - plot.at(Direction.LEFT_AND_DOWN) in region |
145 | | - and plot.at(Direction.LEFT) not in region |
146 | | - and plot.at(Direction.DOWN) not in region |
147 | | - ): |
148 | | - ans += 1 |
149 | | - if ( |
150 | | - plot.at(Direction.LEFT_AND_UP) in region |
151 | | - and plot.at(Direction.LEFT) not in region |
152 | | - and plot.at(Direction.UP) not in region |
153 | | - ): |
154 | | - ans += 1 |
155 | | - if ( |
156 | | - plot.at(Direction.RIGHT_AND_DOWN) in region |
157 | | - and plot.at(Direction.RIGHT) not in region |
158 | | - and plot.at(Direction.DOWN) not in region |
159 | | - ): |
160 | | - ans += 1 |
161 | | - if ( |
162 | | - plot.at(Direction.RIGHT_AND_UP) in region |
163 | | - and plot.at(Direction.RIGHT) not in region |
164 | | - and plot.at(Direction.UP) not in region |
165 | | - ): |
166 | | - ans += 1 |
167 | | - return ans |
168 | | - |
169 | | - d = list[list[int]]() |
170 | | - regions = self.get_regions(grid) |
171 | | - for plant in regions: |
172 | | - for region in regions[plant]: |
173 | | - dd = list[int]() |
174 | | - for plot in region: |
175 | | - dd.append(count_corners(plot, region)) |
176 | | - d.append(dd) |
177 | | - return sum(len(dd) * sum(dd) for dd in d) |
| 93 | + yield region |
| 94 | + all_plots_with_plant -= region |
| 95 | + |
| 96 | + return sum( |
| 97 | + sum(count(plot, region) for plot in region) * len(region) |
| 98 | + for region in get_regions(input) |
| 99 | + ) |
| 100 | + |
| 101 | + def part_1(self, input: Input) -> Output1: |
| 102 | + def count_edges(plot: Cell, region: set[Cell]) -> int: |
| 103 | + return 4 - sum(n in region for n in plot.get_capital_neighbours()) |
| 104 | + |
| 105 | + return self.solve(input, count_edges) |
| 106 | + |
| 107 | + def part_2(self, input: Input) -> Output2: |
| 108 | + def count_corners(plot: Cell, region: set[Cell]) -> int: |
| 109 | + return sum( |
| 110 | + tuple(1 if plot.at(d[i]) in region else 0 for i in range(3)) |
| 111 | + in ((0, 0, 0), (1, 0, 0), (0, 1, 1)) |
| 112 | + for d in CORNER_DIRS |
| 113 | + ) |
| 114 | + |
| 115 | + return self.solve(input, count_corners) |
178 | 116 |
|
179 | 117 | @aoc_samples( |
180 | 118 | ( |
|
0 commit comments