diff --git a/src/day21.rs b/src/day21.rs new file mode 100644 index 0000000..57d7595 --- /dev/null +++ b/src/day21.rs @@ -0,0 +1,271 @@ +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