From 7281c1088f788ac54cdca3d7068e4fd2ce01557c Mon Sep 17 00:00:00 2001 From: Daniel Lin Date: Tue, 17 Dec 2024 21:08:48 -0500 Subject: [PATCH] Day 16: Reindeer Maze --- README.md | 2 +- rs/benches/criterion.rs | 9 ++- rs/src/day16.rs | 158 ++++++++++++++++++++++++++++++++++++++++ rs/src/lib.rs | 1 + rs/src/main.rs | 11 ++- 5 files changed, 178 insertions(+), 3 deletions(-) create mode 100644 rs/src/day16.rs diff --git a/README.md b/README.md index 098127a5..922ebb2f 100644 --- a/README.md +++ b/README.md @@ -20,5 +20,5 @@ Development occurs in language-specific directories: |[Day13.hs](hs/src/Day13.hs)|[Day13.kt](kt/aoc2024-lib/src/commonMain/kotlin/com/github/ephemient/aoc2024/Day13.kt)|[day13.py](py/aoc2024/day13.py)|[day13.rs](rs/src/day13.rs)| |[Day14.hs](hs/src/Day14.hs)|[Day14.kt](kt/aoc2024-lib/src/commonMain/kotlin/com/github/ephemient/aoc2024/Day14.kt)|[day14.py](py/aoc2024/day14.py)|[day14.rs](rs/src/day14.rs)| |[Day15.hs](hs/src/Day15.hs)|[Day15.kt](kt/aoc2024-lib/src/commonMain/kotlin/com/github/ephemient/aoc2024/Day15.kt)|[day15.py](py/aoc2024/day15.py)|[day15.rs](rs/src/day15.rs)| -|[Day16.hs](hs/src/Day16.hs)|[Day16.kt](kt/aoc2024-lib/src/commonMain/kotlin/com/github/ephemient/aoc2024/Day16.kt)|[day16.py](py/aoc2024/day16.py)|| +|[Day16.hs](hs/src/Day16.hs)|[Day16.kt](kt/aoc2024-lib/src/commonMain/kotlin/com/github/ephemient/aoc2024/Day16.kt)|[day16.py](py/aoc2024/day16.py)|[day16.rs](rs/src/day16.rs)| |[Day17.hs](hs/src/Day17.hs)|[Day17.kt](kt/aoc2024-lib/src/commonMain/kotlin/com/github/ephemient/aoc2024/Day17.kt)||| diff --git a/rs/benches/criterion.rs b/rs/benches/criterion.rs index 0c7c1fc0..93c123f3 100644 --- a/rs/benches/criterion.rs +++ b/rs/benches/criterion.rs @@ -1,5 +1,6 @@ use aoc2024::{ - day1, day10, day11, day12, day13, day14, day15, day2, day3, day4, day5, day6, day7, day8, day9, + day1, day10, day11, day12, day13, day14, day15, day16, day2, day3, day4, day5, day6, day7, + day8, day9, }; use criterion::{black_box, Criterion}; use std::env; @@ -106,6 +107,12 @@ fn aoc2024_bench(c: &mut Criterion) -> io::Result<()> { g.bench_function("part 2", |b| b.iter(|| day15::part2(black_box(&data)))); g.finish(); + let data = get_day_input(16)?; + let mut g = c.benchmark_group("day 16"); + g.bench_function("part 1", |b| b.iter(|| day16::part1(black_box(&data)))); + g.bench_function("part 2", |b| b.iter(|| day16::part2(black_box(&data)))); + g.finish(); + Ok(()) } diff --git a/rs/src/day16.rs b/rs/src/day16.rs new file mode 100644 index 00000000..f70ad3b5 --- /dev/null +++ b/rs/src/day16.rs @@ -0,0 +1,158 @@ +use if_chain::if_chain; +use std::cmp::Reverse; +use std::collections::{BTreeMap, BTreeSet, BinaryHeap}; + +type YX = (usize, usize); + +fn parse(data: &str) -> Option<(BTreeSet, YX, YX)> { + let mut maze = BTreeSet::new(); + let mut start = None; + let mut end = None; + for (y, line) in data.lines().enumerate() { + for (x, b) in line.bytes().enumerate() { + match b { + b'#' => { + maze.insert((y, x)); + } + b'S' => { + if start.replace((y, x)).is_some() { + return None; + } + } + b'E' => { + if end.replace((y, x)).is_some() { + return None; + } + } + _ => {} + } + } + } + Some((maze, start?, end?)) +} + +pub fn part1(data: &str) -> Option { + let (maze, start, end) = parse(data)?; + let mut queue: BinaryHeap<_> = [(Reverse(0), start, (0, 1))].into(); + let mut visited = BTreeSet::new(); + while let Some((Reverse(score), (y, x), (dy, dx))) = queue.pop() { + if !visited.insert(((y, x), (dy, dx))) { + continue; + } + if (y, x) == end { + return Some(score); + } + for (score, (dy, dx)) in [ + (score + 1, (dy, dx)), + (score + 1001, (-dx, dy)), + (score + 1001, (dx, -dy)), + ] { + if_chain! { + if let Some(y) = y.checked_add_signed(dy); + if let Some(x) = x.checked_add_signed(dx); + if !maze.contains(&(y, x)); + then { + queue.push((Reverse(score), (y, x), (dy, dx))); + } + } + } + } + None +} + +pub fn part2(data: &str) -> Option { + let (maze, start, end) = parse(data)?; + let mut best = None; + let mut acc = BTreeSet::new(); + let mut queue: BinaryHeap<(_, _, _, BTreeSet<_>)> = + [(Reverse(0), start, (0, 1), [start].into())].into(); + let mut visited = BTreeMap::new(); + while let Some((Reverse(score), (y, x), (dy, dx), mut path)) = queue.pop() { + if best.filter(|best| *best < score).is_some() { + break; + } + if *visited.entry(((y, x), (dy, dx))).or_insert(score) < score { + continue; + } + if (y, x) == end { + best = Some(score); + acc.append(&mut path); + continue; + } + for (score, (dy, dx)) in [ + (score + 1, (dy, dx)), + (score + 1001, (-dx, dy)), + (score + 1001, (dx, -dy)), + ] { + if_chain! { + if let Some(y) = y.checked_add_signed(dy); + if let Some(x) = x.checked_add_signed(dx); + if !maze.contains(&(y, x)); + then { + let mut path = path.clone(); + if path.insert((y, x)) { + queue.push((Reverse(score), (y, x), (dy, dx), path)); + } + } + } + } + } + Some(acc.len()) +} + +#[cfg(test)] +mod tests { + use super::*; + use indoc::indoc; + use pretty_assertions::assert_eq; + + static EXAMPLE_1: &str = indoc! {" + ############### + #.......#....E# + #.#.###.#.###.# + #.....#.#...#.# + #.###.#####.#.# + #.#.#.......#.# + #.#.#####.###.# + #...........#.# + ###.#.#####.#.# + #...#.....#.#.# + #.#.#.###.#.#.# + #.....#...#.#.# + #.###.#.#.#.#.# + #S..#.....#...# + ############### + "}; + + static EXAMPLE_2: &str = indoc! {" + ################# + #...#...#...#..E# + #.#.#.#.#.#.#.#.# + #.#.#.#...#...#.# + #.#.#.#.###.#.#.# + #...#.#.#.....#.# + #.#.#.#.#.#####.# + #.#...#.#.#.....# + #.#.#####.#.###.# + #.#.#.......#...# + #.#.###.#####.### + #.#.#...#.....#.# + #.#.#.#####.###.# + #.#.#.........#.# + #.#.#.#########.# + #S#.............# + ################# + "}; + + #[test] + fn part1_examples() { + assert_eq!(Some(7036), part1(EXAMPLE_1)); + assert_eq!(Some(11048), part1(EXAMPLE_2)); + } + + #[test] + fn part2_examples() { + assert_eq!(Some(45), part2(EXAMPLE_1)); + assert_eq!(Some(64), part2(EXAMPLE_2)); + } +} diff --git a/rs/src/lib.rs b/rs/src/lib.rs index cd79b8c3..e3e48eab 100644 --- a/rs/src/lib.rs +++ b/rs/src/lib.rs @@ -5,6 +5,7 @@ pub mod day12; pub mod day13; pub mod day14; pub mod day15; +pub mod day16; pub mod day2; pub mod day3; pub mod day4; diff --git a/rs/src/main.rs b/rs/src/main.rs index 0885f0ed..fe54205d 100644 --- a/rs/src/main.rs +++ b/rs/src/main.rs @@ -1,6 +1,7 @@ use anyhow::anyhow; use aoc2024::{ - day1, day10, day11, day12, day13, day14, day15, day2, day3, day4, day5, day6, day7, day8, day9, + day1, day10, day11, day12, day13, day14, day15, day16, day2, day3, day4, day5, day6, day7, + day8, day9, }; use std::collections::HashSet; use std::env; @@ -139,5 +140,13 @@ fn main() -> anyhow::Result<()> { println!(); } + if args.is_empty() || args.contains("16") { + println!("Day 16"); + let data = get_day_input(16)?; + println!("{:?}", day16::part1(&data).ok_or(anyhow!("None"))?); + println!("{:?}", day16::part2(&data).ok_or(anyhow!("None"))?); + println!(); + } + Ok(()) }