From dc5f48821202b3890ecadd80c3b08d0efb54c1b7 Mon Sep 17 00:00:00 2001 From: Jan-Bulthuis Date: Mon, 16 Dec 2024 17:44:55 +0100 Subject: [PATCH] day16 --- aoc_2024/src/day16.rs | 287 ++++++++++++++++++++++++++++++++++++++++++ aoc_2024/src/lib.rs | 1 + 2 files changed, 288 insertions(+) create mode 100644 aoc_2024/src/day16.rs diff --git a/aoc_2024/src/day16.rs b/aoc_2024/src/day16.rs new file mode 100644 index 0000000..5bf475a --- /dev/null +++ b/aoc_2024/src/day16.rs @@ -0,0 +1,287 @@ +use std::{ + cmp::{Ordering, Reverse}, + collections::BinaryHeap, +}; + +use aoc_runner_derive::{aoc, aoc_generator}; +use hashbrown::HashSet; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum Cell { + Empty, + Wall, +} + +type Input = ((usize, usize), (usize, usize), Vec>); + +#[aoc_generator(day16)] +fn parse(input: &str) -> Input { + let mut pos = (0, 0); + let mut end = (0, 0); + let map = input + .lines() + .enumerate() + .map(|(y, line)| { + line.chars() + .enumerate() + .map(|(x, c)| match c { + '#' => Cell::Wall, + '.' => Cell::Empty, + 'E' => { + end = (x, y); + Cell::Empty + } + 'S' => { + pos = (x, y); + Cell::Empty + } + _ => unreachable!(), + }) + .collect() + }) + .collect(); + (pos, end, map) +} + +#[aoc(day16, part1)] +fn part1(input: &Input) -> i64 { + let start = (input.0 .0 as i64, input.0 .1 as i64); + let end = (input.1 .0 as i64, input.1 .1 as i64); + let map = input.2.clone(); + let w = map[0].len(); + let h = map.len(); + let mut d = vec![vec![[i64::MAX; 4]; w]; h]; + + #[allow(clippy::type_complexity)] + let mut queue: BinaryHeap> = BinaryHeap::new(); + d[start.1 as usize][start.0 as usize][1] = 0; + queue.push(Reverse((0, start.0, start.1, 1, 0))); + + while let Some(Reverse((dist, pos_x, pos_y, dir_x, dir_y))) = queue.pop() { + let dir_num = match (dir_x, dir_y) { + (0, -1) => 0, + (1, 0) => 1, + (0, 1) => 2, + (-1, 0) => 3, + _ => unreachable!(), + }; + + if d[pos_y as usize][pos_x as usize][dir_num] < dist { + continue; + } + + if (pos_x, pos_y) == end { + break; + } + + if map[(pos_y + dir_y) as usize][(pos_x + dir_x) as usize] == Cell::Empty { + let dist_next = &mut d[(pos_y + dir_y) as usize][(pos_x + dir_x) as usize][dir_num]; + if (dist + 1).cmp(dist_next) == Ordering::Less { + *dist_next = dist + 1; + queue.push(Reverse(( + *dist_next, + pos_x + dir_x, + pos_y + dir_y, + dir_x, + dir_y, + ))); + } + } + + { + let dist_next = &mut d[pos_y as usize][pos_x as usize][(dir_num + 3) % 4]; + if (dist + 1000).cmp(dist_next) == Ordering::Less { + *dist_next = dist + 1000; + queue.push(Reverse((*dist_next, pos_x, pos_y, dir_y, -dir_x))); + } + } + + { + let dist_next = &mut d[pos_y as usize][pos_x as usize][(dir_num + 1) % 4]; + if (dist + 1000).cmp(dist_next) == Ordering::Less { + *dist_next = dist + 1000; + queue.push(Reverse((*dist_next, pos_x, pos_y, -dir_y, dir_x))); + } + } + } + + *d[end.1 as usize][end.0 as usize].iter().min().unwrap() +} + +#[aoc(day16, part2)] +fn part2(input: &Input) -> i64 { + let start = (input.0 .0 as i64, input.0 .1 as i64); + let end = (input.1 .0 as i64, input.1 .1 as i64); + let map = input.2.clone(); + let w = map[0].len(); + let h = map.len(); + let mut d = vec![vec![[i64::MAX; 4]; w]; h]; + let mut b = vec![vec![[vec![], vec![], vec![], vec![]]; w]; h]; + + #[allow(clippy::type_complexity)] + let mut queue: BinaryHeap> = BinaryHeap::new(); + d[start.1 as usize][start.0 as usize][1] = 0; + queue.push(Reverse((0, start.0, start.1, 1, 0))); + + while let Some(Reverse((dist, pos_x, pos_y, dir_x, dir_y))) = queue.pop() { + let dir_num = match (dir_x, dir_y) { + (0, -1) => 0, + (1, 0) => 1, + (0, 1) => 2, + (-1, 0) => 3, + _ => unreachable!(), + }; + + if d[pos_y as usize][pos_x as usize][dir_num] < dist { + continue; + } + + if (pos_x, pos_y) == end { + break; + } + + if map[(pos_y + dir_y) as usize][(pos_x + dir_x) as usize] == Cell::Empty { + let dist_next = &mut d[(pos_y + dir_y) as usize][(pos_x + dir_x) as usize][dir_num]; + let back_next = &mut b[(pos_y + dir_y) as usize][(pos_x + dir_x) as usize][dir_num]; + if (dist + 1).cmp(dist_next) == Ordering::Less { + *dist_next = dist + 1; + back_next.clear(); + back_next.push((pos_x, pos_y, dir_num)); + queue.push(Reverse(( + *dist_next, + pos_x + dir_x, + pos_y + dir_y, + dir_x, + dir_y, + ))); + } else if (dist + 1).cmp(dist_next) == Ordering::Equal { + back_next.push((pos_x, pos_y, dir_num)); + } + } + + { + let dist_next = &mut d[pos_y as usize][pos_x as usize][(dir_num + 3) % 4]; + let back_next = &mut b[pos_y as usize][pos_x as usize][(dir_num + 3) % 4]; + if (dist + 1000).cmp(dist_next) == Ordering::Less { + *dist_next = dist + 1000; + back_next.clear(); + back_next.push((pos_x, pos_y, dir_num)); + queue.push(Reverse((*dist_next, pos_x, pos_y, dir_y, -dir_x))); + } else if (dist + 1000).cmp(dist_next) == Ordering::Equal { + back_next.push((pos_x, pos_y, dir_num)); + } + } + + { + let dist_next = &mut d[pos_y as usize][pos_x as usize][(dir_num + 1) % 4]; + let back_next = &mut b[pos_y as usize][pos_x as usize][(dir_num + 1) % 4]; + if (dist + 1000).cmp(dist_next) == Ordering::Less { + *dist_next = dist + 1000; + back_next.clear(); + back_next.push((pos_x, pos_y, dir_num)); + queue.push(Reverse((*dist_next, pos_x, pos_y, -dir_y, dir_x))); + } else if (dist + 1000).cmp(dist_next) == Ordering::Equal { + back_next.push((pos_x, pos_y, dir_num)); + } + } + } + + let mut visited = HashSet::new(); + let dist_min = *d[end.1 as usize][end.0 as usize].iter().min().unwrap(); + let mut stack = vec![]; + d[end.1 as usize][end.0 as usize] + .iter() + .enumerate() + .for_each(|(i, d)| { + if *d == dist_min { + stack.push((end.0, end.1, i)); + } + }); + + while let Some((pos_x, pos_y, dir_num)) = stack.pop() { + visited.insert((pos_x, pos_y)); + stack.append(&mut b[pos_y as usize][pos_x as usize][dir_num]); + } + + visited.len() as i64 +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn part1_example() { + assert_eq!( + part1(&parse( + "############### +#.......#....E# +#.#.###.#.###.# +#.....#.#...#.# +#.###.#####.#.# +#.#.#.......#.# +#.#.#####.###.# +#...........#.# +###.#.#####.#.# +#...#.....#.#.# +#.#.#.###.#.#.# +#.....#...#.#.# +#.###.#.#.#.#.# +#S..#.....#...# +###############" + )), + 7036 + ); + assert_eq!( + part1(&parse(include_str!("../input/2024/day16.txt"))), + 85420 + ); + } + + #[test] + fn part2_example() { + assert_eq!( + part2(&parse( + "############### +#.......#....E# +#.#.###.#.###.# +#.....#.#...#.# +#.###.#####.#.# +#.#.#.......#.# +#.#.#####.###.# +#...........#.# +###.#.#####.#.# +#...#.....#.#.# +#.#.#.###.#.#.# +#.....#...#.#.# +#.###.#.#.#.#.# +#S..#.....#...# +###############" + )), + 45 + ); + assert_eq!( + part2(&parse( + "################# +#...#...#...#..E# +#.#.#.#.#.#.#.#.# +#.#.#.#...#...#.# +#.#.#.#.###.#.#.# +#...#.#.#.....#.# +#.#.#.#.#.#####.# +#.#...#.#.#.....# +#.#.#####.#.###.# +#.#.#.......#...# +#.#.###.#####.### +#.#.#...#.....#.# +#.#.#.#####.###.# +#.#.#.........#.# +#.#.#.#########.# +#S#.............# +#################" + )), + 64 + ); + assert_eq!(part2(&parse(include_str!("../input/2024/day16.txt"))), 492); + } +} diff --git a/aoc_2024/src/lib.rs b/aoc_2024/src/lib.rs index 0e6b7ae..c53a429 100644 --- a/aoc_2024/src/lib.rs +++ b/aoc_2024/src/lib.rs @@ -5,6 +5,7 @@ mod day12; mod day13; mod day14; mod day15; +mod day16; mod day2; mod day3; mod day4;