|
4 | 4 | # |
5 | 5 |
|
6 | 6 | import sys |
| 7 | +from typing import Callable |
7 | 8 |
|
8 | 9 | from aoc import my_aocd |
9 | 10 | from aoc.common import InputData |
10 | 11 | from aoc.common import SolutionBase |
11 | 12 | from aoc.common import aoc_samples |
12 | 13 | from aoc.geometry import Direction |
13 | 14 | from aoc.grid import Cell |
14 | | -from aoc.grid import CharGrid |
15 | 15 |
|
16 | | -Input = tuple[CharGrid, list[Direction]] |
| 16 | +Grid = list[list[str]] |
| 17 | +Input = tuple[Grid, list[Direction]] |
17 | 18 | Output1 = int |
18 | 19 | Output2 = int |
19 | 20 |
|
|
56 | 57 |
|
57 | 58 |
|
58 | 59 | class Solution(SolutionBase[Input, Output1, Output2]): |
| 60 | + FLOOR, WALL, ROBOT = ".", "#", "@" |
| 61 | + BOX, BIG_BOX_LEFT, BIG_BOX_RIGHT = "O", "[", "]" |
| 62 | + SCALE_UP = { |
| 63 | + WALL: [WALL, WALL], |
| 64 | + BOX: [BIG_BOX_LEFT, BIG_BOX_RIGHT], |
| 65 | + FLOOR: [FLOOR, FLOOR], |
| 66 | + ROBOT: [ROBOT, FLOOR], |
| 67 | + } |
| 68 | + |
59 | 69 | def parse_input(self, input_data: InputData) -> Input: |
60 | 70 | blocks = my_aocd.to_blocks(input_data) |
61 | | - grid = CharGrid.from_strings(blocks[0]) |
62 | | - dirs = list[Direction]() |
63 | | - for line in blocks[1]: |
64 | | - dirs.extend([Direction.from_str(ch) for ch in line]) |
| 71 | + grid = [list(line) for line in blocks[0]] |
| 72 | + dirs = [Direction.from_str(ch) for ch in "".join(blocks[1])] |
65 | 73 | return grid, dirs |
66 | 74 |
|
67 | | - def get_gps(self, grid: CharGrid) -> int: |
| 75 | + def solve( |
| 76 | + self, |
| 77 | + grid: Grid, |
| 78 | + robot: Cell, |
| 79 | + dirs: list[Direction], |
| 80 | + get_to_move: Callable[[Grid, Cell, Direction], list[Cell]], |
| 81 | + ) -> int: |
| 82 | + for dir in dirs: |
| 83 | + to_move = get_to_move(grid, robot, dir) |
| 84 | + if len(to_move) == 0: |
| 85 | + continue |
| 86 | + to_move.pop(0) |
| 87 | + vals = [list(row) for row in grid] |
| 88 | + grid[robot.row][robot.col] = Solution.FLOOR |
| 89 | + nxt_robot = robot.at(dir) |
| 90 | + grid[nxt_robot.row][nxt_robot.col] = Solution.ROBOT |
| 91 | + robot = nxt_robot |
| 92 | + for cell in to_move: |
| 93 | + grid[cell.row][cell.col] = Solution.FLOOR |
| 94 | + for cell in to_move: |
| 95 | + nxt = cell.at(dir) |
| 96 | + grid[nxt.row][nxt.col] = vals[cell.row][cell.col] |
68 | 97 | return sum( |
69 | | - cell.row * 100 + cell.col |
70 | | - for cell in grid.get_cells() |
71 | | - if grid.get_value(cell) in {"O", "["} |
| 98 | + r * 100 + c |
| 99 | + for r in range(len(grid)) |
| 100 | + for c in range(len(grid[r])) |
| 101 | + if grid[r][c] in {Solution.BOX, Solution.BIG_BOX_LEFT} |
72 | 102 | ) |
73 | 103 |
|
74 | 104 | def part_1(self, input: Input) -> Output1: |
75 | | - def move(line: list[str], r: int) -> list[str]: |
76 | | - def swap(line: list[str], idx1: int, idx2: int) -> list[str]: |
77 | | - tmp = line[idx1] |
78 | | - line[idx1] = line[idx2] |
79 | | - line[idx2] = tmp |
80 | | - return line |
81 | | - |
82 | | - assert line[r] == "@" |
83 | | - if line[r + 1] == "#": |
84 | | - return line |
85 | | - # max_idx = len(line) - 2 |
86 | | - # #[email protected]....# -> #.....@O....# |
87 | | - if line[r + 1] == ".": |
88 | | - line = swap(line, r, r + 1) |
89 | | - # #.....@O....# -> #......@O...# |
90 | | - try: |
91 | | - dot_idx = line.index(".", r) |
92 | | - octo_idx = line.index("#", r) |
93 | | - if dot_idx < octo_idx: |
94 | | - line.pop(dot_idx) |
95 | | - line.insert(r, ".") |
96 | | - except ValueError: |
97 | | - return line |
98 | | - return line |
99 | | - |
100 | | - def to_array(s: str) -> list[str]: |
101 | | - return [ch for ch in s] |
102 | | - |
103 | | - def to_str(a: list[str]) -> str: |
104 | | - return "".join(a) |
105 | | - |
106 | | - assert to_str(move(to_array("#..@#"), 3)) == "#..@#" |
107 | | - assert ( |
108 | | - to_str( move( to_array( "#[email protected]....#"), 5)) == "#.....@O....#" |
109 | | - ) # noqa E501 |
110 | | - assert ( |
111 | | - to_str(move(to_array("#.....@O....#"), 6)) == "#......@O...#" |
112 | | - ) # noqa E501 |
113 | | - assert to_str(move(to_array("#..@O#"), 3)) == "#..@O#" # noqa E501 |
114 | | - assert to_str( move( to_array( "#[email protected].#"), 2)) == "#..@OO.#" # noqa E501 |
115 | | - assert to_str(move(to_array("#.#@O..#"), 3)) == "#.#.@O.#" # noqa E501 |
116 | | - assert to_str(move(to_array("#.@O#..#"), 2)) == "#.@O#..#" # noqa E501 |
117 | | - grid, dirs = input |
118 | | - grid = CharGrid.from_strings(["".join(row) for row in grid.values]) |
119 | | - robot = next(grid.get_all_equal_to("@")) |
120 | | - for dir in dirs: |
121 | | - match dir: |
122 | | - case Direction.RIGHT: |
123 | | - row = grid.values[robot.row] |
124 | | - tmp = move(row, robot.col) |
125 | | - grid.values[robot.row] = tmp |
126 | | - robot = Cell(robot.row, tmp.index("@")) |
127 | | - case Direction.LEFT: |
128 | | - row = grid.values[robot.row] |
129 | | - row.reverse() |
130 | | - tmp = move(row, row.index("@")) |
131 | | - tmp.reverse() |
132 | | - grid.values[robot.row] = tmp |
133 | | - robot = Cell(robot.row, tmp.index("@")) |
134 | | - case Direction.DOWN: |
135 | | - col = grid.get_col(robot.col) |
136 | | - tmp = move(col, robot.row) |
137 | | - grid.replace_col(robot.col, tmp) |
138 | | - robot = Cell(tmp.index("@"), robot.col) |
139 | | - case Direction.UP: |
140 | | - col = grid.get_col(robot.col) |
141 | | - col.reverse() |
142 | | - tmp = move(col, col.index("@")) |
143 | | - tmp.reverse() |
144 | | - grid.replace_col(robot.col, tmp) |
145 | | - robot = Cell(tmp.index("@"), robot.col) |
146 | | - return self.get_gps(grid) |
| 105 | + def get_to_move(grid: Grid, robot: Cell, dir: Direction) -> list[Cell]: |
| 106 | + to_move = [robot] |
| 107 | + for cell in to_move: |
| 108 | + nxt = cell.at(dir) |
| 109 | + if nxt in to_move: |
| 110 | + continue |
| 111 | + match grid[nxt.row][nxt.col]: |
| 112 | + case Solution.WALL: |
| 113 | + return [] |
| 114 | + case Solution.BOX: |
| 115 | + to_move.append(nxt) |
| 116 | + return to_move |
147 | 117 |
|
148 | | - def part_2(self, input: Input) -> Output2: |
149 | 118 | grid_in, dirs = input |
150 | | - grid = [] |
151 | | - for row in grid_in.get_rows_as_strings(): |
152 | | - line = list[str]() |
153 | | - for ch in row: |
154 | | - match ch: |
155 | | - case "#": |
156 | | - line.extend(["#", "#"]) |
157 | | - case "O": |
158 | | - line.extend(["[", "]"]) |
159 | | - case ".": |
160 | | - line.extend([".", "."]) |
161 | | - case "@": |
162 | | - line.extend(["@", "."]) |
163 | | - grid.append(line) |
| 119 | + grid = [list(row) for row in grid_in] |
164 | 120 | for r in range(len(grid)): |
165 | 121 | for c in range(len(grid[r])): |
166 | | - if grid[r][c] == "@": |
| 122 | + if grid[r][c] == Solution.ROBOT: |
167 | 123 | robot = Cell(r, c) |
168 | 124 | break |
169 | 125 | else: |
170 | 126 | continue |
171 | 127 | break |
172 | | - for dir in dirs: |
| 128 | + return self.solve(grid, robot, dirs, get_to_move) |
| 129 | + |
| 130 | + def part_2(self, input: Input) -> Output2: |
| 131 | + def get_to_move(grid: Grid, robot: Cell, dir: Direction) -> list[Cell]: |
173 | 132 | to_move = [robot] |
174 | 133 | for cell in to_move: |
175 | 134 | nxt = cell.at(dir) |
176 | 135 | if nxt in to_move: |
177 | 136 | continue |
178 | 137 | match grid[nxt.row][nxt.col]: |
179 | | - case "#": |
180 | | - to_move = [] |
181 | | - break |
182 | | - case "[": |
| 138 | + case Solution.WALL: |
| 139 | + return [] |
| 140 | + case Solution.BIG_BOX_LEFT: |
183 | 141 | to_move.append(nxt) |
184 | 142 | to_move.append(nxt.at(Direction.RIGHT)) |
185 | | - case "]": |
| 143 | + case Solution.BIG_BOX_RIGHT: |
186 | 144 | to_move.append(nxt) |
187 | 145 | to_move.append(nxt.at(Direction.LEFT)) |
188 | | - if len(to_move) == 0: |
189 | | - continue |
190 | | - to_move.pop(0) |
191 | | - vals = [list(row) for row in grid] |
192 | | - grid[robot.row][robot.col] = "." |
193 | | - nxt_robot = robot.at(dir) |
194 | | - grid[nxt_robot.row][nxt_robot.col] = "@" |
195 | | - robot = nxt_robot |
196 | | - for cell in to_move: |
197 | | - grid[cell.row][cell.col] = "." |
198 | | - for cell in to_move: |
199 | | - nxt = cell.at(dir) |
200 | | - grid[nxt.row][nxt.col] = vals[cell.row][cell.col] |
201 | | - tmp = CharGrid.from_strings(["".join(row) for row in grid]) |
202 | | - return self.get_gps(tmp) |
| 146 | + return to_move |
| 147 | + |
| 148 | + grid_in, dirs = input |
| 149 | + grid = [] |
| 150 | + for r, row in enumerate(grid_in): |
| 151 | + line = [] |
| 152 | + for c, ch in enumerate(row): |
| 153 | + if ch == Solution.ROBOT: |
| 154 | + robot = Cell(r, 2 * c) |
| 155 | + line.extend(Solution.SCALE_UP[ch]) |
| 156 | + grid.append(line) |
| 157 | + return self.solve(grid, robot, dirs, get_to_move) |
203 | 158 |
|
204 | 159 | @aoc_samples( |
205 | 160 | ( |
|
0 commit comments