use aoc_runner_derive::{aoc, aoc_generator}; #[derive(Debug, Clone, Copy, PartialEq, Eq)] struct Path { steps: usize, path: [Option; 6], } impl Path { fn new() -> Self { Self { steps: 0, path: [None, None, None, None, None, None], } } fn push(&mut self, button: T) { self.path[self.steps] = Some(button); self.steps += 1; } fn append(&mut self, other: &Self) { for i in 0..other.steps { self.push(other.path[i].unwrap()); } } } impl IntoIterator for Path { type Item = T; type IntoIter = std::iter::Flatten, 6>>; fn into_iter(self) -> Self::IntoIter { self.path.into_iter().flatten() } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum DirButton { Up, Down, Left, Right, A, } impl DirButton { fn pos(&self) -> (usize, usize) { match self { DirButton::Up => (1, 0), DirButton::Down => (1, 1), DirButton::Left => (0, 1), DirButton::Right => (2, 1), DirButton::A => (2, 0), } } fn index(&self) -> usize { match self { DirButton::Up => 0, DirButton::Down => 1, DirButton::Left => 2, DirButton::Right => 3, DirButton::A => 4, } } fn to(&self, other: &DirButton) -> [Option>; 2] { let start = self.pos(); let end = other.pos(); let mut x = Path::new(); let mut y = Path::new(); if start.0 < end.0 { (start.0..end.0).for_each(|_| x.push(DirButton::Right)); } else { (end.0..start.0).for_each(|_| x.push(DirButton::Left)); }; if start.1 < end.1 { (start.1..end.1).for_each(|_| y.push(DirButton::Down)); } else { (end.1..start.1).for_each(|_| y.push(DirButton::Up)); }; if start.0 == 0 && end.1 == 0 { x.append(&y); x.push(DirButton::A); [Some(x), None] } else if end.0 == 0 && start.1 == 0 { y.append(&x); y.push(DirButton::A); [Some(y), None] } else { let x_copy = x; x.append(&y); x.push(DirButton::A); y.append(&x_copy); y.push(DirButton::A); [Some(x), Some(y)] } } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum NumButton { One, Two, Three, Four, Five, Six, Seven, Eight, Nine, Zero, A, } impl NumButton { fn pos(&self) -> (usize, usize) { match self { NumButton::One => (0, 2), NumButton::Two => (1, 2), NumButton::Three => (2, 2), NumButton::Four => (0, 1), NumButton::Five => (1, 1), NumButton::Six => (2, 1), NumButton::Seven => (0, 0), NumButton::Eight => (1, 0), NumButton::Nine => (2, 0), NumButton::Zero => (1, 3), NumButton::A => (2, 3), } } fn to(&self, other: &NumButton) -> [Option>; 2] { let start = self.pos(); let end = other.pos(); let mut x = Path::new(); let mut y = Path::new(); if start.0 < end.0 { (start.0..end.0).for_each(|_| x.push(DirButton::Right)); } else { (end.0..start.0).for_each(|_| x.push(DirButton::Left)); }; if start.1 < end.1 { (start.1..end.1).for_each(|_| y.push(DirButton::Down)); } else { (end.1..start.1).for_each(|_| y.push(DirButton::Up)); }; if start.0 == 0 && end.1 == 3 { x.append(&y); x.push(DirButton::A); [Some(x), None] } else if end.0 == 0 && start.1 == 3 { y.append(&x); y.push(DirButton::A); [Some(y), None] } else { let x_copy = x; x.append(&y); x.push(DirButton::A); y.append(&x_copy); y.push(DirButton::A); [Some(x), Some(y)] } } } type Input = Vec<(usize, Vec)>; #[aoc_generator(day21)] fn parse(input: &str) -> Input { input .lines() .map(|line| { ( line[0..3].parse().unwrap(), line.chars() .map(|c| match c { '1' => NumButton::One, '2' => NumButton::Two, '3' => NumButton::Three, '4' => NumButton::Four, '5' => NumButton::Five, '6' => NumButton::Six, '7' => NumButton::Seven, '8' => NumButton::Eight, '9' => NumButton::Nine, '0' => NumButton::Zero, 'A' => NumButton::A, _ => unreachable!(), }) .collect(), ) }) .collect() } type DistanceMatrix = [[usize; 5]; 5]; fn calculate_steps(start: DirButton, end: DirButton, steps: &mut [DistanceMatrix]) -> usize { if steps.is_empty() { return 1; } if steps[0][start.index()][end.index()] == 0 { let extension = start .to(&end) .into_iter() .flatten() .map(|path| shortest_dir_path(path, &mut steps[1..])) .min() .unwrap(); steps[0][start.index()][end.index()] = extension; } steps[0][start.index()][end.index()] } fn shortest_dir_path(path: Path, steps: &mut [DistanceMatrix]) -> usize { path.path .into_iter() .flatten() .fold((0, DirButton::A), |acc, next| { let sum = acc.0; let pos = acc.1; let extension = calculate_steps(pos, next, steps); (sum + extension, next) }) .0 } fn shortest_path(path: Vec, steps: &mut [DistanceMatrix]) -> usize { path.into_iter() .fold((0, NumButton::A), |acc, end| { let sum = acc.0; let start = acc.1; let extension = start .to(&end) .into_iter() .flatten() .map(|path| shortest_dir_path(path, steps)) .min() .unwrap(); (sum + extension, end) }) .0 } #[aoc(day21, part1)] fn part1(input: &Input) -> usize { let mut steps = vec![[[0; 5]; 5]; 3 - 1]; input .iter() .cloned() .map(|(num, path)| num * shortest_path(path, &mut steps)) .sum() } #[aoc(day21, part2)] fn part2(input: &Input) -> usize { let mut steps = vec![[[0; 5]; 5]; 26 - 1]; input .iter() .cloned() .map(|(num, path)| num * shortest_path(path, &mut steps)) .sum() } #[cfg(test)] mod tests { use super::*; #[test] fn part1_example() { assert_eq!(part1(&parse("179A")), 68 * 179); assert_eq!(part1(&parse("379A")), 64 * 379); assert_eq!(part1(&parse("456A")), 64 * 456); assert_eq!(part1(&parse("980A")), 60 * 980); assert_eq!(part1(&parse("029A")), 68 * 29); } }