Skip to content

Commit 26722d9

Browse files
committed
Faster day 16
1 parent b7152e8 commit 26722d9

File tree

3 files changed

+99
-96
lines changed

3 files changed

+99
-96
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@ Solutions for [Advent of Code](https://adventofcode.com/) in [Rust](https://www.
2626
| [Day 13](./src/bin/13.rs) | `90.9µs` | `91.8µs` |
2727
| [Day 14](./src/bin/14.rs) | `30.8µs` | `3.5ms` |
2828
| [Day 15](./src/bin/15.rs) | `119.7µs` | `591.0µs` |
29-
| [Day 16](./src/bin/16.rs) | `193.4µs` | `22.6ms` |
29+
| [Day 16](./src/bin/16.rs) | `158.9µs` | `202.6µs` |
3030

31-
**Total: 41.87ms**
31+
**Total: 19.44ms**
3232
<!--- benchmarking table --->
3333

3434
---

data/examples/16-3.txt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
################
2+
####.#####.#.#E#
3+
##...#...#.#.#.#
4+
##.###.#.#.#.#.#
5+
#......#.......#
6+
####.###.#######
7+
##...#.........#
8+
##.#.#.#######.#
9+
##.#...#.....#.#
10+
##S#.###.###.#.#
11+
################

src/bin/16.rs

Lines changed: 86 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
use object_pool::Pool;
21
use std::collections::VecDeque;
32

43
use mygrid::{
54
direction::{Direction, DOWN, LEFT, RIGHT, UP},
65
grid::Grid,
6+
point::Point,
77
};
88

99
advent_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

Comments
 (0)