diff --git a/day21/src/main.rs b/day21/src/main.rs index d411a7f..50560b8 100644 --- a/day21/src/main.rs +++ b/day21/src/main.rs @@ -1,39 +1,75 @@ -use std::{ - collections::{BTreeMap, HashMap}, - iter::{once, repeat, repeat_n}, -}; +use std::{collections::BTreeMap, iter::once}; use aoc_derive::aoc_main; use graphs::{WeightedGraph, dijkstra}; -use itertools::{Itertools, iproduct}; +use itertools::Itertools; use math::Vec2D; use utils::*; -#[derive(Debug, Clone)] -struct KeypadRobot { - pos: Vec2D, - num_dirpads: usize, +// Order matters here! e.g., changing >^ to ^> changes the shortest path to the code. +// One could write an algorithm that tries all legal move orders and the permutations for each +// path, but lucklily they all seem to be independent, so I manually tried all legal move orders +// for each path, keeping the one that yielded the lowest answer in the end. +fn move_on_dirpad(from: char, to: char) -> String { + match (from, to) { + (from, to) if from == to => "", + + ('A', '>') => "v", + ('A', '<') => "v<<", + ('A', '^') => "<", + ('A', 'v') => "') => ">>", + ('<', 'v') => ">", + ('<', '^') => ">^", + ('<', 'A') => ">>^", + + ('^', 'A') => ">", + ('^', '>') => "v>", + ('^', 'v') => "v", + ('^', '<') => "v<", + + ('>', 'A') => "^", + ('>', '^') => "<^", + ('>', 'v') => "<", + ('>', '<') => "<<", + + ('v', 'A') => "^>", + ('v', '>') => ">", + ('v', '^') => "^", + ('v', '<') => "<", + + _ => unreachable!("{from} -> {to}"), + } + .to_string() + + "A" +} + +fn expand_moves(moves: &str) -> Vec { + ("A".to_string() + moves) + .chars() + .tuple_windows() + .map(|(from, to)| move_on_dirpad(from, to)) + .collect() } -fn check_path(start: Vec2D, path: &[char], forbidden: Vec2D) -> bool { - path.iter() - .try_fold(start, |pos, c| { - (pos != forbidden).then_some( - pos + Vec2D::from(match c { - '<' => (-1, 0), - '>' => (1, 0), - '^' => (0, -1), - 'v' => (0, 1), - _ => unreachable!(), - }), - ) +fn expand_path(path: BTreeMap) -> BTreeMap { + path.into_iter() + .flat_map(|(m, count)| expand_moves(&m).into_iter().map(move |m| (m, count))) + .fold(BTreeMap::new(), |mut acc, (m, count)| { + *acc.entry(m).or_insert(0) += count; + acc }) - .is_some() +} + +#[derive(Debug, Clone)] +struct KeypadRobot { + num_dirpads: usize, } impl KeypadRobot { fn new(num_dirpads: usize) -> Self { - Self { pos: Self::coord('A'), num_dirpads } + Self { num_dirpads } } fn coord(c: char) -> Vec2D { @@ -56,22 +92,25 @@ impl KeypadRobot { fn expand_move(&self, from: char, to: char) -> usize { (0..self.num_dirpads) - .fold([(make_move(from, to), 1)].into_iter().collect(), |path, _| expand_path(path)) + .fold([(move_on_dirpad(from, to), 1)].into_iter().collect(), |path, _| { + expand_path(path) + }) .into_iter() .map(|(path, count)| path.len() * count) .sum() } - fn enter_char(&mut self, c: char) -> usize { - let dist = - dijkstra(self, [(self.pos, 'A', false)], |&node| node == (Self::coord(c), 'A', true)); - - self.pos = Self::coord(c); - dist.unwrap() - } - fn enter_code(&mut self, code: &str) -> usize { - code.chars().map(|c| self.enter_char(c)).sum() + once('A') + .chain(code.chars()) + .tuple_windows() + .map(|(from, to)| { + dijkstra(self, [(Self::coord(from), 'A', false)], |&node| { + node == (Self::coord(to), 'A', true) + }) + .unwrap() + }) + .sum() } } @@ -104,13 +143,8 @@ impl WeightedGraph for KeypadRobot { } } -fn shortest_sequence(code: &str, num_dirpads: usize) -> usize { - KeypadRobot::new(num_dirpads).enter_code(code) -} - fn complexity(code: &str, num_dirpads: usize) -> usize { - println!("check code {code}"); - code[..3].parse::().unwrap() * shortest_sequence(code, num_dirpads) + code[..3].parse::().unwrap() * KeypadRobot::new(num_dirpads).enter_code(code) } #[aoc_main] @@ -121,104 +155,11 @@ fn solve(input: Input) -> impl Into { ) } -// 454307058698260 -// 1153311057543380 - -fn make_move(from: char, to: char) -> String { - match (from, to) { - (from, to) if from == to => "", - - ('A', '>') => "v", - ('A', '<') => "v<<", - ('A', '^') => "<", - ('A', 'v') => "v<", - - ('<', '>') => ">>", - ('<', 'v') => ">", - ('<', '^') => ">^", - ('<', 'A') => ">>^", - - ('^', 'A') => ">", - ('^', '>') => ">v", - ('^', 'v') => "v", - ('^', '<') => "v<", - - ('>', 'A') => "^", - ('>', '^') => "<^", - ('>', 'v') => "<", - ('>', '<') => "<<", - - ('v', 'A') => ">^", - ('v', '>') => ">", - ('v', '^') => "^", - ('v', '<') => "<", - - _ => unreachable!("{from} -> {to}"), - } - .to_string() - + "A" -} - -fn expand_move(m: &str) -> Vec { - ("A".to_string() + m).chars().tuple_windows().map(|(from, to)| make_move(from, to)).collect() -} - -fn expand_path(path: BTreeMap) -> BTreeMap { - path.into_iter() - .flat_map(|(m, count)| expand_move(&m).into_iter().map(move |m| (m, count))) - .fold(BTreeMap::new(), |mut acc, (m, count)| { - *acc.entry(m).or_insert(0) += count; - acc - }) -} - #[cfg(test)] mod tests { - use std::collections::BTreeSet; - use super::*; #[test] fn test_examples() { - dbg!(shortest_sequence("0", 2)); - dbg!(shortest_sequence("2", 2)); - dbg!(shortest_sequence("3", 2)); - dbg!(shortest_sequence("6", 2)); - dbg!(shortest_sequence("8", 2)); - - dbg!(make_move('A', '<')); - dbg!(expand_path([(make_move('A', '<'), 1)].into_iter().collect())); - - dbg!(make_move('<', 'A')); - dbg!(expand_path([(make_move('<', 'A'), 1)].into_iter().collect())); - - //dbg!(expand_path(BTreeMap::from([("A".to_string(), 1)]))); - // - //dbg!(expand_path(BTreeMap::from([("A".to_string(), 1)]))); - - dbg!(expand_path(BTreeMap::from([("