88//!
99//! Part two re-uses the part one logic for horizontal moves. Vertical moves use a
1010//! [breadth first search](https://en.wikipedia.org/wiki/Breadth-first_search) to identify the
11- //! cascading boxes that need to be moved. Marking boxes as `seen` during the search prevents both
12- //! unintended exponential growth or pushing the bottom most row twice in this example:
11+ //! cascading boxes that need to be moved. Boxes are added strictly left to right to make checking
12+ //! for previously added boxes easier. To prevent adding a box twice we check that the
13+ //! item at `index - 2` is different. For example:
1314//!
1415//! ```none
15- //! @
16- //! []
17- //! [][]
18- //! []
16+ //! @ Indices:
17+ //! [] 23
18+ //! [][] 4567
19+ //! [] 89
1920//! ```
2021//!
22+ //! When processing 6 we try to add 8, however 8 and 9 have already been added when processing 4
23+ //! so we skip.
24+ //!
2125//! If any next space is a wall then we cancel the entire move and return right away. Otherwise
2226//! all boxes are moved in the *reverse* order that they were found by the search.
2327use crate :: util:: grid:: * ;
@@ -35,8 +39,10 @@ pub fn parse(input: &str) -> Input<'_> {
3539pub fn part1 ( input : & Input < ' _ > ) -> i32 {
3640 let ( grid, moves) = input;
3741
42+ // We don't need to move the robot symbol so mark as empty space once located.
3843 let mut grid = grid. clone ( ) ;
3944 let mut position = grid. find ( b'@' ) . unwrap ( ) ;
45+ grid[ position] = b'.' ;
4046
4147 // Treat moves as a single string ignoring any newline characters.
4248 for b in moves. bytes ( ) {
@@ -57,18 +63,17 @@ pub fn part2(input: &Input<'_>) -> i32 {
5763
5864 let mut grid = stretch ( grid) ;
5965 let mut position = grid. find ( b'@' ) . unwrap ( ) ;
66+ grid[ position] = b'.' ;
6067
6168 // Reuse to minimize allocations.
62- let mut todo = Vec :: new ( ) ;
63- let mut seen = grid. same_size_with ( usize:: MAX ) ;
69+ let mut todo = Vec :: with_capacity ( 50 ) ;
6470
65- // Use index as a unique id for each move.
66- for ( id, b) in moves. bytes ( ) . enumerate ( ) {
71+ for b in moves. bytes ( ) {
6772 match b {
6873 b'<' => narrow ( & mut grid, & mut position, LEFT ) ,
6974 b'>' => narrow ( & mut grid, & mut position, RIGHT ) ,
70- b'^' => wide ( & mut grid, & mut position, UP , & mut todo, & mut seen , id ) ,
71- b'v' => wide ( & mut grid, & mut position, DOWN , & mut todo, & mut seen , id ) ,
75+ b'^' => wide ( & mut grid, & mut position, UP , & mut todo) ,
76+ b'v' => wide ( & mut grid, & mut position, DOWN , & mut todo) ,
7277 _ => ( ) ,
7378 }
7479 }
@@ -78,7 +83,7 @@ pub fn part2(input: &Input<'_>) -> i32 {
7883
7984fn narrow ( grid : & mut Grid < u8 > , start : & mut Point , direction : Point ) {
8085 let mut position = * start + direction;
81- let mut size = 2 ;
86+ let mut size = 1 ;
8287
8388 // Search for the next wall or open space.
8489 while grid[ position] != b'.' && grid[ position] != b'#' {
@@ -89,7 +94,7 @@ fn narrow(grid: &mut Grid<u8>, start: &mut Point, direction: Point) {
8994 // Move items one space in direction.
9095 if grid[ position] == b'.' {
9196 let mut previous = b'.' ;
92- let mut position = * start;
97+ let mut position = * start + direction ;
9398
9499 for _ in 0 ..size {
95100 swap ( & mut previous, & mut grid[ position] ) ;
@@ -101,58 +106,41 @@ fn narrow(grid: &mut Grid<u8>, start: &mut Point, direction: Point) {
101106 }
102107}
103108
104- fn wide (
105- grid : & mut Grid < u8 > ,
106- start : & mut Point ,
107- direction : Point ,
108- todo : & mut Vec < Point > ,
109- seen : & mut Grid < usize > ,
110- id : usize ,
111- ) {
109+ fn wide ( grid : & mut Grid < u8 > , start : & mut Point , direction : Point , todo : & mut Vec < Point > ) {
112110 // Short circuit if path in front of robot is empty.
113- let position = * start;
114- let next = position + direction;
115-
116- if grid[ next] == b'.' {
117- grid[ position] = b'.' ;
118- grid[ next] = b'@' ;
111+ if grid[ * start + direction] == b'.' {
119112 * start += direction;
120113 return ;
121114 }
122115
123116 // Clear any items from previous push.
124117 todo. clear ( ) ;
118+ // Add dummy item to prevent index of out bounds when checking for previously added boxes.
119+ todo. push ( ORIGIN ) ;
125120 todo. push ( * start) ;
126- let mut index = 0 ;
121+ let mut index = 1 ;
127122
128123 while index < todo. len ( ) {
129124 let next = todo[ index] + direction;
130125 index += 1 ;
131126
132- let other = match grid[ next] {
127+ // Add boxes strictly left to right.
128+ let ( first, second) = match grid[ next] {
129+ b'[' => ( next, next + RIGHT ) ,
130+ b']' => ( next + LEFT , next) ,
133131 b'#' => return , // Return early if there's a wall in the way.
134- b'[' => RIGHT ,
135- b']' => LEFT ,
136- _ => continue , // Open space doesn't add any more items to move.
132+ _ => continue , // Open space doesn't add any more items to move.
137133 } ;
138134
139- // Enqueue the first half of box directly above us.
140- let first = next;
141- if seen[ first] != id {
142- seen[ first] = id;
135+ // Check if this box has already been added by the previous box in this row.
136+ if first != todo[ todo. len ( ) - 2 ] {
143137 todo. push ( first) ;
144- }
145-
146- // Enqueue the other half of the box directly above us.
147- let second = next + other;
148- if seen[ second] != id {
149- seen[ second] = id;
150138 todo. push ( second) ;
151139 }
152140 }
153141
154- // Move boxes in reverse order.
155- for & point in todo. iter ( ) . rev ( ) {
142+ // Move boxes in reverse order, skipping the dummy item and robot .
143+ for & point in todo[ 2 .. ] . iter ( ) . rev ( ) {
156144 grid[ point + direction] = grid[ point] ;
157145 grid[ point] = b'.' ;
158146 }
0 commit comments