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: DirButton) { 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()); } } fn reversed(&self) -> Self { let mut path = [None; 6]; (0..self.steps).for_each(|i| { path[i] = match self.path[self.steps - i - 1] { Some(DirButton::Up) => Some(DirButton::Down), Some(DirButton::Down) => Some(DirButton::Up), Some(DirButton::Left) => Some(DirButton::Right), Some(DirButton::Right) => Some(DirButton::Left), Some(DirButton::A) => Some(DirButton::A), None => None, }; }); Self { steps: self.steps, path, } } } impl IntoIterator for Path { type Item = DirButton; 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), } } #[inline(always)] 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 precompute_steps(steps: &mut [DistanceMatrix]) { if steps.len() == 1 { return; } precompute_steps(&mut steps[1..]); let buttons = [ DirButton::Up, DirButton::Down, DirButton::Left, DirButton::Right, DirButton::A, ]; buttons.iter().enumerate().for_each(|(i, start)| { buttons[i + 1..].iter().for_each(|end| { let (forward, reversed) = start .to(end) .into_iter() .flatten() .map(|mut path| { path.steps -= 1; let mut reversed = path.reversed(); let mut forward = path; reversed.push(DirButton::A); forward.push(DirButton::A); ( forward .path .into_iter() .flatten() .fold((0, DirButton::A), |acc, next| { let extension = steps[1][acc.1.index()][next.index()]; (acc.0 + extension, next) }) .0, reversed .path .into_iter() .flatten() .fold((0, DirButton::A), |acc, next| { let extension = steps[1][acc.1.index()][next.index()]; (acc.0 + extension, next) }) .0, ) }) .fold((usize::MAX, usize::MAX), |acc, next| { (acc.0.min(next.0), acc.1.min(next.1)) }); steps[0][start.index()][end.index()] = forward; steps[0][end.index()][start.index()] = reversed; }); }); } 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 = steps[0][pos.index()][next.index()]; (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![[[1; 5]; 5]; 3]; precompute_steps(&mut steps); 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![[[1; 5]; 5]; 26]; precompute_steps(&mut steps); 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); } }