Add day21

This commit is contained in:
Adrian Groh 2024-12-22 17:13:04 +01:00
parent 0af0645dda
commit 2d3c79bf43
Signed by: Gobidev
GPG Key ID: 3AA3153E98B0D771
3 changed files with 305 additions and 0 deletions

131
day21/Cargo.lock generated Normal file
View File

@ -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"

9
day21/Cargo.toml Normal file
View File

@ -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"

165
day21/src/main.rs Normal file
View File

@ -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<char, IVec2> = 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<char, IVec2> = 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<String> {
input.lines().map(|l| l.to_string()).collect()
}
fn directions(pos1: &IVec2, pos2: &IVec2) -> Vec<Vec<Direction>> {
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<Vec<Direction>> {
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::<String>() + "A")
})
.multi_cartesian_product()
.map(|combination| {
combination
.iter()
.map(|c| min_length(c, &pads[1..], cache))
.sum::<usize>()
})
.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<Pad> = iter::once(Pad::Numpad)
.chain((0..keypad_robot_count).map(|_| Pad::Keypad))
.collect();
input
.iter()
.map(|s| s[..s.len() - 1].parse::<usize>().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));
}