Skip to content

Commit e529ced

Browse files
committed
2024 day 16
1 parent 4c6da48 commit e529ced

File tree

4 files changed

+237
-0
lines changed

4 files changed

+237
-0
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
###############
2+
#.......#....E#
3+
#.#.###.#.###.#
4+
#.....#.#...#.#
5+
#.###.#####.#.#
6+
#.#.#.......#.#
7+
#.#.#####.###.#
8+
#...........#.#
9+
###.#.#####.#.#
10+
#...#.....#.#.#
11+
#.#.#.###.#.#.#
12+
#.....#...#.#.#
13+
#.###.#.#.#.#.#
14+
#S..#.....#...#
15+
###############
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#################
2+
#...#...#...#..E#
3+
#.#.#.#.#.#.#.#.#
4+
#.#.#.#...#...#.#
5+
#.#.#.#.###.#.#.#
6+
#...#.#.#.....#.#
7+
#.#.#.#.#.#####.#
8+
#.#...#.#.#.....#
9+
#.#.#####.#.###.#
10+
#.#.#.......#...#
11+
#.#.###.#####.###
12+
#.#.#...#.....#.#
13+
#.#.#.#####.###.#
14+
#.#.#.........#.#
15+
#.#.#.#########.#
16+
#S#.............#
17+
#################

crates/year2024/src/day16.rs

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
use std::cmp::Reverse;
2+
use std::collections::BinaryHeap;
3+
use utils::grid;
4+
use utils::prelude::*;
5+
6+
/// Finding the shortest paths through a maze.
7+
#[derive(Clone, Debug)]
8+
pub struct Day16 {
9+
grid: Vec<u8>,
10+
start: usize,
11+
end: usize,
12+
offsets: [isize; 4],
13+
cheapest: Vec<[u32; 4]>,
14+
part1: u32,
15+
}
16+
17+
impl Day16 {
18+
pub fn new(input: &str, _: InputType) -> Result<Self, InputError> {
19+
let (rows, cols, mut grid) = grid::from_str(input, |b| match b {
20+
b'.' | b'#' | b'S' | b'E' => Some(b),
21+
_ => None,
22+
})?;
23+
24+
if !grid::is_enclosed(rows, cols, &grid, |&b| b == b'#') {
25+
return Err(InputError::new(
26+
input,
27+
0,
28+
"expected grid to be enclosed by walls",
29+
));
30+
}
31+
32+
let mut starts = grid.iter().enumerate().filter(|(_, &b)| b == b'S');
33+
let Some((start, _)) = starts.next() else {
34+
return Err(InputError::new(input, 0, "expected one start"));
35+
};
36+
if starts.count() > 0 {
37+
return Err(InputError::new(input, 0, "expected one start"));
38+
}
39+
grid[start] = b'.';
40+
41+
let mut ends = grid.iter().enumerate().filter(|(_, &b)| b == b'E');
42+
let Some((end, _)) = ends.next() else {
43+
return Err(InputError::new(input, 0, "expected one end"));
44+
};
45+
if ends.count() > 0 {
46+
return Err(InputError::new(input, 0, "expected one end"));
47+
}
48+
grid[end] = b'.';
49+
50+
let mut instance = Self {
51+
cheapest: vec![[u32::MAX; 4]; grid.len()],
52+
part1: 0,
53+
grid,
54+
start,
55+
end,
56+
offsets: [1, cols as isize, -1, -(cols as isize)],
57+
};
58+
59+
// Precompute part 1 as dijkstra output is needed for both parts
60+
if !instance.dijkstra() {
61+
return Err(InputError::new(input, 'E', "no path"));
62+
}
63+
64+
Ok(instance)
65+
}
66+
67+
fn dijkstra(&mut self) -> bool {
68+
let mut queue = BinaryHeap::new();
69+
queue.push(Reverse((0, self.start, 0)));
70+
71+
while let Some(Reverse((score, index, dir))) = queue.pop() {
72+
if score > self.cheapest[index][dir] {
73+
continue;
74+
}
75+
if index == self.end {
76+
self.part1 = score;
77+
return true;
78+
}
79+
80+
for (next_dir, next_score) in [
81+
(dir, score + 1),
82+
((dir + 1) % 4, score + 1001),
83+
((dir + 3) % 4, score + 1001),
84+
] {
85+
let next = index.wrapping_add_signed(self.offsets[next_dir]);
86+
// Advancing to the next branch each time instead of the neighbor reduces the number
87+
// of items pushed to the priority queue significantly
88+
if let Some(branch) = self.find_branch(next, next_dir, next_score) {
89+
// Reverse needed to use BinaryHeap as a min heap and order by the lowest score
90+
queue.push(Reverse(branch));
91+
}
92+
}
93+
}
94+
95+
false
96+
}
97+
98+
fn find_branch(
99+
&mut self,
100+
mut index: usize,
101+
mut dir: usize,
102+
mut score: u32,
103+
) -> Option<(u32, usize, usize)> {
104+
if self.grid[index] != b'.' {
105+
return None;
106+
}
107+
108+
while index != self.end {
109+
let mut count = 0;
110+
let mut next_index = 0;
111+
let mut next_dir = 0;
112+
for d in [dir, (dir + 1) % 4, (dir + 3) % 4] {
113+
let i = index.wrapping_add_signed(self.offsets[d]);
114+
if self.grid[i] == b'.' {
115+
count += 1;
116+
next_index = i;
117+
next_dir = d;
118+
}
119+
}
120+
121+
if count == 0 {
122+
return None;
123+
} else if count > 1 {
124+
break;
125+
}
126+
127+
score += if dir == next_dir { 1 } else { 1001 };
128+
index = next_index;
129+
dir = next_dir;
130+
}
131+
132+
if score < self.cheapest[index][dir] {
133+
self.cheapest[index][dir] = score;
134+
Some((score, index, dir))
135+
} else {
136+
None
137+
}
138+
}
139+
140+
#[must_use]
141+
pub fn part1(&self) -> u32 {
142+
self.part1
143+
}
144+
145+
#[must_use]
146+
pub fn part2(&self) -> u32 {
147+
let mut on_best = vec![false; self.grid.len()];
148+
on_best[self.start] = true;
149+
on_best[self.end] = true;
150+
for d in 0..4 {
151+
if self.cheapest[self.end][d] == self.part1 {
152+
let prev = self.end.wrapping_add_signed(-self.offsets[d]);
153+
self.reverse(prev, d, self.part1 - 1, &mut on_best);
154+
}
155+
}
156+
on_best.iter().filter(|&&b| b).count() as u32
157+
}
158+
159+
fn reverse(&self, index: usize, dir: usize, score: u32, on_best: &mut [bool]) {
160+
if on_best[index] {
161+
return;
162+
}
163+
on_best[index] = true;
164+
165+
let mut count = 0;
166+
let mut next_index = 0;
167+
let mut next_dir = 0;
168+
for d in [dir, (dir + 1) % 4, (dir + 3) % 4] {
169+
let i = index.wrapping_add_signed(-self.offsets[d]);
170+
if self.grid[i] == b'.' {
171+
count += 1;
172+
next_index = i;
173+
next_dir = d;
174+
}
175+
}
176+
assert!(count > 0);
177+
178+
if count == 1 {
179+
let next_score = score - if dir == next_dir { 1 } else { 1001 };
180+
self.reverse(next_index, next_dir, next_score, on_best);
181+
} else {
182+
// At a branch, only continue down directions where the cheapest seen score matches
183+
for (next_dir, next_score) in [
184+
(dir, score),
185+
((dir + 1) % 4, score - 1000),
186+
((dir + 3) % 4, score - 1000),
187+
] {
188+
if self.cheapest[index][next_dir] == next_score {
189+
self.reverse(
190+
index.wrapping_add_signed(-self.offsets[next_dir]),
191+
next_dir,
192+
next_score - 1,
193+
on_best,
194+
);
195+
}
196+
}
197+
}
198+
}
199+
}
200+
201+
examples!(Day16 -> (u32, u32) [
202+
{file: "day16_example0.txt", part1: 7036, part2: 45},
203+
{file: "day16_example1.txt", part1: 11048, part2: 64},
204+
]);

crates/year2024/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,5 @@ utils::year!(2024 => year2024, ${
1717
13 => day13::Day13,
1818
14 => day14::Day14,
1919
15 => day15::Day15<'_>,
20+
16 => day16::Day16,
2021
});

0 commit comments

Comments
 (0)