diff --git a/aoc_2024/src/day21.rs b/aoc_2024/src/day21.rs index 17170d9..4c21e16 100644 --- a/aoc_2024/src/day21.rs +++ b/aoc_2024/src/day21.rs @@ -1,135 +1,5 @@ use aoc_runner_derive::{aoc, aoc_generator}; -#[derive(Debug)] -struct Path { - path: [Option; 6], -} - -impl Path { - const fn from(buttons: &[DirButton]) -> Option { - let steps = buttons.len(); - let mut path = [None, None, None, None, None, None]; - let mut i = 0; - while i < steps { - path[i] = Some(buttons[i]); - i += 1; - } - path[i] = Some(DirButton::A); - Some(Self { path }) - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum DirButton { - Up, - Down, - Left, - Right, - A, -} - -const DIRBUTTON_PATHS: [[[Option; 2]; 5]; 5] = [ - // Up - [ - // Up - [Path::from(&[]), None], - // Down - [Path::from(&[DirButton::Down]), None], - // Left - [Path::from(&[DirButton::Down, DirButton::Left]), None], - // Right - [ - Path::from(&[DirButton::Down, DirButton::Right]), - Path::from(&[DirButton::Right, DirButton::Down]), - ], - // A - [Path::from(&[DirButton::Right]), None], - ], - // Down - [ - // Up - [Path::from(&[DirButton::Up]), None], - // Down - [Path::from(&[]), None], - // Left - [Path::from(&[DirButton::Left]), None], - // Right - [Path::from(&[DirButton::Right]), None], - // A - [ - Path::from(&[DirButton::Up, DirButton::Right]), - Path::from(&[DirButton::Right, DirButton::Up]), - ], - ], - // Left - [ - // Up - [Path::from(&[DirButton::Right, DirButton::Up]), None], - // Down - [Path::from(&[DirButton::Right]), None], - // Left - [Path::from(&[]), None], - // Right - [Path::from(&[DirButton::Right, DirButton::Right]), None], - // A - [ - Path::from(&[DirButton::Right, DirButton::Right, DirButton::Up]), - None, - ], - ], - // Right - [ - // Up - [ - Path::from(&[DirButton::Up, DirButton::Left]), - Path::from(&[DirButton::Left, DirButton::Up]), - ], - // Down - [Path::from(&[DirButton::Left]), None], - // Left - [Path::from(&[DirButton::Left, DirButton::Left]), None], - // Right - [Path::from(&[]), None], - // A - [Path::from(&[DirButton::Up]), None], - ], - // A - [ - // Up - [Path::from(&[DirButton::Left]), None], - // Down - [ - Path::from(&[DirButton::Down, DirButton::Left]), - Path::from(&[DirButton::Left, DirButton::Down]), - ], - // Left - [ - Path::from(&[DirButton::Down, DirButton::Left, DirButton::Left]), - None, - ], - // Right - [Path::from(&[DirButton::Down]), None], - // A - [Path::from(&[]), None], - ], -]; - -impl DirButton { - const fn index(&self) -> usize { - match self { - DirButton::Up => 0, - DirButton::Down => 1, - DirButton::Left => 2, - DirButton::Right => 3, - DirButton::A => 4, - } - } - - const fn paths_to(&self, other: &DirButton) -> &[Option; 2] { - &DIRBUTTON_PATHS[self.index()][other.index()] - } -} - #[derive(Debug, PartialEq, Eq)] enum NumButton { One, @@ -162,179 +32,350 @@ impl NumButton { } } - fn paths_to(&self, other: &NumButton) -> [Option; 2] { + const fn index(&self) -> usize { + self.pos().1 * 3 + self.pos().0 + } + + const fn value(&self) -> usize { + match self { + NumButton::One => 1, + NumButton::Two => 2, + NumButton::Three => 3, + NumButton::Four => 4, + NumButton::Five => 5, + NumButton::Six => 6, + NumButton::Seven => 7, + NumButton::Eight => 8, + NumButton::Nine => 9, + NumButton::Zero => 0, + NumButton::A => unimplemented!(), + } + } + + const fn from_index(idx: usize) -> Self { + match idx { + 0 => NumButton::Seven, + 1 => NumButton::Eight, + 2 => NumButton::Nine, + 3 => NumButton::Four, + 4 => NumButton::Five, + 5 => NumButton::Six, + 6 => NumButton::One, + 7 => NumButton::Two, + 8 => NumButton::Three, + 10 => NumButton::Zero, + 11 => NumButton::A, + _ => unreachable!(), + } + } + + const fn paths_to(&self, other: &NumButton) -> [Path; 2] { let start = self.pos(); let end = other.pos(); - let bx = if start.0 < end.0 { - DirButton::Right - } else { - DirButton::Left - }; + let bx = if start.0 < end.0 { RIGHT } else { LEFT }; let dx = start.0.abs_diff(end.0); - let by = if start.1 < end.1 { - DirButton::Down - } else { - DirButton::Up - }; + let by = if start.1 < end.1 { DOWN } else { UP }; let dy = start.1.abs_diff(end.1); - if start.0 == 0 && end.1 == 3 { - let mut x = [None, None, None, None, None, None]; - (0..dx).for_each(|i| { - x[i] = Some(bx); - }); - (0..dy).for_each(|i| { - x[i + dx] = Some(by); - }); - x[dx + dy] = Some(DirButton::A); - [Some(Path { path: x }), None] - } else if end.0 == 0 && start.1 == 3 { - let mut y = [None, None, None, None, None, None]; - (0..dx).for_each(|i| { - y[i + dy] = Some(bx); - }); - (0..dy).for_each(|i| { - y[i] = Some(by); - }); - y[dx + dy] = Some(DirButton::A); - [Some(Path { path: y }), None] + if (start.0 == 0 && end.1 == 3) || start.1 == end.1 { + let mut x = [DistanceSize::MAX; 6]; + let y = [DistanceSize::MAX; 6]; + let mut i = dx; + while i > 0 { + x[i - 1] = bx; + i -= 1; + } + let mut i = dy; + while i > 0 { + x[i - 1 + dx] = by; + i -= 1; + } + x[dx + dy] = A; + [x, y] + } else if (end.0 == 0 && start.1 == 3) || start.0 == end.0 { + let x = [DistanceSize::MAX; 6]; + let mut y = [DistanceSize::MAX; 6]; + let mut i = dx; + while i > 0 { + y[i - 1 + dy] = bx; + i -= 1; + } + let mut i = dy; + while i > 0 { + y[i - 1] = by; + i -= 1; + } + y[dx + dy] = A; + [y, x] } else { - let mut x = [None, None, None, None, None, None]; - let mut y = [None, None, None, None, None, None]; - (0..dx).for_each(|i| { - x[i] = Some(bx); - y[i + dy] = Some(bx); - }); - (0..dy).for_each(|i| { - x[i + dx] = Some(by); - y[i] = Some(by); - }); - x[dx + dy] = Some(DirButton::A); - y[dx + dy] = Some(DirButton::A); - [Some(Path { path: x }), Some(Path { path: y })] + let mut x = [DistanceSize::MAX; 6]; + let mut y = [DistanceSize::MAX; 6]; + let mut i = dx; + while i > 0 { + x[i - 1] = bx; + y[i - 1 + dy] = bx; + i -= 1; + } + let mut i = dy; + while i > 0 { + x[i - 1 + dx] = by; + y[i - 1] = by; + i -= 1; + } + x[dx + dy] = A; + y[dx + dy] = A; + [x, y] } } } -type Input = Vec<(DistanceSize, Vec)>; +type Input = String; #[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() + input.to_string() } -type DistanceSize = u64; +type Path = [DistanceSize; 6]; + +type DistanceSize = usize; type DistanceMatrix = [[DistanceSize; 5]; 5]; -fn precompute_steps(steps: &mut [DistanceMatrix]) { - if steps.len() == 1 { - return; +const fn shortest_dir_path(path: &Path, steps: &DistanceMatrix) -> DistanceSize { + let mut sum = 0; + let mut pos = A; + let mut i = 0; + + while i < 6 && path[i] != DistanceSize::MAX { + sum += steps[pos][path[i]]; + pos = path[i]; + i += 1; } - precompute_steps(&mut steps[1..]); - - let buttons = [ - DirButton::Up, - DirButton::Down, - DirButton::Left, - DirButton::Right, - DirButton::A, - ]; - - buttons.iter().for_each(|start| { - buttons.iter().for_each(|end| { - steps[0][start.index()][end.index()] = start - .paths_to(end) - .iter() - .flatten() - .map(|path| { - path.path - .iter() - .flatten() - .fold((0, &DirButton::A), |acc, next| { - (acc.0 + steps[1][acc.1.index()][next.index()], next) - }) - .0 - }) - .fold(DistanceSize::MAX, |acc, next| acc.min(next)); - }); - }); + sum } -fn shortest_dir_path(path: &Path, steps: &mut [DistanceMatrix]) -> DistanceSize { - path.path - .iter() - .flatten() - .fold((0, &DirButton::A), |acc, next| { - (acc.0 + steps[0][acc.1.index()][next.index()], next) - }) - .0 -} - -fn shortest_path(path: &[NumButton], steps: &mut [DistanceMatrix]) -> DistanceSize { - path.iter() - .fold((0, &NumButton::A), |acc, end| { - ( - acc.0 - + acc - .1 - .paths_to(end) - .iter() - .flatten() - .map(|path| shortest_dir_path(path, steps)) - .fold(DistanceSize::MAX, |acc, next| acc.min(next)), - end, +const fn shortest_path( + path: &[usize], + paths: &[[[Path; 2]; 12]; 12], + steps: &DistanceMatrix, +) -> DistanceSize { + let mut sum = 0; + let mut pos = NumButton::A.index(); + let mut i = 0; + while i < path.len() { + let step_paths = paths[pos][path[i]]; + sum += if step_paths[1][0] != DistanceSize::MAX { + min( + shortest_dir_path(&step_paths[0], steps), + shortest_dir_path(&step_paths[1], steps), ) - }) - .0 + } else { + shortest_dir_path(&step_paths[0], steps) + }; + pos = path[i]; + i += 1; + } + sum } +const UP: usize = 0; +const DOWN: usize = 1; +const LEFT: usize = 2; +const RIGHT: usize = 3; +const A: usize = 4; + +const fn min(a: usize, b: usize) -> usize { + if a < b { + a + } else { + b + } +} + +const fn precompute_steps_new(matrix: DistanceMatrix) -> DistanceMatrix { + let mut steps = 0; + let mut matrix = matrix; + + while steps < N { + let up = matrix[A][UP] + matrix[UP][A]; + let down = matrix[A][DOWN] + matrix[DOWN][A]; + let left = matrix[A][LEFT] + matrix[LEFT][A]; + let right = matrix[A][RIGHT] + matrix[RIGHT][A]; + let left_left = matrix[A][LEFT] + matrix[LEFT][LEFT] + matrix[LEFT][A]; + let right_right = matrix[A][RIGHT] + matrix[RIGHT][RIGHT] + matrix[RIGHT][A]; + let left_down = matrix[A][LEFT] + matrix[LEFT][DOWN] + matrix[DOWN][A]; + let down_left = matrix[A][DOWN] + matrix[DOWN][LEFT] + matrix[LEFT][A]; + let left_up = matrix[A][LEFT] + matrix[LEFT][UP] + matrix[UP][A]; + let up_left = matrix[A][UP] + matrix[UP][LEFT] + matrix[LEFT][A]; + let right_down = matrix[A][RIGHT] + matrix[RIGHT][DOWN] + matrix[DOWN][A]; + let down_right = matrix[A][DOWN] + matrix[DOWN][RIGHT] + matrix[RIGHT][A]; + let right_up = matrix[A][RIGHT] + matrix[RIGHT][UP] + matrix[UP][A]; + let up_right = matrix[A][UP] + matrix[UP][RIGHT] + matrix[RIGHT][A]; + let down_left_left = + matrix[A][DOWN] + matrix[DOWN][LEFT] + matrix[LEFT][LEFT] + matrix[LEFT][A]; + let right_right_up = + matrix[A][RIGHT] + matrix[RIGHT][RIGHT] + matrix[RIGHT][UP] + matrix[UP][A]; + + matrix[UP][UP] = 1; + matrix[UP][DOWN] = down; + matrix[UP][LEFT] = down_left; + matrix[UP][RIGHT] = min(down_right, right_down); + matrix[UP][A] = right; + + matrix[DOWN][UP] = up; + matrix[DOWN][DOWN] = 1; + matrix[DOWN][LEFT] = left; + matrix[DOWN][RIGHT] = right; + matrix[DOWN][A] = min(up_right, right_up); + + matrix[LEFT][UP] = right_up; + matrix[LEFT][DOWN] = right; + matrix[LEFT][LEFT] = 1; + matrix[LEFT][RIGHT] = right_right; + matrix[LEFT][A] = right_right_up; + + matrix[RIGHT][UP] = min(up_left, left_up); + matrix[RIGHT][DOWN] = left; + matrix[RIGHT][LEFT] = left_left; + matrix[RIGHT][RIGHT] = 1; + matrix[RIGHT][A] = up; + + matrix[A][UP] = left; + matrix[A][DOWN] = min(left_down, down_left); + matrix[A][LEFT] = down_left_left; + matrix[A][RIGHT] = down; + matrix[A][A] = 1; + + steps += 1; + } + + matrix +} + +const fn precompute_paths() -> [[[Path; 2]; 12]; 12] { + let mut paths = [[[[DistanceSize::MAX; 6], [DistanceSize::MAX; 6]]; 12]; 12]; + + let mut start = 0usize; + while start < 12 { + let mut end = 0usize; + if start == 9 { + start += 1; + continue; + } + while end < 12 { + if end == 9 { + end += 1; + continue; + } + + paths[start][end] = NumButton::from_index(start).paths_to(&NumButton::from_index(end)); + + end += 1; + } + start += 1; + } + + paths +} + +const fn precompute_num_paths( + steps: &[[usize; 5]; 5], + paths: &[[[[usize; 6]; 2]; 12]; 12], +) -> [[[usize; 12]; 12]; 12] { + let mut matrix = [[[0; 12]; 12]; 12]; + + let mut one = 0usize; + while one < 12 { + if one == 9 || one == 11 { + one += 1; + continue; + } + let mut two = 0usize; + while two < 12 { + if two == 9 || two == 11 { + two += 1; + continue; + } + let mut three = 0usize; + while three < 12 { + if three == 9 || three == 11 { + three += 1; + continue; + } + + let num = NumButton::from_index(one).value() * 100 + + NumButton::from_index(two).value() * 10 + + NumButton::from_index(three).value(); + matrix[one][two][three] = + num * shortest_path(&[one, two, three, NumButton::A.index()], paths, steps); + + three += 1; + } + two += 1; + } + one += 1; + } + + matrix +} + +const CHAR_IDX: [usize; 10] = [ + NumButton::Zero.index(), + NumButton::One.index(), + NumButton::Two.index(), + NumButton::Three.index(), + NumButton::Four.index(), + NumButton::Five.index(), + NumButton::Six.index(), + NumButton::Seven.index(), + NumButton::Eight.index(), + NumButton::Nine.index(), +]; + #[aoc(day21, part1)] fn part1(input: &Input) -> DistanceSize { - let mut steps = vec![[[1; 5]; 5]; 3]; - - precompute_steps(&mut steps); + let num_paths = const { + let steps = precompute_steps_new::<2>([[1; 5]; 5]); + let paths = precompute_paths(); + precompute_num_paths(&steps, &paths) + }; input - .iter() - .map(|(num, path)| num * shortest_path(path, &mut steps)) + .as_bytes() + .chunks(5) + .map(|chunk| { + let num = [ + CHAR_IDX[chunk[0] as usize - 48], + CHAR_IDX[chunk[1] as usize - 48], + CHAR_IDX[chunk[2] as usize - 48], + ]; + num_paths[num[0]][num[1]][num[2]] + }) .sum() } #[aoc(day21, part2)] fn part2(input: &Input) -> DistanceSize { - let mut steps = vec![[[1; 5]; 5]; 26]; - - precompute_steps(&mut steps); + let num_paths = const { + let steps = precompute_steps_new::<25>([[1; 5]; 5]); + let paths = precompute_paths(); + precompute_num_paths(&steps, &paths) + }; input - .iter() - .map(|(num, path)| num * shortest_path(path, &mut steps)) + .as_bytes() + .chunks(5) + .map(|chunk| { + let num = [ + CHAR_IDX[chunk[0] as usize - 48], + CHAR_IDX[chunk[1] as usize - 48], + CHAR_IDX[chunk[2] as usize - 48], + ]; + num_paths[num[0]][num[1]][num[2]] + }) .sum() } @@ -344,8 +385,8 @@ mod tests { #[test] fn part1_example() { - assert_eq!(part1(&parse("179A")), 68 * 179); assert_eq!(part1(&parse("379A")), 64 * 379); + assert_eq!(part1(&parse("179A")), 68 * 179); assert_eq!(part1(&parse("456A")), 64 * 456); assert_eq!(part1(&parse("980A")), 60 * 980); assert_eq!(part1(&parse("029A")), 68 * 29);