1- use object_pool:: Pool ;
21use std:: collections:: VecDeque ;
32
43use mygrid:: {
54 direction:: { Direction , DOWN , LEFT , RIGHT , UP } ,
65 grid:: Grid ,
6+ point:: Point ,
77} ;
88
99advent_of_code:: solution!( 16 ) ;
@@ -19,143 +19,119 @@ fn dir_to_index(dir: Direction) -> usize {
1919 }
2020}
2121
22- pub fn part_one ( input : & str ) -> Option < u64 > {
23- let ( grid, start) = Grid :: new_from_str_capture_start ( input, & |c| c, & |c| c == 'S' ) ;
24- let target_pos = grid. find_position_of ( & 'E' ) . unwrap ( ) ;
22+ #[ inline]
23+ fn index_to_dir ( idx : usize ) -> Direction {
24+ match idx {
25+ 0 => RIGHT ,
26+ 1 => DOWN ,
27+ 2 => LEFT ,
28+ 3 => UP ,
29+ _ => unreachable ! ( ) ,
30+ }
31+ }
2532
33+ #[ inline]
34+ fn build_best_cost_grid ( grid : & Grid < char > , start : Point , target_pos : Point ) -> Grid < [ i64 ; 4 ] > {
35+ let mut best_cost_grid = Grid :: new ( grid. width , grid. height , [ i64:: MAX ; 4 ] ) ;
2636 let mut q = VecDeque :: new ( ) ;
2737 q. push_back ( ( start, RIGHT , 0 ) ) ;
2838
29- let mut best_cost_grid = Grid :: new ( grid. width , grid. height , [ u64:: MAX ; 4 ] ) ;
30-
39+ let mut best_target_cost = i64:: MAX ;
3140 while let Some ( ( pos, dir, cost) ) = q. pop_front ( ) {
3241 if pos == target_pos {
33- best_cost_grid [ pos ] [ dir_to_index ( dir ) ] = cost;
42+ best_target_cost = cost;
3443 continue ;
3544 }
3645
3746 let right = dir. rotate_clockwise ( ) ;
3847 let left = dir. rotate_counterclockwise ( ) ;
3948
40- for & ( pos , dir , cost ) in [
49+ [
4150 ( pos + dir, dir, cost + 1 ) ,
42- ( pos + right, right, cost + 1000 + 1 ) ,
4351 ( pos + left, left, cost + 1000 + 1 ) ,
52+ ( pos + right, right, cost + 1000 + 1 ) ,
4453 ]
4554 . iter ( )
46- {
55+ . for_each ( | & ( pos , dir , cost ) | {
4756 if grid[ pos] == '#' {
48- continue ;
57+ return ;
4958 }
50- let best_cost = best_cost_grid[ pos] [ dir_to_index ( dir) ] ;
51- if best_cost <= cost {
52- continue ;
59+ if best_cost_grid[ pos] [ dir_to_index ( dir) ] <= cost {
60+ return ;
61+ }
62+ if cost > best_target_cost {
63+ return ;
5364 }
54- best_cost_grid[ pos] [ dir_to_index ( dir) ] = cost;
5565
66+ best_cost_grid[ pos] [ dir_to_index ( dir) ] = cost;
5667 q. push_back ( ( pos, dir, cost) ) ;
57- }
68+ } ) ;
5869 }
5970
60- let min_cost = * best_cost_grid[ target_pos] . iter ( ) . min ( ) . unwrap ( ) ;
61- Some ( min_cost)
71+ best_cost_grid
6272}
6373
64- pub fn part_two ( input : & str ) -> Option < u64 > {
74+ pub fn part_one ( input : & str ) -> Option < i64 > {
6575 let ( grid, start) = Grid :: new_from_str_capture_start ( input, & |c| c, & |c| c == 'S' ) ;
6676 let target_pos = grid. find_position_of ( & 'E' ) . unwrap ( ) ;
6777
68- const PATH_CAPACITY : usize = 512 ;
69- let path_pool = Pool :: new ( 256 , || Vec :: with_capacity ( PATH_CAPACITY ) ) ;
70- let mut path = path_pool. pull ( || Vec :: with_capacity ( PATH_CAPACITY ) ) ;
71- path. push ( start) ;
78+ let best_cost_grid = build_best_cost_grid ( & grid, start, target_pos) ;
79+ let min_cost = * best_cost_grid[ target_pos] . iter ( ) . min ( ) . unwrap ( ) ;
7280
73- let mut q = VecDeque :: new ( ) ;
74- q . push_back ( ( path , start , RIGHT , 0 ) ) ;
81+ Some ( min_cost )
82+ }
7583
76- let base_false_grid = Grid :: new ( grid. width , grid. height , false ) ;
77- let mut best_spots_grid = base_false_grid. clone ( ) ;
78- let mut best_target_cost = u64:: MAX ;
79- let mut best_cost_grid = Grid :: new ( grid. width , grid. height , [ u64:: MAX ; 4 ] ) ;
84+ pub fn part_two ( input : & str ) -> Option < i64 > {
85+ let ( grid, start) = Grid :: new_from_str_capture_start ( input, & |c| c, & |c| c == 'S' ) ;
86+ let target_pos = grid. find_position_of ( & 'E' ) . unwrap ( ) ;
8087
81- while let Some ( ( mut path, pos, dir, cost) ) = q. pop_front ( ) {
82- if pos == target_pos {
83- if best_target_cost < cost {
84- continue ;
85- }
88+ let best_cost_grid = build_best_cost_grid ( & grid, start, target_pos) ;
89+ let ( idx, & best_cost) = best_cost_grid[ target_pos]
90+ . iter ( )
91+ . enumerate ( )
92+ . min_by_key ( |& ( _, & cost) | cost)
93+ . unwrap ( ) ;
94+ let best_dir = index_to_dir ( idx) ;
95+ let mut seen = Grid :: new ( grid. width , grid. height , false ) ;
8696
87- // reset best_spots_grid if we found a better path
88- if best_target_cost > cost {
89- best_spots_grid = base_false_grid. clone ( ) ;
90- }
97+ // reverse lookup
98+ let ( target_pos, start) = ( start, target_pos) ;
99+ seen[ start] = true ;
91100
92- for & p in path. iter ( ) {
93- best_spots_grid[ p] = true ;
94- }
101+ let mut q = VecDeque :: new ( ) ;
102+ q. push_back ( ( start, best_dir, best_cost) ) ;
95103
96- best_target_cost = cost;
104+ while let Some ( ( pos, dir, cost) ) = q. pop_front ( ) {
105+ if pos == target_pos {
97106 continue ;
98107 }
99108
100- best_cost_grid[ pos] [ dir_to_index ( dir) ] = cost;
101-
102109 let right = dir. rotate_clockwise ( ) ;
103110 let left = dir. rotate_counterclockwise ( ) ;
104- let is_viable = |& ( pos, dir, cost) | {
105- if grid[ pos] == '#' {
106- return false ;
107- }
108- let best_cost_to_next_pos = best_cost_grid[ pos] [ dir_to_index ( dir) ] ;
109- if best_cost_to_next_pos < cost {
110- return false ;
111+ [
112+ ( pos - dir, dir, cost - 1 ) ,
113+ ( pos - dir, right, cost - 1000 - 1 ) ,
114+ ( pos - dir, left, cost - 1000 - 1 ) ,
115+ ( pos + left, left, cost - 1000 - 1 ) ,
116+ ( pos + right, left, cost - 1000 - 1 ) ,
117+ ]
118+ . iter ( )
119+ . for_each ( |& ( pos, dir, cost) | {
120+ if cost < 0 {
121+ return ;
111122 }
112- if cost > best_target_cost {
113- return false ;
123+ if grid [ pos ] == '#' {
124+ return ;
114125 }
115- true
116- } ;
117-
118- let all_options = [
119- ( pos + dir, dir, cost + 1 ) ,
120- ( pos + left, left, cost + 1000 + 1 ) ,
121- ( pos + right, right, cost + 1000 + 1 ) ,
122- ] ;
123-
124- let viable_options = [
125- is_viable ( & all_options[ 0 ] ) ,
126- is_viable ( & all_options[ 1 ] ) ,
127- is_viable ( & all_options[ 2 ] ) ,
128- ] ;
129-
130- // fast path when there is only one option to avoid copying the path
131- let viable_count = viable_options. iter ( ) . filter ( |& & b| b) . count ( ) ;
132- match viable_count {
133- 0 => continue ,
134- 1 => {
135- let idx = viable_options. iter ( ) . position ( |& b| b) . unwrap ( ) ;
136- let ( pos, dir, cost) = all_options[ idx] ;
137- path. push ( pos) ;
138- q. push_back ( ( path, pos, dir, cost) ) ;
126+ if cost == best_cost_grid[ pos] [ dir_to_index ( dir) ] {
127+ seen[ pos] = true ;
128+ q. push_back ( ( pos, dir, cost) ) ;
139129 }
140- _ => viable_options
141- . iter ( )
142- . enumerate ( )
143- . for_each ( |( idx, & is_viable) | {
144- if !is_viable {
145- return ;
146- }
147-
148- let ( pos, dir, cost) = all_options[ idx] ;
149- let mut new_path = path_pool. pull ( || Vec :: with_capacity ( PATH_CAPACITY ) ) ;
150- new_path. clone_from ( & path) ;
151- new_path. push ( pos) ;
152- q. push_back ( ( new_path, pos, dir, cost) ) ;
153- } ) ,
154- }
130+ } ) ;
155131 }
156132
157- let tile_count = best_spots_grid . iter ( ) . filter ( |& & b| b) . count ( ) ;
158- Some ( tile_count as u64 )
133+ let tile_count = seen . iter ( ) . filter ( |& & b| b) . count ( ) ;
134+ Some ( tile_count as i64 + 1 /* + target */ )
159135}
160136
161137#[ cfg( test) ]
@@ -178,6 +154,14 @@ mod tests {
178154 assert_eq ! ( result, Some ( 11048 ) ) ;
179155 }
180156
157+ #[ test]
158+ fn test_part_one_3 ( ) {
159+ let result = part_one ( & advent_of_code:: template:: read_file_part (
160+ "examples" , DAY , 3 ,
161+ ) ) ;
162+ assert_eq ! ( result, Some ( 9024 ) ) ;
163+ }
164+
181165 #[ test]
182166 fn test_part_two_1 ( ) {
183167 let result = part_two ( & advent_of_code:: template:: read_file_part (
@@ -193,4 +177,12 @@ mod tests {
193177 ) ) ;
194178 assert_eq ! ( result, Some ( 64 ) ) ;
195179 }
180+
181+ #[ test]
182+ fn test_part_two_3 ( ) {
183+ let result = part_two ( & advent_of_code:: template:: read_file_part (
184+ "examples" , DAY , 3 ,
185+ ) ) ;
186+ assert_eq ! ( result, Some ( 34 ) ) ;
187+ }
196188}
0 commit comments