|
| 1 | +use std::collections::{HashMap, VecDeque}; |
| 2 | +use std::fs; |
| 3 | + |
| 4 | +pub const DIRS: [(i32, i32); 4] = [(1, 0), (0, 1), (-1, 0), (0, -1)]; |
| 5 | + |
| 6 | +fn find_shortest_paths(keypad: &[[u8; 3]], from: u8, to: u8) -> Vec<Vec<u8>> { |
| 7 | + // find 'from' and 'to' on keypad |
| 8 | + let mut start = (0, 0); |
| 9 | + let mut end = (0, 0); |
| 10 | + for (y, row) in keypad.iter().enumerate() { |
| 11 | + for (x, &c) in row.iter().enumerate() { |
| 12 | + if c == from { |
| 13 | + start = (x, y); |
| 14 | + } |
| 15 | + if c == to { |
| 16 | + end = (x, y); |
| 17 | + } |
| 18 | + } |
| 19 | + } |
| 20 | + |
| 21 | + if start == end { |
| 22 | + return vec![vec![b'A']]; |
| 23 | + } |
| 24 | + |
| 25 | + // flood fill keypad to find the shortest distances |
| 26 | + let mut dists = vec![[usize::MAX; 3]; keypad.len()]; |
| 27 | + let mut queue = VecDeque::new(); |
| 28 | + queue.push_back((start.0, start.1, 0)); |
| 29 | + |
| 30 | + while let Some((x, y, steps)) = queue.pop_front() { |
| 31 | + dists[y][x] = steps; |
| 32 | + for (dx, dy) in DIRS { |
| 33 | + let nx = x as i32 + dx; |
| 34 | + let ny = y as i32 + dy; |
| 35 | + if nx >= 0 |
| 36 | + && ny >= 0 |
| 37 | + && nx < 3 |
| 38 | + && ny < keypad.len() as i32 |
| 39 | + && keypad[ny as usize][nx as usize] != b' ' |
| 40 | + && dists[ny as usize][nx as usize] == usize::MAX |
| 41 | + { |
| 42 | + queue.push_back((nx as usize, ny as usize, steps + 1)); |
| 43 | + } |
| 44 | + } |
| 45 | + } |
| 46 | + |
| 47 | + // backtrack from 'end' back to 'start' and collect all paths |
| 48 | + let mut paths = Vec::new(); |
| 49 | + let mut stack = Vec::new(); |
| 50 | + stack.push((end.0, end.1, vec![b'A'])); |
| 51 | + while let Some((x, y, path)) = stack.pop() { |
| 52 | + if x == start.0 && y == start.1 { |
| 53 | + paths.push(path); |
| 54 | + continue; |
| 55 | + } |
| 56 | + for (i, (dx, dy)) in DIRS.iter().enumerate() { |
| 57 | + let nx = x as i32 + dx; |
| 58 | + let ny = y as i32 + dy; |
| 59 | + if nx >= 0 |
| 60 | + && ny >= 0 |
| 61 | + && nx < 3 |
| 62 | + && ny < keypad.len() as i32 |
| 63 | + && dists[ny as usize][nx as usize] < dists[y][x] |
| 64 | + { |
| 65 | + // do everything in reverse |
| 66 | + let c = match i { |
| 67 | + 0 => b'<', |
| 68 | + 1 => b'^', |
| 69 | + 2 => b'>', |
| 70 | + 3 => b'v', |
| 71 | + _ => panic!(), |
| 72 | + }; |
| 73 | + let mut new_path = vec![c]; |
| 74 | + new_path.extend(&path); |
| 75 | + stack.push((nx as usize, ny as usize, new_path)); |
| 76 | + } |
| 77 | + } |
| 78 | + } |
| 79 | + |
| 80 | + paths |
| 81 | +} |
| 82 | + |
| 83 | +fn find_shortest_sequence( |
| 84 | + s: &[u8], |
| 85 | + depth: usize, |
| 86 | + highest: bool, |
| 87 | + cursors: &mut Vec<u8>, |
| 88 | + numeric: &[[u8; 3]], |
| 89 | + directional: &[[u8; 3]], |
| 90 | + cache: &mut HashMap<(Vec<u8>, usize, u8), usize>, |
| 91 | +) -> usize { |
| 92 | + let cache_key = (s.to_vec(), depth, cursors[depth]); |
| 93 | + if let Some(cached) = cache.get(&cache_key) { |
| 94 | + return *cached; |
| 95 | + } |
| 96 | + |
| 97 | + let mut result = 0; |
| 98 | + for &c in s { |
| 99 | + let paths = find_shortest_paths( |
| 100 | + if highest { numeric } else { directional }, |
| 101 | + cursors[depth], |
| 102 | + c, |
| 103 | + ); |
| 104 | + if depth == 0 { |
| 105 | + result += paths.into_iter().map(|l| l.len()).min().unwrap(); |
| 106 | + } else { |
| 107 | + result += paths |
| 108 | + .into_iter() |
| 109 | + .map(|p| { |
| 110 | + find_shortest_sequence( |
| 111 | + &p, |
| 112 | + depth - 1, |
| 113 | + false, |
| 114 | + cursors, |
| 115 | + numeric, |
| 116 | + directional, |
| 117 | + cache, |
| 118 | + ) |
| 119 | + }) |
| 120 | + .min() |
| 121 | + .unwrap(); |
| 122 | + } |
| 123 | + cursors[depth] = c; |
| 124 | + } |
| 125 | + |
| 126 | + cache.insert(cache_key, result); |
| 127 | + |
| 128 | + result |
| 129 | +} |
| 130 | + |
| 131 | +fn main() { |
| 132 | + let numeric = vec![ |
| 133 | + [b'7', b'8', b'9'], |
| 134 | + [b'4', b'5', b'6'], |
| 135 | + [b'1', b'2', b'3'], |
| 136 | + [b' ', b'0', b'A'], |
| 137 | + ]; |
| 138 | + |
| 139 | + let directional = vec![[b' ', b'^', b'A'], [b'<', b'v', b'>']]; |
| 140 | + |
| 141 | + let input = fs::read_to_string("input.txt").expect("Could not read file"); |
| 142 | + let lines = input.lines().collect::<Vec<_>>(); |
| 143 | + let mut cache = HashMap::new(); |
| 144 | + |
| 145 | + for part1 in [true, false] { |
| 146 | + let max_depth = if part1 { 2 } else { 25 }; |
| 147 | + |
| 148 | + let mut total = 0; |
| 149 | + for l in &lines { |
| 150 | + let mut cursors = vec![b'A'; max_depth + 1]; |
| 151 | + let len = find_shortest_sequence( |
| 152 | + l.as_bytes(), |
| 153 | + max_depth, |
| 154 | + true, |
| 155 | + &mut cursors, |
| 156 | + &numeric, |
| 157 | + &directional, |
| 158 | + &mut cache, |
| 159 | + ); |
| 160 | + |
| 161 | + let n = l[0..3].parse::<usize>().unwrap(); |
| 162 | + total += n * len; |
| 163 | + } |
| 164 | + println!("{}", total); |
| 165 | + } |
| 166 | +} |
0 commit comments