55
66import sys
77from typing import Callable
8+ from typing import NamedTuple
89
910from aoc import my_aocd
1011from aoc .common import InputData
1112from aoc .common import SolutionBase
1213from aoc .common import aoc_samples
1314from aoc .geometry import Direction
1415from aoc .grid import Cell
15-
16- Grid = list [list [str ]]
17- Input = tuple [Grid , list [Direction ]]
18- Output1 = int
19- Output2 = int
20-
16+ from aoc .grid import CharGrid
2117
2218TEST1 = """\
2319 ########
5652"""
5753
5854
55+ class GridSupplier (NamedTuple ):
56+ grid_in : list [str ]
57+
58+ def get_grid (self ) -> CharGrid :
59+ return CharGrid .from_strings (self .grid_in )
60+
61+ def get_wide_grid (self ) -> CharGrid :
62+ grid = [
63+ "" .join (Solution .SCALE_UP [ch ] for ch in line )
64+ for line in self .grid_in
65+ ]
66+ return CharGrid .from_strings (grid )
67+
68+
69+ Input = tuple [GridSupplier , list [Direction ]]
70+ Output1 = int
71+ Output2 = int
72+
73+
5974class Solution (SolutionBase [Input , Output1 , Output2 ]):
6075 FLOOR , WALL , ROBOT = "." , "#" , "@"
6176 BOX , BIG_BOX_LEFT , BIG_BOX_RIGHT = "O" , "[" , "]"
6277 SCALE_UP = {
63- WALL : [ WALL , WALL ] ,
64- BOX : [ BIG_BOX_LEFT , BIG_BOX_RIGHT ] ,
65- FLOOR : [ FLOOR , FLOOR ] ,
66- ROBOT : [ ROBOT , FLOOR ] ,
78+ WALL : WALL + WALL ,
79+ BOX : BIG_BOX_LEFT + BIG_BOX_RIGHT ,
80+ FLOOR : FLOOR + FLOOR ,
81+ ROBOT : ROBOT + FLOOR ,
6782 }
6883
6984 def parse_input (self , input_data : InputData ) -> Input :
7085 blocks = my_aocd .to_blocks (input_data )
71- grid = [list (line ) for line in blocks [0 ]]
7286 dirs = [Direction .from_str (ch ) for ch in "" .join (blocks [1 ])]
73- return grid , dirs
87+ return GridSupplier ( blocks [ 0 ]) , dirs
7488
7589 def solve (
7690 self ,
77- grid : Grid ,
78- robot : Cell ,
91+ grid : CharGrid ,
7992 dirs : list [Direction ],
80- get_to_move : Callable [[Grid , Cell , Direction ], list [Cell ]],
93+ get_to_move : Callable [[CharGrid , Cell , Direction ], list [Cell ]],
8194 ) -> int :
95+ robot = next (grid .get_all_equal_to (Solution .ROBOT ))
8296 for dir in dirs :
8397 to_move = get_to_move (grid , robot , dir )
8498 if len (to_move ) == 0 :
8599 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
100+ vals = {tm : grid .get_value (tm ) for tm in to_move }
101+ robot = robot .at (dir )
92102 for cell in to_move :
93- grid [ cell . row ][ cell . col ] = Solution .FLOOR
103+ grid . set_value ( cell , Solution .FLOOR )
94104 for cell in to_move :
95- nxt = cell .at (dir )
96- grid [nxt .row ][nxt .col ] = vals [cell .row ][cell .col ]
105+ grid .set_value (cell .at (dir ), vals [cell ])
97106 return sum (
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 }
107+ cell .row * 100 + cell .col
108+ for cell in grid .find_all_matching (
109+ lambda cell : grid .get_value (cell )
110+ in {Solution .BOX , Solution .BIG_BOX_LEFT }
111+ )
102112 )
103113
104114 def part_1 (self , input : Input ) -> Output1 :
105- def get_to_move (grid : Grid , robot : Cell , dir : Direction ) -> list [Cell ]:
115+ def get_to_move (
116+ grid : CharGrid , robot : Cell , dir : Direction
117+ ) -> list [Cell ]:
106118 to_move = [robot ]
107119 for cell in to_move :
108120 nxt = cell .at (dir )
109121 if nxt in to_move :
110122 continue
111- match grid [ nxt . row ][ nxt . col ] :
123+ match grid . get_value ( nxt ) :
112124 case Solution .WALL :
113125 return []
114126 case Solution .BOX :
115127 to_move .append (nxt )
116128 return to_move
117129
118- grid_in , dirs = input
119- grid = [list (row ) for row in grid_in ]
120- for r in range (len (grid )):
121- for c in range (len (grid [r ])):
122- if grid [r ][c ] == Solution .ROBOT :
123- robot = Cell (r , c )
124- break
125- else :
126- continue
127- break
128- return self .solve (grid , robot , dirs , get_to_move )
130+ grid , dirs = input
131+ return self .solve (grid .get_grid (), dirs , get_to_move )
129132
130133 def part_2 (self , input : Input ) -> Output2 :
131- def get_to_move (grid : Grid , robot : Cell , dir : Direction ) -> list [Cell ]:
134+ def get_to_move (
135+ grid : CharGrid , robot : Cell , dir : Direction
136+ ) -> list [Cell ]:
132137 to_move = [robot ]
133138 for cell in to_move :
134139 nxt = cell .at (dir )
135140 if nxt in to_move :
136141 continue
137- match grid [ nxt . row ][ nxt . col ] :
142+ match grid . get_value ( nxt ) :
138143 case Solution .WALL :
139144 return []
140145 case Solution .BIG_BOX_LEFT :
@@ -145,16 +150,8 @@ def get_to_move(grid: Grid, robot: Cell, dir: Direction) -> list[Cell]:
145150 to_move .append (nxt .at (Direction .LEFT ))
146151 return to_move
147152
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 )
153+ grid , dirs = input
154+ return self .solve (grid .get_wide_grid (), dirs , get_to_move )
158155
159156 @aoc_samples (
160157 (
0 commit comments