Skip to content

Commit 7c46a56

Browse files
committed
Year 2024 Day 16
1 parent fe27da3 commit 7c46a56

File tree

7 files changed

+154
-4
lines changed

7 files changed

+154
-4
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ Performance is reasonable even on older hardware, for example a 2011 MacBook Pro
8787
| 13 | [Claw Contraption](https://adventofcode.com/2024/day/13) | [Source](src/year2024/day13.rs) | 14 |
8888
| 14 | [Restroom Redoubt](https://adventofcode.com/2024/day/14) | [Source](src/year2024/day14.rs) | 74 |
8989
| 15 | [Warehouse Woes](https://adventofcode.com/2024/day/15) | [Source](src/year2024/day15.rs) | 303 |
90+
| 16 | [Reindeer Maze](https://adventofcode.com/2024/day/16) | [Source](src/year2024/day16.rs) | 356 |
9091

9192
## 2023
9293

benches/benchmark.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,5 +88,5 @@ benchmark!(year2023
8888

8989
benchmark!(year2024
9090
day01, day02, day03, day04, day05, day06, day07, day08, day09, day10, day11, day12, day13,
91-
day14, day15
91+
day14, day15, day16
9292
);

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,5 +68,5 @@ library!(year2023 "Restore global snow production."
6868

6969
library!(year2024 "Locate the Chief Historian in time for the big Christmas sleigh launch."
7070
day01, day02, day03, day04, day05, day06, day07, day08, day09, day10, day11, day12, day13,
71-
day14, day15
71+
day14, day15, day16
7272
);

src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,5 +142,5 @@ run!(year2023
142142

143143
run!(year2024
144144
day01, day02, day03, day04, day05, day06, day07, day08, day09, day10, day11, day12, day13,
145-
day14, day15
145+
day14, day15, day16
146146
);

src/year2024/day16.rs

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
//! # Reindeer Maze
2+
//!
3+
//! Solves part one and part two simultaneously.
4+
//!
5+
//! Part one is a normal [Dijkstra](https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm)
6+
//! search from start to end.
7+
//!
8+
//! Part two is a a BFS *backwards* from the end to the finish, tracing the cost exactly
9+
//! to find all possible paths. This reuses the cost information from the Dijkstra without
10+
//! requiring any extra state keeping for the paths.
11+
use crate::util::grid::*;
12+
use crate::util::point::*;
13+
use std::collections::VecDeque;
14+
15+
type Input = (u32, usize);
16+
17+
/// Clockwise order starting with facing right.
18+
const DIRECTIONS: [Point; 4] = [RIGHT, DOWN, LEFT, UP];
19+
20+
pub fn parse(input: &str) -> Input {
21+
let grid = Grid::parse(input);
22+
let start = grid.find(b'S').unwrap();
23+
let end = grid.find(b'E').unwrap();
24+
25+
// Forwards Dijkstra. This can almost be treated as a BFS since turns are so much more
26+
// expensive than forward moves. The grid is 140 x 140 so there can never be more than 138
27+
// moves in a straight line. Maintaining two queues keeps moves in order and is much faster
28+
// than a heap.
29+
let mut todo_first = VecDeque::with_capacity(1_000);
30+
let mut todo_second = VecDeque::with_capacity(1_000);
31+
32+
// State is `(position, direction)`.
33+
let mut seen = grid.same_size_with([u32::MAX; 4]);
34+
let mut lowest = u32::MAX;
35+
36+
todo_first.push_back((start, 0, 0));
37+
seen[start][0] = 0;
38+
39+
while lowest == u32::MAX {
40+
while let Some((position, direction, cost)) = todo_first.pop_front() {
41+
// Once we find the end node then stop. All paths of the same cost must be in
42+
// this bucket, so have already been accounted for.
43+
if position == end {
44+
lowest = cost;
45+
break;
46+
}
47+
48+
// -1.rem_euclid(4) = 3
49+
let left = (direction + 3) % 4;
50+
let right = (direction + 1) % 4;
51+
let next = [
52+
(position + DIRECTIONS[direction], direction, cost + 1),
53+
(position, left, cost + 1000),
54+
(position, right, cost + 1000),
55+
];
56+
57+
for tuple @ (next_position, next_direction, next_cost) in next {
58+
if grid[next_position] != b'#' && next_cost < seen[next_position][next_direction] {
59+
if next_direction == direction {
60+
todo_first.push_back(tuple);
61+
} else {
62+
todo_second.push_back(tuple);
63+
}
64+
seen[next_position][next_direction] = next_cost;
65+
}
66+
}
67+
}
68+
69+
(todo_first, todo_second) = (todo_second, todo_first);
70+
}
71+
72+
// Backwards BFS
73+
let mut todo = VecDeque::new();
74+
let mut path = grid.same_size_with(false);
75+
76+
// Lowest paths can arrive at end node in multiple directions.
77+
for direction in 0..4 {
78+
if seen[end][direction] == lowest {
79+
todo.push_back((end, direction, lowest));
80+
}
81+
}
82+
83+
while let Some((position, direction, cost)) = todo.pop_front() {
84+
path[position] = true;
85+
if position == start {
86+
continue;
87+
}
88+
89+
// Reverse direction and subtract cost.
90+
let left = (direction + 3) % 4;
91+
let right = (direction + 1) % 4;
92+
let next = [
93+
(position - DIRECTIONS[direction], direction, cost - 1),
94+
(position, left, cost - 1000),
95+
(position, right, cost - 1000),
96+
];
97+
98+
for (next_position, next_direction, next_cost) in next {
99+
// Trace our cost step by step so it will exactly match possible paths.
100+
if next_cost == seen[next_position][next_direction] {
101+
todo.push_back((next_position, next_direction, next_cost));
102+
// Set cost back to `u32::MAX` to prevent redundant path explorations.
103+
seen[next_position][next_direction] = u32::MAX;
104+
}
105+
}
106+
}
107+
108+
(lowest, path.bytes.iter().filter(|&&b| b).count())
109+
}
110+
111+
pub fn part1(input: &Input) -> u32 {
112+
input.0
113+
}
114+
115+
pub fn part2(input: &Input) -> usize {
116+
input.1
117+
}

tests/test.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,5 +81,5 @@ test!(year2023
8181

8282
test!(year2024
8383
day01, day02, day03, day04, day05, day06, day07, day08, day09, day10, day11, day12, day13,
84-
day14, day15
84+
day14, day15, day16
8585
);

tests/year2024/day16.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
use aoc::year2024::day16::*;
2+
3+
const EXAMPLE: &str = "\
4+
#################
5+
#...#...#...#..E#
6+
#.#.#.#.#.#.#.#.#
7+
#.#.#.#...#...#.#
8+
#.#.#.#.###.#.#.#
9+
#...#.#.#.....#.#
10+
#.#.#.#.#.#####.#
11+
#.#...#.#.#.....#
12+
#.#.#####.#.###.#
13+
#.#.#.......#...#
14+
#.#.###.#####.###
15+
#.#.#...#.....#.#
16+
#.#.#.#####.###.#
17+
#.#.#.........#.#
18+
#.#.#.#########.#
19+
#S#.............#
20+
#################";
21+
22+
#[test]
23+
fn part1_test() {
24+
let input = parse(EXAMPLE);
25+
assert_eq!(part1(&input), 11048);
26+
}
27+
28+
#[test]
29+
fn part2_test() {
30+
let input = parse(EXAMPLE);
31+
assert_eq!(part2(&input), 64);
32+
}

0 commit comments

Comments
 (0)