From 7f74e0f0b84b080dd61b103abca99410d432fc0c Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Wed, 25 Dec 2024 19:59:15 +0100 Subject: [PATCH] feat: add day 20 p1 --- README.md | 3 +- data/examples/20.txt | 15 ++++ src/bin/20.rs | 204 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 221 insertions(+), 1 deletion(-) create mode 100644 data/examples/20.txt create mode 100644 src/bin/20.rs diff --git a/README.md b/README.md index 63c7a12..cd5dfb1 100644 --- a/README.md +++ b/README.md @@ -53,8 +53,9 @@ Solutions for [Advent of Code](https://adventofcode.com/) in [Rust](https://www. | [Day 17](./src/bin/17.rs) | `1.5ms` | `-` | | [Day 18](./src/bin/18.rs) | `2.1ms` | `5.0ms` | | [Day 19](./src/bin/19.rs) | `21.1ms` | `707.2ms` | +| [Day 20](./src/bin/20.rs) | `754.2ms` | `-` | -**Total: 14003.90ms** +**Total: 14758.10ms** --- diff --git a/data/examples/20.txt b/data/examples/20.txt new file mode 100644 index 0000000..c097dae --- /dev/null +++ b/data/examples/20.txt @@ -0,0 +1,15 @@ +############### +#...#...#.....# +#.#.#.#.#.###.# +#S#...#.#.#...# +#######.#.#.### +#######.#.#...# +#######.#.###.# +###..E#...#...# +###.#######.### +#...###...#...# +#.#####.#.###.# +#.#...#.#.#...# +#.#.#.#.#.#.### +#...#...#...### +############### diff --git a/src/bin/20.rs b/src/bin/20.rs new file mode 100644 index 0000000..c3c7e49 --- /dev/null +++ b/src/bin/20.rs @@ -0,0 +1,204 @@ +advent_of_code::solution!(20); + +use std::collections::HashMap; + +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +struct Pos { + x: usize, + y: usize, +} + +impl Pos { + fn init(x: usize, y: usize) -> Pos { + Pos { x, y } + } + + fn neighbors(&self) -> Vec { + let mut res: Vec = Vec::new(); + if self.x > 0 { + res.push(Pos::init(self.x - 1, self.y)); + } + if self.y > 0 { + res.push(Pos::init(self.x, self.y - 1)); + } + res.push(Pos::init(self.x + 1, self.y)); + res.push(Pos::init(self.x, self.y + 1)); + res + } +} + +#[derive(Debug, Clone)] +struct PosState { + c: char, + dist: Option, +} + +impl From for PosState { + fn from(input: char) -> PosState { + PosState { + c: input, + dist: None, + } + } +} + +#[derive(Debug)] +struct State { + m: HashMap, + end: Pos, + start: Pos, +} + +impl State { + fn new() -> State { + State { + m: HashMap::new(), + end: Pos::init(0, 0), + start: Pos::init(0, 0), + } + } + + fn update(&self, p: Pos, ps: PosState) -> State { + let end = if ps.c == 'E' { + p.clone() + } else { + self.end.clone() + }; + let start = if ps.c == 'S' { + p.clone() + } else { + self.start.clone() + }; + let mut m = self.m.clone(); + m.insert(p, ps); + + State { m, end, start } + } + + fn fill_distances(&mut self, current: Vec, dist: usize) { + if current.is_empty() { + return; + } + let mut next: Vec = Vec::new(); + for cur in ¤t { + let mut add = false; + self.m.entry(cur.clone()).and_modify(|e| { + if e.dist.is_none() { + e.dist = Some(dist); + if e.c != '#' { + add = true; + } + } + }); + if add { + for n in cur.neighbors() { + if self.m.contains_key(&n) { + next.push(n); + } + } + } + } + next.sort(); + next.dedup(); + self.fill_distances(next, dist + 1); + } + + // fn print(&self) { + // for y in 0..15 { + // for x in 0..15 { + // let p = Pos::init(x, y); + // if let Some(e) = self.m.get(&p) { + // print!("{}{}{}{}{}{}", e.c, e.c, e.c, e.c, e.c, e.c); + // } + // } + // println!(); + // for x in 0..15 { + // let p = Pos::init(x, y); + // if let Some(e) = self.m.get(&p) { + // if let Some(d) = e.dist { + // print!("{}{:^4}{}", e.c, d, e.c); + // } else { + // print!("{}{}{}{}{}{}", e.c, e.c, e.c, e.c, e.c, e.c); + // } + // } + // } + // println!(); + // for x in 0..15 { + // let p = Pos::init(x, y); + // if let Some(e) = self.m.get(&p) { + // print!("{}{}{}{}{}{}", e.c, e.c, e.c, e.c, e.c, e.c); + // } + // } + // println!(); + // } + // } + + fn get_time_saving_cheats(&self) -> HashMap { + let mut cheats: HashMap = HashMap::new(); + + for (p, ps) in self.m.iter() { + if ps.c == '#' { + continue; + } + for n in p.neighbors() { + if let Some(ns) = self.m.get(&n) { + if ns.c == '#' && ns.dist.is_some() && ns.dist < ps.dist { + let cheat_save = + (ps.dist.unwrap() as isize) - (ns.dist.unwrap() as isize) - 1isize; + if cheat_save > 0 { + *cheats.entry(cheat_save as usize).or_default() += 1; + } + } + } + } + } + cheats + } +} + +fn parse_input(input: &str) -> State { + let mut state = input + .strip_suffix("\n") + .unwrap_or(input) + .lines() + .enumerate() + .flat_map(|(y, l)| { + l.chars() + .enumerate() + .map(|(x, c)| (Pos::init(x, y), PosState::from(c))) + .collect::>() + }) + .fold(State::new(), |st, (p, ps)| st.update(p, ps)); + state.fill_distances(vec![state.end.clone()], 0); + state +} + +pub fn part_one(input: &str) -> Option { + parse_input(input) + .get_time_saving_cheats() + .iter() + .filter_map(|(k, v)| if *k >= 100 { Some(v) } else { None }) + .sum::() + .into() +} + +pub fn part_two(_input: &str) -> Option { + None +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_part_one() { + let result = part_one(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, Some(0)); + } + + #[test] + fn test_part_two() { + let result = part_two(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, None); + } +}