1
- use object_pool:: Pool ;
2
1
use std:: collections:: VecDeque ;
3
2
4
3
use mygrid:: {
5
4
direction:: { Direction , DOWN , LEFT , RIGHT , UP } ,
6
5
grid:: Grid ,
6
+ point:: Point ,
7
7
} ;
8
8
9
9
advent_of_code:: solution!( 16 ) ;
@@ -19,143 +19,119 @@ fn dir_to_index(dir: Direction) -> usize {
19
19
}
20
20
}
21
21
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
+ }
25
32
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 ] ) ;
26
36
let mut q = VecDeque :: new ( ) ;
27
37
q. push_back ( ( start, RIGHT , 0 ) ) ;
28
38
29
- let mut best_cost_grid = Grid :: new ( grid. width , grid. height , [ u64:: MAX ; 4 ] ) ;
30
-
39
+ let mut best_target_cost = i64:: MAX ;
31
40
while let Some ( ( pos, dir, cost) ) = q. pop_front ( ) {
32
41
if pos == target_pos {
33
- best_cost_grid [ pos ] [ dir_to_index ( dir ) ] = cost;
42
+ best_target_cost = cost;
34
43
continue ;
35
44
}
36
45
37
46
let right = dir. rotate_clockwise ( ) ;
38
47
let left = dir. rotate_counterclockwise ( ) ;
39
48
40
- for & ( pos , dir , cost ) in [
49
+ [
41
50
( pos + dir, dir, cost + 1 ) ,
42
- ( pos + right, right, cost + 1000 + 1 ) ,
43
51
( pos + left, left, cost + 1000 + 1 ) ,
52
+ ( pos + right, right, cost + 1000 + 1 ) ,
44
53
]
45
54
. iter ( )
46
- {
55
+ . for_each ( | & ( pos , dir , cost ) | {
47
56
if grid[ pos] == '#' {
48
- continue ;
57
+ return ;
49
58
}
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 ;
53
64
}
54
- best_cost_grid[ pos] [ dir_to_index ( dir) ] = cost;
55
65
66
+ best_cost_grid[ pos] [ dir_to_index ( dir) ] = cost;
56
67
q. push_back ( ( pos, dir, cost) ) ;
57
- }
68
+ } ) ;
58
69
}
59
70
60
- let min_cost = * best_cost_grid[ target_pos] . iter ( ) . min ( ) . unwrap ( ) ;
61
- Some ( min_cost)
71
+ best_cost_grid
62
72
}
63
73
64
- pub fn part_two ( input : & str ) -> Option < u64 > {
74
+ pub fn part_one ( input : & str ) -> Option < i64 > {
65
75
let ( grid, start) = Grid :: new_from_str_capture_start ( input, & |c| c, & |c| c == 'S' ) ;
66
76
let target_pos = grid. find_position_of ( & 'E' ) . unwrap ( ) ;
67
77
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 ( ) ;
72
80
73
- let mut q = VecDeque :: new ( ) ;
74
- q . push_back ( ( path , start , RIGHT , 0 ) ) ;
81
+ Some ( min_cost )
82
+ }
75
83
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 ( ) ;
80
87
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 ) ;
86
96
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 ;
91
100
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) ) ;
95
103
96
- best_target_cost = cost;
104
+ while let Some ( ( pos, dir, cost) ) = q. pop_front ( ) {
105
+ if pos == target_pos {
97
106
continue ;
98
107
}
99
108
100
- best_cost_grid[ pos] [ dir_to_index ( dir) ] = cost;
101
-
102
109
let right = dir. rotate_clockwise ( ) ;
103
110
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 ;
111
122
}
112
- if cost > best_target_cost {
113
- return false ;
123
+ if grid [ pos ] == '#' {
124
+ return ;
114
125
}
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) ) ;
139
129
}
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
+ } ) ;
155
131
}
156
132
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 */ )
159
135
}
160
136
161
137
#[ cfg( test) ]
@@ -178,6 +154,14 @@ mod tests {
178
154
assert_eq ! ( result, Some ( 11048 ) ) ;
179
155
}
180
156
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
+
181
165
#[ test]
182
166
fn test_part_two_1 ( ) {
183
167
let result = part_two ( & advent_of_code:: template:: read_file_part (
@@ -193,4 +177,12 @@ mod tests {
193
177
) ) ;
194
178
assert_eq ! ( result, Some ( 64 ) ) ;
195
179
}
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
+ }
196
188
}
0 commit comments