]> aoc.elinar.fr Git - aoc_2024/commitdiff
Day20 - part 1
authoralex <>
Fri, 20 Dec 2024 08:25:27 +0000 (09:25 +0100)
committeralex <>
Fri, 20 Dec 2024 08:25:27 +0000 (09:25 +0100)
src/day20.rs [new file with mode: 0644]
src/main.rs

diff --git a/src/day20.rs b/src/day20.rs
new file mode 100644 (file)
index 0000000..4ac9bb9
--- /dev/null
@@ -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<u64, Box<dyn Error>> {
+    println!("Running {} - part 1", get_day());
+    let puzzle = Puzzle::new(input);
+    Ok(puzzle.count_cheats_above(100))
+}
+
+fn run_part2(input: &str) -> Result<u64, Box<dyn Error>> {
+    println!("Running {} - part 2", get_day());
+
+    Ok(0)
+}
+
+pub fn run(input: &str) -> Result<(), Box<dyn Error>> {
+    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());
+    }
+}
index 57cd4b58039bad6bfbfcb9645d8754577bbacef0..c5ea0ce2e2a919ecb16dbaac457a404f7a752a11 100644 (file)
@@ -23,6 +23,7 @@ pub mod day16;
 pub mod day17;
 pub mod day18;
 pub mod day19;
+pub mod day20;
 
 fn main() {
     let args: Vec<String> = env::args().collect();
@@ -64,6 +65,7 @@ fn run(day: &str, input_file: &str) -> Result<(), Box<dyn Error>> {
         "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(())