diff --git a/.aoc_tiles/tiles/2024/21.png b/.aoc_tiles/tiles/2024/21.png index 1094c33..007aeb9 100644 Binary files a/.aoc_tiles/tiles/2024/21.png and b/.aoc_tiles/tiles/2024/21.png differ diff --git a/README.md b/README.md index ee1f321..46d6e9c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

- 2024 - 39 ⭐ - Rust + 2024 - 40 ⭐ - Rust

@@ -62,4 +62,7 @@ + + + diff --git a/src/day21.rs b/src/day21.rs new file mode 100644 index 0000000..9fa3caf --- /dev/null +++ b/src/day21.rs @@ -0,0 +1,206 @@ +use aoc_runner_derive::aoc; +use itertools::Itertools; +use std::iter::repeat_n; + +trait KeypadRobot { + fn new() -> Self; + fn press(&mut self, target: u8) -> Vec>; +} + +#[derive(Clone, Copy, Debug)] +struct NumberKeypadRobot { + pointing_at: u8, +} + +impl NumberKeypadRobot { + fn pos_of(button: u8) -> (i8, i8) { + match button { + b'7' => (0, 0), + b'8' => (1, 0), + b'9' => (2, 0), + b'4' => (0, 1), + b'5' => (1, 1), + b'6' => (2, 1), + b'1' => (0, 2), + b'2' => (1, 2), + b'3' => (2, 2), + b'X' => (0, 3), + b'0' => (1, 3), + b'A' => (2, 3), + c => unimplemented!("unexpected character {}", c), + } + } +} +impl KeypadRobot for NumberKeypadRobot { + fn new() -> Self { + Self { pointing_at: b'A' } + } + fn press(&mut self, target: u8) -> Vec> { + let cur_pos = Self::pos_of(self.pointing_at); + let goal_pos = Self::pos_of(target); + let x_ofs = goal_pos.0 - cur_pos.0; + let y_ofs = goal_pos.1 - cur_pos.1; + + let mut paths = Vec::new(); + + if (cur_pos.0 + x_ofs, cur_pos.1) != Self::pos_of(b'X') { + let mut x_first = Vec::new(); + x_first.extend(repeat_n(if x_ofs > 0 { b'>' } else { b'<' }, x_ofs.abs() as usize)); + x_first.extend(repeat_n(if y_ofs > 0 { b'v' } else { b'^' }, y_ofs.abs() as usize)); + x_first.push(b'A'); + paths.push(x_first); + } + if (cur_pos.0, cur_pos.1 + y_ofs) != Self::pos_of(b'X') { + let mut y_first = Vec::new(); + y_first.extend(repeat_n(if y_ofs > 0 { b'v' } else { b'^' }, y_ofs.abs() as usize)); + y_first.extend(repeat_n(if x_ofs > 0 { b'>' } else { b'<' }, x_ofs.abs() as usize)); + y_first.push(b'A'); + paths.push(y_first); + } + if paths.is_empty() { + panic!("all paths lead to the void"); + } + paths.dedup(); + self.pointing_at = target; + paths + } +} + +#[derive(Clone, Copy, Debug)] +struct DirectionKeypadRobot { + pointing_at: u8, + child: Option, +} + +impl DirectionKeypadRobot { + fn pos_of(target: u8) -> (i8, i8) { + match target { + b'X' => (0, 0), + b'^' => (1, 0), + b'A' => (2, 0), + b'<' => (0, 1), + b'v' => (1, 1), + b'>' => (2, 1), + c => unimplemented!("unexpected char {}", c), + } + } + fn move_to(&mut self, target: u8) -> Vec { + let cur_pos = Self::pos_of(self.pointing_at); + let goal_pos = Self::pos_of(target); + let x_ofs = goal_pos.0 - cur_pos.0; + let y_ofs = goal_pos.1 - cur_pos.1; + + self.pointing_at = target; + + if (cur_pos.0 + x_ofs, cur_pos.1) != Self::pos_of(b'X') { + let mut x_first = Vec::new(); + x_first.extend(repeat_n(if x_ofs > 0 { b'>' } else { b'<' }, x_ofs.abs() as usize)); + x_first.extend(repeat_n(if y_ofs > 0 { b'v' } else { b'^' }, y_ofs.abs() as usize)); + x_first.push(b'A'); + return x_first; + } + if (cur_pos.0, cur_pos.1 + y_ofs) != Self::pos_of(b'X') { + let mut y_first = Vec::new(); + y_first.extend(repeat_n(if y_ofs > 0 { b'v' } else { b'^' }, y_ofs.abs() as usize)); + y_first.extend(repeat_n(if x_ofs > 0 { b'>' } else { b'<' }, x_ofs.abs() as usize)); + y_first.push(b'A'); + return y_first; + } + panic!("all routes lead to the void"); + } + fn path_to(&mut self, moves: &Vec) -> Vec { + let prev_point = self.pointing_at; + let mut path = Vec::new(); + for m in moves { + path.append(&mut self.move_to(*m)); + } + self.pointing_at = prev_point; + path + } +} +impl KeypadRobot for DirectionKeypadRobot { + fn new() -> Self { + Self { + pointing_at: b'A', + child: None, + } + } + fn press(&mut self, target: u8) -> Vec> { + let path_options = self.child.as_mut().unwrap().press(target); + // for each path option, find our shortest route + let mut candidate_paths = Vec::new(); + for child_path in path_options { + let candidate_path = self.path_to(&child_path); + candidate_paths.push(candidate_path); + } + candidate_paths + } +} + +struct Code(Vec); + +impl Code { + fn num_val(&self) -> i64 { + String::from_utf8_lossy(&self.0.as_slice()[0..3]).parse().unwrap() + } +} + +fn parse(input: &str) -> Vec { + let mut codes = Vec::new(); + for code in input.lines() { + codes.push(Code(code.as_bytes().to_vec())) + } + codes +} + +#[aoc(day21, part1)] +fn part1(input: &str) -> i64 { + let codes = parse(input); + let mut sum = 0; + for code in &codes { + let numpad = NumberKeypadRobot::new(); + let mut robot1 = DirectionKeypadRobot::new(); + robot1.child = Some(numpad); + + let mut robot2 = DirectionKeypadRobot::new(); + robot2.child = Some(robot1); + + let mut path = Vec::new(); + for button in &code.0 { + let paths = robot2.press(*button); + path.push(paths); + } + let paths = path.clone().into_iter() + .multi_cartesian_product() + .map(|c| c.iter().flatten().map(|c| *c as char).join("")).collect_vec(); + let best = paths.iter().map(|p| p.len()).min().unwrap() as i64; + let score = code.num_val() * best; + sum += score; + } + sum +} + +#[aoc(day21, part2)] +fn part2(input: &str) -> i64 { + todo!() +} + +#[cfg(test)] +mod tests { + use super::*; + const EXAMPLE: &str = "029A +980A +179A +456A +379A"; + + #[test] + fn part1_example() { + assert_eq!(part1(EXAMPLE), 126384); + } + + #[test] + fn part2_example() { + assert_eq!(part2(EXAMPLE), 0); + } +} diff --git a/src/lib.rs b/src/lib.rs index 0d05b93..0a534d1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,6 +11,7 @@ pub mod day18; pub mod day19; pub mod day2; pub mod day20; +pub mod day21; pub mod day3; pub mod day4; pub mod day5;