Skip to content

Commit 1d911da

Browse files
committed
Day 16 solutions
1 parent 33caf5f commit 1d911da

File tree

5 files changed

+467
-4
lines changed

5 files changed

+467
-4
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,3 +101,4 @@ This should start the server at `localhost:8080`.
101101
❄️ [Day 13](aoc-solver/src/y2024/day13.rs)
102102
❄️ [Day 14](aoc-solver/src/y2024/day14.rs)
103103
❄️ [Day 15](aoc-solver/src/y2024/day15.rs)
104+
❄️ [Day 16](aoc-solver/src/y2024/day16.rs)

aoc-solver/src/y2024/day16.rs

Lines changed: 320 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,320 @@
1+
use std::{
2+
cmp::Ordering,
3+
collections::{BinaryHeap, HashMap, HashSet},
4+
};
5+
6+
use crate::solution::{AocError, Solution};
7+
8+
type Coords = (usize, usize);
9+
type Grid = Vec<Vec<bool>>;
10+
11+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
12+
enum Direction {
13+
North,
14+
East,
15+
South,
16+
West,
17+
}
18+
19+
impl Direction {
20+
fn as_delta(&self) -> (isize, isize) {
21+
match self {
22+
Direction::North => (0, -1),
23+
Direction::East => (1, 0),
24+
Direction::South => (0, 1),
25+
Direction::West => (-1, 0),
26+
}
27+
}
28+
29+
fn rotate_left(&self) -> Direction {
30+
match self {
31+
Direction::North => Direction::West,
32+
Direction::East => Direction::North,
33+
Direction::South => Direction::East,
34+
Direction::West => Direction::South,
35+
}
36+
}
37+
38+
fn rotate_right(&self) -> Direction {
39+
match self {
40+
Direction::North => Direction::East,
41+
Direction::East => Direction::South,
42+
Direction::South => Direction::West,
43+
Direction::West => Direction::North,
44+
}
45+
}
46+
}
47+
48+
#[derive(Clone, Eq, PartialEq)]
49+
struct Search {
50+
score: u32,
51+
position: Coords,
52+
direction: Direction,
53+
visited: HashSet<Coords>,
54+
}
55+
56+
impl Ord for Search {
57+
fn cmp(&self, other: &Self) -> Ordering {
58+
other.score.cmp(&self.score)
59+
}
60+
}
61+
62+
impl PartialOrd for Search {
63+
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
64+
Some(self.cmp(other))
65+
}
66+
}
67+
68+
fn parse(input: &str) -> Result<(Grid, Coords, Coords), AocError> {
69+
let mut grid = vec![];
70+
let mut start = None;
71+
let mut end = None;
72+
73+
for (y, line) in input.trim().lines().enumerate() {
74+
let mut row = vec![];
75+
for (x, tile) in line.chars().enumerate() {
76+
match tile {
77+
'#' => row.push(false),
78+
'.' => row.push(true),
79+
'S' => {
80+
row.push(true);
81+
start = Some((x, y))
82+
}
83+
'E' => {
84+
row.push(true);
85+
end = Some((x, y))
86+
}
87+
other => return Err(AocError::parse(other, "Unexpected tile")),
88+
}
89+
}
90+
grid.push(row);
91+
}
92+
93+
match (start, end) {
94+
(Some(start), Some(end)) => Ok((grid, start, end)),
95+
_ => Err(AocError::parse(input, "Missing start or end")),
96+
}
97+
}
98+
99+
fn dijkstra(grid: &Grid, start: Coords, end: Coords, find_all: bool) -> Option<u32> {
100+
let mut scores: HashMap<(Coords, Direction), u32> = HashMap::new();
101+
let mut heap: BinaryHeap<Search> = BinaryHeap::new();
102+
103+
let height = grid.len() as isize;
104+
let width = grid[0].len() as isize;
105+
106+
heap.push(Search {
107+
score: 0,
108+
direction: Direction::East,
109+
position: start,
110+
visited: HashSet::new(),
111+
});
112+
113+
let mut best_score: Option<u32> = None;
114+
let mut best_path_tiles: HashSet<Coords> = HashSet::new();
115+
116+
while let Some(Search {
117+
score,
118+
direction,
119+
position,
120+
mut visited,
121+
}) = heap.pop()
122+
{
123+
if !visited.insert(position) {
124+
continue;
125+
}
126+
127+
if score > *scores.get(&(position, direction)).unwrap_or(&u32::MAX) {
128+
continue;
129+
}
130+
131+
if position == end {
132+
if find_all {
133+
match best_score {
134+
None => {
135+
best_score = Some(score);
136+
best_path_tiles.extend(visited.iter());
137+
}
138+
Some(best) if best == score => {
139+
best_path_tiles.extend(visited.iter());
140+
}
141+
_ => {}
142+
}
143+
} else {
144+
return Some(score);
145+
}
146+
}
147+
148+
for (next_direction, turn_cost) in [
149+
(direction, 0),
150+
(direction.rotate_left(), 1000),
151+
(direction.rotate_right(), 1000),
152+
] {
153+
let next_score = score + turn_cost + 1;
154+
155+
let (dx, dy) = next_direction.as_delta();
156+
let (x, y) = (position.0 as isize + dx, position.1 as isize + dy);
157+
158+
if x < 0 || y < 0 || x >= width || y >= height {
159+
continue;
160+
}
161+
162+
if !grid[y as usize][x as usize] {
163+
continue;
164+
}
165+
166+
let next_pos = (x as usize, y as usize);
167+
let best_known = scores.entry((next_pos, next_direction)).or_insert(u32::MAX);
168+
169+
if next_score <= *best_known {
170+
*best_known = next_score;
171+
172+
heap.push(Search {
173+
score: next_score,
174+
direction: next_direction,
175+
position: next_pos,
176+
visited: visited.clone(),
177+
});
178+
}
179+
}
180+
}
181+
182+
if find_all {
183+
return Some(best_path_tiles.len() as u32);
184+
}
185+
186+
None
187+
}
188+
189+
pub struct Day16;
190+
impl Solution for Day16 {
191+
type A = u32;
192+
type B = u32;
193+
194+
fn default_input(&self) -> &'static str {
195+
include_str!("../../../inputs/2024/day16.txt")
196+
}
197+
198+
fn part_1(&self, input: &str) -> Result<u32, AocError> {
199+
let (grid, start, end) = parse(input)?;
200+
201+
let smallest_score =
202+
dijkstra(&grid, start, end, false).ok_or_else(|| AocError::logic("No path found"))?;
203+
204+
Ok(smallest_score)
205+
}
206+
207+
fn part_2(&self, input: &str) -> Result<u32, AocError> {
208+
let (grid, start, end) = parse(input)?;
209+
210+
let smallest_score =
211+
dijkstra(&grid, start, end, true).ok_or_else(|| AocError::logic("No path found"))?;
212+
213+
Ok(smallest_score)
214+
}
215+
}
216+
217+
#[cfg(test)]
218+
mod tests {
219+
use super::*;
220+
221+
#[test]
222+
fn it_solves_part1_example_1() {
223+
assert_eq!(
224+
Day16.part_1(
225+
"###############\n\
226+
#.......#....E#\n\
227+
#.#.###.#.###.#\n\
228+
#.....#.#...#.#\n\
229+
#.###.#####.#.#\n\
230+
#.#.#.......#.#\n\
231+
#.#.#####.###.#\n\
232+
#...........#.#\n\
233+
###.#.#####.#.#\n\
234+
#...#.....#.#.#\n\
235+
#.#.#.###.#.#.#\n\
236+
#.....#...#.#.#\n\
237+
#.###.#.#.#.#.#\n\
238+
#S..#.....#...#\n\
239+
###############"
240+
),
241+
Ok(7036)
242+
);
243+
}
244+
245+
#[test]
246+
fn it_solves_part1_example_2() {
247+
assert_eq!(
248+
Day16.part_1(
249+
"#################\n\
250+
#...#...#...#..E#\n\
251+
#.#.#.#.#.#.#.#.#\n\
252+
#.#.#.#...#...#.#\n\
253+
#.#.#.#.###.#.#.#\n\
254+
#...#.#.#.....#.#\n\
255+
#.#.#.#.#.#####.#\n\
256+
#.#...#.#.#.....#\n\
257+
#.#.#####.#.###.#\n\
258+
#.#.#.......#...#\n\
259+
#.#.###.#####.###\n\
260+
#.#.#...#.....#.#\n\
261+
#.#.#.#####.###.#\n\
262+
#.#.#.........#.#\n\
263+
#.#.#.#########.#\n\
264+
#S#.............#\n\
265+
#################"
266+
),
267+
Ok(11048)
268+
);
269+
}
270+
271+
#[test]
272+
fn it_solves_part2_example_1() {
273+
assert_eq!(
274+
Day16.part_2(
275+
"###############\n\
276+
#.......#....E#\n\
277+
#.#.###.#.###.#\n\
278+
#.....#.#...#.#\n\
279+
#.###.#####.#.#\n\
280+
#.#.#.......#.#\n\
281+
#.#.#####.###.#\n\
282+
#...........#.#\n\
283+
###.#.#####.#.#\n\
284+
#...#.....#.#.#\n\
285+
#.#.#.###.#.#.#\n\
286+
#.....#...#.#.#\n\
287+
#.###.#.#.#.#.#\n\
288+
#S..#.....#...#\n\
289+
###############"
290+
),
291+
Ok(45)
292+
);
293+
}
294+
295+
#[test]
296+
fn it_solves_part2_example_2() {
297+
assert_eq!(
298+
Day16.part_2(
299+
"#################\n\
300+
#...#...#...#..E#\n\
301+
#.#.#.#.#.#.#.#.#\n\
302+
#.#.#.#...#...#.#\n\
303+
#.#.#.#.###.#.#.#\n\
304+
#...#.#.#.....#.#\n\
305+
#.#.#.#.#.#####.#\n\
306+
#.#...#.#.#.....#\n\
307+
#.#.#####.#.###.#\n\
308+
#.#.#.......#...#\n\
309+
#.#.###.#####.###\n\
310+
#.#.#...#.....#.#\n\
311+
#.#.#.#####.###.#\n\
312+
#.#.#.........#.#\n\
313+
#.#.#.#########.#\n\
314+
#S#.............#\n\
315+
#################"
316+
),
317+
Ok(64)
318+
);
319+
}
320+
}

aoc-solver/src/y2024/mod.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ pub mod day12;
1515
pub mod day13;
1616
pub mod day14;
1717
pub mod day15;
18-
// pub mod day16;
18+
pub mod day16;
1919
// pub mod day17;
2020
// pub mod day18;
2121
// pub mod day19;
@@ -26,7 +26,7 @@ pub mod day15;
2626
// pub mod day24;
2727
// pub mod day25;
2828

29-
pub const MAX_DAYS: u8 = 15;
29+
pub const MAX_DAYS: u8 = 16;
3030

3131
pub struct Y2024;
3232

@@ -48,7 +48,7 @@ impl Solver for Y2024 {
4848
13 => day13::Day13.run(input, 13, 2024),
4949
14 => day14::Day14.run(input, 14, 2024),
5050
15 => day15::Day15.run(input, 15, 2024),
51-
// 16 => day16::Day16.run(input, 16, 2024),
51+
16 => day16::Day16.run(input, 16, 2024),
5252
// 17 => day17::Day17.run(input, 17, 2024),
5353
// 18 => day18::Day18.run(input, 18, 2024),
5454
// 19 => day19::Day19.run(input, 19, 2024),
@@ -90,7 +90,7 @@ impl Solver for Y2024 {
9090
13 => include_str!("./day13.rs"),
9191
14 => include_str!("./day14.rs"),
9292
15 => include_str!("./day15.rs"),
93-
// 16 => include_str!("./day16.rs"),
93+
16 => include_str!("./day16.rs"),
9494
// 17 => include_str!("./day17.rs"),
9595
// 18 => include_str!("./day18.rs"),
9696
// 19 => include_str!("./day19.rs"),

aoc-web/src/header.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ pub fn header(props: &HeaderProps) -> Html {
3636
<NavLink route={Route::EasterEgg} current={props.route.clone()} text={"14+"}/>
3737
<NavLink route={Route::Solution { year: 2024, day: 15 }} current={props.route.clone()} text={"15"}/>
3838
<NavLink route={Route::WarehouseRobot} current={props.route.clone()} text={"15+"}/>
39+
<NavLink route={Route::Solution { year: 2024, day: 15 }} current={props.route.clone()} text={"16"}/>
3940
</>
4041
}
4142
},

0 commit comments

Comments
 (0)