Skip to content

Commit 401731c

Browse files
committed
AoC 2024 Day 15 - cleanup
1 parent eecfa67 commit 401731c

File tree

3 files changed

+74
-155
lines changed

3 files changed

+74
-155
lines changed

src/main/python/AoC2024_15.py

Lines changed: 74 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,17 @@
44
#
55

66
import sys
7+
from typing import Callable
78

89
from aoc import my_aocd
910
from aoc.common import InputData
1011
from aoc.common import SolutionBase
1112
from aoc.common import aoc_samples
1213
from aoc.geometry import Direction
1314
from aoc.grid import Cell
14-
from aoc.grid import CharGrid
1515

16-
Input = tuple[CharGrid, list[Direction]]
16+
Grid = list[list[str]]
17+
Input = tuple[Grid, list[Direction]]
1718
Output1 = int
1819
Output2 = int
1920

@@ -56,150 +57,104 @@
5657

5758

5859
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+
5969
def parse_input(self, input_data: InputData) -> Input:
6070
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])]
6573
return grid, dirs
6674

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]
6897
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}
72102
)
73103

74104
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
147117

148-
def part_2(self, input: Input) -> Output2:
149118
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]
164120
for r in range(len(grid)):
165121
for c in range(len(grid[r])):
166-
if grid[r][c] == "@":
122+
if grid[r][c] == Solution.ROBOT:
167123
robot = Cell(r, c)
168124
break
169125
else:
170126
continue
171127
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]:
173132
to_move = [robot]
174133
for cell in to_move:
175134
nxt = cell.at(dir)
176135
if nxt in to_move:
177136
continue
178137
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:
183141
to_move.append(nxt)
184142
to_move.append(nxt.at(Direction.RIGHT))
185-
case "]":
143+
case Solution.BIG_BOX_RIGHT:
186144
to_move.append(nxt)
187145
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)
203158

204159
@aoc_samples(
205160
(

src/main/python/aoc/grid.py

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -143,14 +143,6 @@ def set_value(self, c: Cell, value: T) -> None:
143143
def get_row_as_string(self, row: int) -> str:
144144
pass
145145

146-
@abstractmethod
147-
def get_col(self, col: int) -> list[T]:
148-
pass
149-
150-
@abstractmethod
151-
def replace_col(self, col: int, val: list[T]) -> None:
152-
pass
153-
154146
def size(self) -> int:
155147
return self.get_height() * self.get_width()
156148

@@ -272,12 +264,6 @@ def increment(self, c: Cell) -> None:
272264
def get_row_as_string(self, row: int) -> str:
273265
return "".join(str(_) for _ in self.values[row])
274266

275-
def get_col(self, col: int) -> list[int]:
276-
raise NotImplementedError
277-
278-
def replace_col(self, col: int, val: list[int]) -> None:
279-
raise NotImplementedError
280-
281267

282268
@dataclass(frozen=True)
283269
class CharGrid(Grid[str]):
@@ -309,14 +295,6 @@ def set_value(self, c: Cell, value: str) -> None:
309295
def get_row_as_string(self, row: int) -> str:
310296
return "".join(self.values[row])
311297

312-
def get_col(self, col: int) -> list[str]:
313-
return [self.values[row][col] for row in range(self.get_height())]
314-
315-
def replace_col(self, col: int, val: list[str]) -> None:
316-
assert len(val) == self.get_height()
317-
for row in range(self.get_height()):
318-
self.values[row][col] = val[row]
319-
320298
def get_col_as_string(self, col: int) -> str:
321299
return "".join(
322300
self.values[row][col] for row in range(self.get_height())

src/test/python/test_grid.py

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -108,20 +108,6 @@ def test_roll_column(self) -> None:
108108
grid, CharGrid.from_strings(["#.#....", "###....", ".#....."])
109109
)
110110

111-
def test_get_col(self) -> None:
112-
grid = CharGrid.from_strings(["ABC", "DEF", "GHI"])
113-
114-
col = grid.get_col(1)
115-
116-
self.assertEqual(col, ["B", "E", "H"])
117-
118-
def test_replace_col(self) -> None:
119-
grid = CharGrid.from_strings(["ABC", "DEF", "GHI"])
120-
121-
grid.replace_col(1, ["X", "X", "X"])
122-
123-
self.assertEqual(grid, CharGrid.from_strings(["AXC", "DXF", "GXI"]))
124-
125111

126112
class IntGridIteratorTest(unittest.TestCase):
127113

0 commit comments

Comments
 (0)