From: alex <> Date: Fri, 20 Dec 2024 08:25:27 +0000 (+0100) Subject: Day20 - part 1 X-Git-Url: https://aoc.elinar.fr/?a=commitdiff_plain;h=77f2c836ba1815c20e1f510f5b4e07fc163137b5;p=aoc_2024 Day20 - part 1 --- diff --git a/src/day20.rs b/src/day20.rs new file mode 100644 index 0000000..4ac9bb9 --- /dev/null +++ b/src/day20.rs @@ -0,0 +1,172 @@ +use std::error::Error; +use std::path::Path; +use std::collections::{HashMap, HashSet}; + +struct Puzzle { + path: HashMap<(isize, isize), u32>, + walls: HashSet<(isize, isize)>, + start: (isize, isize), + end: (isize, isize), +} + +impl Puzzle { + pub fn new(input: &str) -> Self { + let mut path: HashMap<(isize, isize), u32> = HashMap::new(); + let mut walls: HashSet<(isize, isize)> = HashSet::new(); + let mut start = (-1, -1); + let mut end = (-1, -1); + input.lines().enumerate() + .for_each(|(row, line)| { + line.as_bytes().iter().enumerate() + .for_each(|(col, v)| { + let (row, col) = (row as isize, col as isize); + match *v { + b'.' => { path.insert((row, col), 0); }, + b'#' => { walls.insert((row, col)); }, + b'S' => { + start = (row, col); + path.insert((row, col), 0); + }, + b'E' => { + end = (row, col); + path.insert((row, col), 0); + } + _ => unreachable!(), + } + }); + }); + + Puzzle::build_dst(&mut path, start, end); + + Self { path, walls, start, end } + } + + /* + * There is only one path between S and E. + */ + fn build_dst(path: &mut HashMap<(isize, isize), u32>, start: (isize, isize), end: (isize, isize)) { + let mut pos = start; + while pos != end { + let dst = *path.get(&pos).unwrap(); + for d in [(-1, 0), (0, 1), (1, 0), (0, -1)] { + let pos_next = (pos.0 + d.0, pos.1 + d.1); + if let Some(dst_next) = path.get_mut(&pos_next) { + if pos_next == start || *dst_next > 0 { + continue; + } + *dst_next = dst + 1; + pos = pos_next; + break; + } + } + } + } + + fn is_cheat_wall_horizontal(&self, wall: (isize, isize)) -> bool { + [(0, 1), (0, -1)].into_iter() + .map(|dir| (wall.0 + dir.0, wall.1 + dir.1)) + .all(|pos| self.path.contains_key(&pos)) + } + + fn is_cheat_wall_vertical(&self, wall: (isize, isize)) -> bool { + [(-1, 0), (1, 0)].into_iter() + .map(|dir| (wall.0 + dir.0, wall.1 + dir.1)) + .all(|pos| self.path.contains_key(&pos)) + } + + fn count_cheats_above(&self, ps: u32) -> u64 { + self.walls.iter() + .filter(|&w| self.is_cheat_wall_vertical(*w) || self.is_cheat_wall_horizontal(*w)) + .map(|&w| { + let (mut p1, mut p2) = (w, w); + let (mut dst1, mut dst2) = (0, 0); + if self.is_cheat_wall_vertical(w) + { + p1.0 -= 1; + p2.0 += 1; + dst1 = *self.path.get(&p1).unwrap(); + dst2 = *self.path.get(&p2).unwrap(); + } + else if self.is_cheat_wall_horizontal(w) + { + p1.1 -= 1; + p2.1 += 1; + dst1 = *self.path.get(&p1).unwrap(); + dst2 = *self.path.get(&p2).unwrap(); + } + dst1.abs_diff(dst2) + }) + .filter(|dst| *dst >= ps + 2) + .count() as u64 + } +} + +fn run_part1(input: &str) -> Result> { + println!("Running {} - part 1", get_day()); + let puzzle = Puzzle::new(input); + Ok(puzzle.count_cheats_above(100)) +} + +fn run_part2(input: &str) -> Result> { + println!("Running {} - part 2", get_day()); + + Ok(0) +} + +pub fn run(input: &str) -> Result<(), Box> { + let res = run_part1(input)?; + println!("{res}"); + + let res = run_part2(input)?; + println!("{res}"); + + Ok(()) +} + +fn get_day() -> String { + let filename = file!(); + Path::new(filename).file_stem().unwrap().to_str().unwrap().to_string() +} + +#[cfg(test)] +mod tests { + use super::*; + + static TEXT_INPUT: &str = "\ +############### +#...#...#.....# +#.#.#.#.#.###.# +#S#...#.#.#...# +#######.#.#.### +#######.#.#...# +#######.#.###.# +###..E#...#...# +###.#######.### +#...###...#...# +#.#####.#.###.# +#.#...#.#.#...# +#.#.#.#.#.#.### +#...#...#...### +###############"; + + #[test] + fn test_part1() { + let puzzle = Puzzle::new(TEXT_INPUT); + assert_eq!(1, puzzle.count_cheats_above(64)); + assert_eq!(2, puzzle.count_cheats_above(40)); + assert_eq!(3, puzzle.count_cheats_above(38)); + assert_eq!(4, puzzle.count_cheats_above(36)); + assert_eq!(5, puzzle.count_cheats_above(20)); + assert_eq!(8, puzzle.count_cheats_above(12)); + assert_eq!(10, puzzle.count_cheats_above(10)); + assert_eq!(14, puzzle.count_cheats_above(8)); + assert_eq!(16, puzzle.count_cheats_above(6)); + assert_eq!(30, puzzle.count_cheats_above(4)); + assert_eq!(44, puzzle.count_cheats_above(2)); + } + + #[test] + fn test_part2() { + assert_eq!(0, run_part2(TEXT_INPUT).unwrap()); + } +} diff --git a/src/main.rs b/src/main.rs index 57cd4b5..c5ea0ce 100644 --- a/src/main.rs +++ b/src/main.rs @@ -23,6 +23,7 @@ pub mod day16; pub mod day17; pub mod day18; pub mod day19; +pub mod day20; fn main() { let args: Vec = env::args().collect(); @@ -64,6 +65,7 @@ fn run(day: &str, input_file: &str) -> Result<(), Box> { "day17" => day17::run(&input)?, "day18" => day18::run(&input)?, "day19" => day19::run(&input)?, + "day20" => day20::run(&input)?, _ => return Err(format!("unknown or unimplemented day \"{day}\"").into()), } Ok(())