use crate::util::maps::Direction; use std::collections::HashMap; const DPADS: usize = 25; pub fn day21() { let sequences: Vec = crate::input(21).lines().map(|l| l.to_string()).collect(); let mut memo: HashMap<(String, usize), u64> = HashMap::new(); fn deeper(s: String, depth: usize, memo: &mut HashMap<(String, usize), u64>) -> u64 { // if this is already in deep lut, return if let Some(&expanded_size) = memo.get(&(s.to_string(), depth)) { return expanded_size; } // if this is two characters at the end, add expanded size to deep lut, and return if depth == 1 { let expanded_size = expand_dpad_string(&s).len() as u64; memo.insert((s, depth), expanded_size); return expanded_size; } // otherwise 1. expand, 2. split into substrings, and 3. recur for each substring let expanded = expand_dpad_string(&s); // 2. get substrings split by A (but including the A characters at the end of each substring) let substrings: Vec = expanded .split_terminator("A") .map(|substring| { let mut substring = substring.to_string(); substring.push_str("A"); substring }) .collect(); let mut total: u64 = 0; for substring in substrings { let final_expanded_size = deeper(substring, depth - 1, memo); total += final_expanded_size; } memo.insert((s, depth), total); total } let mut complexity: u64 = 0; for sequence in sequences.iter() { let first_dpad_steps = expand_numpad_string(sequence); let length = deeper(first_dpad_steps.clone(), DPADS, &mut memo); let numeric = sequence .chars() .filter(|c| c.is_numeric()) .collect::() .parse::() .unwrap(); complexity += length * numeric; } println!("Total Complexity: {}", complexity); } fn expand_dpad_string(s: &str) -> String { let sequence: Vec = s.chars().map(char_to_directional_keypad_index).collect(); sequence_to_dpad_steps( &directional_keypad(), sequence, char_to_directional_keypad_index('A'), ) } fn expand_numpad_string(s: &str) -> String { let sequence: Vec = s.chars().map(char_to_numerical_keypad_index).collect(); sequence_to_dpad_steps( &numerical_keypad(), sequence, char_to_numerical_keypad_index('A'), ) } fn sequence_to_dpad_steps(keypad: &Keypad, sequence: Vec, start: usize) -> String { let mut sequence = sequence.clone(); sequence.insert(0, start); let mut total_path = "".to_string(); for w in sequence.windows(2) { let best_path = shortest_paths(keypad, w[0], w[1]) .into_iter() // convert paths to strings .map(|path| path.iter().map(direction_to_char).collect::()) // get the most efficient path (there are certain arbitrary rules for efficiency) .max_by_key(|path| efficiency_score(path.as_str())) .unwrap(); total_path.push_str(&best_path); total_path.push_str("A"); } total_path } fn shortest_paths(keypad: &Keypad, start: usize, end: usize) -> Vec> { fn path_costs(keypad: &Keypad, p: usize, cost_so_far: u64, costs: &mut HashMap) { if cost_so_far < costs.get(&p).copied().unwrap_or(u64::MAX) { costs.insert(p, cost_so_far); } else { return; } for &adjacent in keypad.adjacents(p) { path_costs(keypad, adjacent, cost_so_far + 1, costs); } } let mut costs: HashMap = HashMap::new(); path_costs(keypad, end, 0, &mut costs); fn find_paths( keypad: &Keypad, start: usize, costs: &HashMap, ) -> Vec> { if costs[&start] == 0 { return vec![vec![]]; } keypad.buttons[start] .links .iter() .filter(|(_, adjacent)| costs[adjacent] < costs[&start]) .flat_map(|(&direction, &adjacent)| { let mut paths = find_paths(keypad, adjacent, costs); paths.iter_mut().for_each(|path| { path.insert(0, direction); }); paths }) .collect() } find_paths(keypad, start, &costs) } fn efficiency_score(s: &str) -> u64 { let repetition_score = s .chars() .collect::>() .windows(2) .filter(|w| w[0] == w[1]) .count() as u64; let mut order_mishaps: u64 = 0; for ss in s.split("A") { order_mishaps += ss.matches(">v").count() as u64; order_mishaps += ss.matches("^<").count() as u64; order_mishaps += ss.matches("v<").count() as u64; order_mishaps += ss.matches(">^").count() as u64; } (repetition_score << 32) | (u32::MAX as u64 - order_mishaps) } struct Keypad { buttons: Vec