diff --git a/src/day21/1.rs b/src/day21/1.rs new file mode 100644 index 0000000..f381e12 --- /dev/null +++ b/src/day21/1.rs @@ -0,0 +1,207 @@ + +#![feature(test)] +extern crate test; + +use fxhash::FxHashMap; +use std::collections::VecDeque; +use rayon::prelude::*; +use fxhash::FxHashSet; + +const INPUTS: [&str; 2] = [ + "029A +980A +179A +456A +379A", + include_str!("./input.txt"), +]; + +const NAV_KEYPAD: [[char; 3]; 4] = [ + ['7', '8', '9'], + ['4', '5', '6'], + ['1', '2', '3'], + [' ', '0', 'A'], +]; + +const DIR_KEYPAD: [[char; 3]; 2] = [ + [' ', '^', 'A'], + ['<', 'v', '>'], +]; + + +pub fn run(input: &str) -> i64 { + let mut seq = input.lines().map(|x| x.chars().collect::>()).collect::>>(); + + let mut answer = 0; + + for seq in seq { + + let mut shortest = std::i64::MAX; + + let keypad_path = find_path_on_keypad(&seq); + + let shortest = keypad_path.into_par_iter().map(|keypad_auth| { + find_path_on_nav(&keypad_auth).into_par_iter().map(|nav1| { + find_path_on_nav(&nav1).into_iter().map(|x| x.len()).map(|x| x as i64).min().unwrap() + }).min().unwrap() + }).min().unwrap(); + + let seq = seq.iter().filter(|x| x.is_digit(10)).collect::().parse::().unwrap(); + answer += seq * shortest; + } + + answer +} + + +fn find_path_on_nav(seq: &[char]) -> Vec> { + + let find_coord = |x: char| match x { + '^' => (0,1), + 'A' => (0,2), + '<' => (1,0), + 'v' => (1,1), + '>' => (1,2), + _ => unreachable!(), + }; + + let mut seq = seq.to_vec(); + seq.insert(0, 'A'); + + let seq = seq.iter().map(|&x| find_coord(x)).collect::>(); + + let path = bfs(&seq, (0,0), (2,3), true); + + path +} + + + +fn find_path_on_keypad(seq: &[char]) -> Vec> { + let find_coord = |x: char| match x { + '1' => (2,0), + '2' => (2,1), + '3' => (2,2), + '4' => (1,0), + '5' => (1,1), + '6' => (1,2), + '7' => (0,0), + '8' => (0,1), + '9' => (0,2), + '0' => (3,1), + 'A' => (3,2), + _ => unreachable!(), + }; + + let mut seq = seq.to_vec(); + seq.insert(0, 'A'); + + let seq = seq.iter().map(|&x| find_coord(x)).collect::>(); + let path = bfs(&seq, (3,0), (4,3), false); + + path +} + + +fn bfs(nodes: &[(i64, i64)], ignore: (i64, i64), limits: (i64, i64), shortest_only: bool) -> Vec> { + let mut q = VecDeque::new(); + q.push_back((nodes[0], 1, vec![], Bitmap::new())); + + let mut best = vec![]; + let mut out = vec![]; + + while let Some(((x, y), mut dest_idx, mut path, mut visited)) = q.pop_front() { + if !best.is_empty() && ((shortest_only && path.len() >= best.len()) || (!shortest_only && path.len() > best.len())) { + continue; + } + if dest_idx == nodes.len() - 1 && x == nodes[dest_idx].0 && y == nodes[dest_idx].1 { + path.push('A'); + + if best.is_empty() || (!shortest_only && best.len() >= path.len()) || (shortest_only && best.len() > path.len()) { + best = path.clone(); + out.push(best.clone()); + } + continue; + } + + while x == nodes[dest_idx].0 && y == nodes[dest_idx].1 { + path.push('A'); + visited.clear(); + dest_idx += 1; + } + + if visited.get(x,y) { + continue; + } + visited.set(x,y); + + for (i,j) in [(0,1), (1,0), (0,-1), (-1,0)].iter() { + let x = i + x; + let y = j + y; + + if x < 0 || y < 0 || x >= limits.0 || y >= limits.1 || visited.get(x,y) { + continue; + } + + if x == ignore.0 && y == ignore.1 { + continue; + } + + let mut path = path.clone(); + match (i,j) { + (0,1) => path.push('>'), + (1,0) => path.push('v'), + (0,-1) => path.push('<'), + (-1,0) => path.push('^'), + _ => unreachable!() + } + q.push_back(((x,y), dest_idx, path, visited)); + } + } + + out +} + +#[derive(Clone,Copy)] +struct Bitmap { + inner: u64, + size: i64, +} + +impl Bitmap { + fn new() -> Self { + Self { inner: 0, size: 5} + } + + const fn get(&self, x: i64, y: i64) -> bool { + let offset = (x * self.size + y) as usize; + + (self.inner >> offset & 1) == 1 + } + + fn set(&mut self, x: i64, y: i64) { + let offset = (x * self.size + y) as usize; + + self.inner |= 1 << offset; + } + + fn clear(&mut self) { + self.inner = 0; + } +} + + + +fn main() { + for input in INPUTS.iter().skip(1) { + println!("answer = {}", run(input)); + } +} + +#[bench] +fn part1(b: &mut test::Bencher) { + b.iter(|| { + let v = run(INPUTS[1]); + test::black_box(v); + }); +} diff --git a/src/day21/2.rs b/src/day21/2.rs new file mode 100644 index 0000000..1073980 --- /dev/null +++ b/src/day21/2.rs @@ -0,0 +1,252 @@ + +#![feature(test)] +extern crate test; + +use std::sync::LazyLock; +use std::collections::VecDeque; +use fxhash::FxHashMap; + +const INPUTS: [&str; 2] = [ + "029A +980A +179A +456A +379A", + include_str!("./input.txt"), +]; + +trait KeyPad { + fn paths(&self) -> &FxHashMap<((i8, i8), (i8, i8)), Vec>>; + fn coord_from_u8(&self, c: u8) -> (i8, i8); +} + +struct NavKeyPad { + // grid: [[u8; 3]; 4], + paths: FxHashMap<((i8, i8), (i8, i8)), Vec>>, +} + +impl KeyPad for LazyLock { + fn coord_from_u8(&self, c: u8) -> (i8, i8) { + match c { + b'1' => (2,0), + b'2' => (2,1), + b'3' => (2,2), + b'4' => (1,0), + b'5' => (1,1), + b'6' => (1,2), + b'7' => (0,0), + b'8' => (0,1), + b'9' => (0,2), + b'0' => (3,1), + b'A' => (3,2), + _ => (0,0) // unreachable + } + } + + fn paths(&self) -> &FxHashMap<((i8, i8), (i8, i8)), Vec>> { + &self.paths + } +} + +const NAV_KEYPAD: LazyLock = LazyLock::new(|| { + let mut map = FxHashMap::default(); + for a in 0..4 { + for b in 0..3 { + for c in 0..4 { + for d in 0..3 { + map.insert(((a,b), (c,d)), compute_path((a,b), (c,d), (3,0), (4,3))); + } + } + } + } + + NavKeyPad { + //grid: [ + // ['7', '8', '9'], + // ['4', '5', '6'], + // ['1', '2', '3'], + // [' ', '0', 'A'], + //], + paths: map, + } +}); + +struct DirKeyPad{ + // grid: [[u8; 3]; 2], + paths: FxHashMap<((i8, i8), (i8, i8)), Vec>>, +} + +impl KeyPad for LazyLock { + fn coord_from_u8(&self, c: u8) -> (i8, i8) { + match c { + b'^' => (0,1), + b'A' => (0,2), + b'<' => (1,0), + b'v' => (1,1), + b'>' => (1,2), + _ => (0, 0) // unreachable + } + } + + fn paths(&self) -> &FxHashMap<((i8, i8), (i8, i8)), Vec>> { + &self.paths + } +} + +const DIR_KEYPAD: LazyLock = LazyLock::new(|| { + let mut map = FxHashMap::default(); + for a in 0..4 { + for b in 0..3 { + for c in 0..4 { + for d in 0..3 { + map.insert(((a,b), (c,d)), compute_path((a,b), (c,d), (0,0), (2,3))); + } + } + } + } + + DirKeyPad { + // grid: [ + // [' ', '^', 'A'], + // ['<', 'v', '>'], + // ], + paths: map, + } +}); + +fn compute_path((sx,sy): (i8, i8), (ex, ey): (i8, i8), ignore: (i8, i8), limits: (i8, i8)) -> Vec> { + let mut q = VecDeque::new(); + q.push_back((sx,sy, vec![], Bitmap::new())); + + let mut out = vec![]; + + while let Some((x, y, path, mut visited)) = q.pop_front() { + if path.len() > out.last().map(|x: &Vec| x.len()).unwrap_or(std::usize::MAX) { + continue; + } + if x == ex && y == ey { + out.push(path); + continue; + } + if visited.get(x,y) { + continue; + } + visited.set(x,y); + + for (i,j) in [(0,1), (1,0), (0,-1), (-1,0)].iter() { + let x = i + x; + let y = j + y; + + if x < 0 || y < 0 || x >= limits.0 || y >= limits.1 || visited.get(x, y) { + continue; + } + if x == ignore.0 && y == ignore.1 { + continue; + } + + let mut path = path.clone(); + match (i,j) { + (0,1) => path.push(b'>'), + (1,0) => path.push(b'v'), + (0,-1) => path.push(b'<'), + (-1,0) => path.push(b'^'), + _ => unreachable!() + } + q.push_back((x, y, path, visited)); + } + } + + out +} + +pub fn run(input: &str) -> i64 { + let input = input.as_bytes(); + let seq = input.split(|&x| x == b'\n').map(|x| x.to_vec()); + let mut cache = FxHashMap::default(); + let mut answer = 0; + + for seq in seq { + let seq_num = seq.iter().filter(|x| x.is_ascii_digit()).fold(0, |a,x| a * 10 + (*x - b'0') as i64); + + let shortest = compute(NAV_KEYPAD, seq, 1 + 25, &mut cache); + + answer += seq_num * shortest; + } + + answer +} + +fn compute( + keypad: impl KeyPad, + seq: Vec, + robots: i8, + cache: &mut FxHashMap<(i8, usize, Vec), i64>, +) -> i64 { + if robots == 0 { + return seq.len() as i64; + } + + if let Some(v) = cache.get(&(robots, seq.len(), seq.clone())) { + return *v; + } + + let mut current_pos = keypad.coord_from_u8(b'A'); + let mut smallest_length = 0; + + for &c in seq.iter() { + let c_pos = keypad.coord_from_u8(c); + let sequence = keypad.paths().get(&(current_pos, c_pos)).unwrap(); + let mut min = std::i64::MAX; + + for s in sequence { + let mut s = s.clone(); + s.push(b'A'); + min = std::cmp::min(min, compute(DIR_KEYPAD, s, robots - 1, cache)); + } + + smallest_length += min; + current_pos = c_pos; + } + + cache.insert((robots, seq.len(), seq), smallest_length); + smallest_length +} + + +#[derive(Clone,Copy)] +struct Bitmap { + inner: u64, + size: i8, +} + +impl Bitmap { + fn new() -> Self { + Self { inner: 0, size: 5} + } + + const fn get(&self, x: i8, y: i8) -> bool { + let offset = (x * self.size + y) as usize; + + (self.inner >> offset & 1) == 1 + } + + fn set(&mut self, x: i8, y: i8) { + let offset = (x * self.size + y) as usize; + + self.inner |= 1 << offset; + } +} + +fn main() { + for input in INPUTS.iter() { + println!("answer = {}", run(input)); + } +} + +#[bench] +fn part2(b: &mut test::Bencher) { + b.iter(|| { + let v = run(INPUTS[1]); + test::black_box(v); + }); +}