From 2d3c79bf435fb41905c581479d43993321f0e06b Mon Sep 17 00:00:00 2001 From: Adrian Groh Date: Sun, 22 Dec 2024 17:13:04 +0100 Subject: [PATCH] Add day21 --- day21/Cargo.lock | 131 ++++++++++++++++++++++++++++++++++++ day21/Cargo.toml | 9 +++ day21/src/main.rs | 165 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 305 insertions(+) create mode 100644 day21/Cargo.lock create mode 100644 day21/Cargo.toml create mode 100644 day21/src/main.rs diff --git a/day21/Cargo.lock b/day21/Cargo.lock new file mode 100644 index 0000000..0c0fc68 --- /dev/null +++ b/day21/Cargo.lock @@ -0,0 +1,131 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "day21" +version = "0.1.0" +dependencies = [ + "glam", + "itertools", + "phf", +] + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "glam" +version = "0.29.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc46dd3ec48fdd8e693a98d2b8bafae273a2d54c1de02a2a7e3d57d501f39677" + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "phf" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +dependencies = [ + "phf_macros", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_macros" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "phf_shared" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +dependencies = [ + "siphasher", +] + +[[package]] +name = "proc-macro2" +version = "1.0.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + +[[package]] +name = "syn" +version = "2.0.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" diff --git a/day21/Cargo.toml b/day21/Cargo.toml new file mode 100644 index 0000000..c75f3b1 --- /dev/null +++ b/day21/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "day21" +version = "0.1.0" +edition = "2021" + +[dependencies] +phf = { version = "0.11.2", features = ["macros"] } +glam = "0.29.2" +itertools = "0.13.0" diff --git a/day21/src/main.rs b/day21/src/main.rs new file mode 100644 index 0000000..5f6bd22 --- /dev/null +++ b/day21/src/main.rs @@ -0,0 +1,165 @@ +use std::{collections::HashMap, fmt::Display, iter}; + +use glam::IVec2; +use itertools::Itertools; +use phf::phf_map; + +#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)] +enum Pad { + Keypad, + Numpad, +} + +#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)] +enum Direction { + Up, + Down, + Left, + Right, +} + +impl Direction { + fn to_vec(self) -> IVec2 { + match self { + Direction::Up => IVec2::new(-1, 0), + Direction::Down => IVec2::new(1, 0), + Direction::Left => IVec2::new(0, -1), + Direction::Right => IVec2::new(0, 1), + } + } +} + +impl Display for Direction { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Direction::Up => write!(f, "^"), + Direction::Down => write!(f, "v"), + Direction::Left => write!(f, "<"), + Direction::Right => write!(f, ">"), + } + } +} + +static NUMPAD_POSITIONS: phf::Map = phf_map! { + '7' => IVec2::new(0, 0), + '8' => IVec2::new(0, 1), + '9' => IVec2::new(0, 2), + '4' => IVec2::new(1, 0), + '5' => IVec2::new(1, 1), + '6' => IVec2::new(1, 2), + '1' => IVec2::new(2, 0), + '2' => IVec2::new(2, 1), + '3' => IVec2::new(2, 2), + '0' => IVec2::new(3, 1), + 'A' => IVec2::new(3, 2), +}; + +static KEYPAD_POSITIONS: phf::Map = phf_map! { + '^' => IVec2::new(0, 1), + 'A' => IVec2::new(0, 2), + '<' => IVec2::new(1, 0), + 'v' => IVec2::new(1, 1), + '>' => IVec2::new(1, 2), +}; + +fn parse(input: &str) -> Vec { + input.lines().map(|l| l.to_string()).collect() +} + +fn directions(pos1: &IVec2, pos2: &IVec2) -> Vec> { + let diff = pos2 - pos1; + let mut path_directions = vec![]; + if diff.x > 0 { + path_directions.extend((0..diff.x).map(|_| Direction::Down)); + } else { + path_directions.extend((0..diff.x.abs()).map(|_| Direction::Up)); + } + if diff.y > 0 { + path_directions.extend((0..diff.y).map(|_| Direction::Right)); + } else { + path_directions.extend((0..diff.y.abs()).map(|_| Direction::Left)); + } + let reverse = path_directions.iter().rev().map(|d| d.to_owned()).collect(); + if reverse == path_directions { + vec![path_directions] + } else { + vec![path_directions, reverse] + } +} + +fn is_allowed_path(pos1: &IVec2, directions: &[Direction], banned_pos: &IVec2) -> bool { + let mut pos = *pos1; + for direction in directions { + pos += direction.to_vec(); + if pos == *banned_pos { + return false; + } + } + true +} + +fn get_paths(pos1: &IVec2, pos2: &IVec2, banned_pos: &IVec2) -> Vec> { + directions(pos1, pos2) + .iter() + .filter(|d| is_allowed_path(pos1, d, banned_pos)) + .map(|d| d.to_owned()) + .collect() +} + +fn min_length(code: &str, pads: &[Pad], cache: &mut HashMap<(String, usize), usize>) -> usize { + // credits: RubixDev + + if pads.is_empty() { + return code.len(); + } + if let Some(val) = cache.get(&(code.to_string(), pads.len())) { + return *val; + } + let result = std::iter::once('A') + .chain(code.chars()) + .tuple_windows() + .map(|(start, end)| { + match pads[0] { + Pad::Numpad => get_paths( + NUMPAD_POSITIONS.get(&start).unwrap(), + NUMPAD_POSITIONS.get(&end).unwrap(), + &IVec2::new(3, 0), + ), + Pad::Keypad => get_paths( + KEYPAD_POSITIONS.get(&start).unwrap(), + KEYPAD_POSITIONS.get(&end).unwrap(), + &IVec2::new(0, 0), + ), + } + .into_iter() + .map(|dirs| dirs.iter().map(|d| d.to_string()).collect::() + "A") + }) + .multi_cartesian_product() + .map(|combination| { + combination + .iter() + .map(|c| min_length(c, &pads[1..], cache)) + .sum::() + }) + .min() + .unwrap(); + cache.insert((code.to_string(), pads.len()), result); + result +} + +fn part12(input: &[String], keypad_robot_count: usize) -> usize { + let mut cache = HashMap::new(); + let pads: Vec = iter::once(Pad::Numpad) + .chain((0..keypad_robot_count).map(|_| Pad::Keypad)) + .collect(); + input + .iter() + .map(|s| s[..s.len() - 1].parse::().unwrap() * min_length(s, &pads, &mut cache)) + .sum() +} + +fn main() { + let input = parse(include_str!("../input.txt")); + println!("{}", part12(&input, 2)); + println!("{}", part12(&input, 25)); +}