diff --git a/data/examples/20.txt b/data/examples/20.txt new file mode 100644 index 000000000..c097dae --- /dev/null +++ b/data/examples/20.txt @@ -0,0 +1,15 @@ +############### +#...#...#.....# +#.#.#.#.#.###.# +#S#...#.#.#...# +#######.#.#.### +#######.#.#...# +#######.#.###.# +###..E#...#...# +###.#######.### +#...###...#...# +#.#####.#.###.# +#.#...#.#.#...# +#.#.#.#.#.#.### +#...#...#...### +############### diff --git a/data/examples/21.txt b/data/examples/21.txt new file mode 100644 index 000000000..4cf0c29 --- /dev/null +++ b/data/examples/21.txt @@ -0,0 +1,5 @@ +029A +980A +179A +456A +379A diff --git a/src/bin/20.rs b/src/bin/20.rs new file mode 100644 index 000000000..621a0df --- /dev/null +++ b/src/bin/20.rs @@ -0,0 +1,148 @@ +use std::collections::VecDeque; + +use aoc::grid_vec::{Direction, Grid, Point}; +use hashbrown::HashMap; + +fn parse_input(input: &str) -> (Grid, Point, Point) { + let mut grid: Grid = input.parse().unwrap(); + let start = grid.find(b'S').unwrap(); + let end = grid.find(b'E').unwrap(); + + grid[start] = b'.'; + grid[end] = b'.'; + + (grid, start, end) +} +fn bfs( + grid: &Grid, + start: Point, + limit: Option, + cheat: bool, + visited: &mut HashMap, + queue: &mut VecDeque<(Point, usize)>, +) { + visited.clear(); + queue.clear(); + + queue.push_back((start, 0)); + + while let Some((p, l)) = queue.pop_front() { + if visited.contains_key(&p) { + continue; + } + visited.insert(p, l); + + for d in Direction::CROSS { + let n = p + d; + + let nv = grid.get(&n); + let uwnv = *nv.unwrap_or(&b'#'); + let valid_neigh = (cheat && nv.is_some()) || uwnv == b'.'; + let valid_length = l < limit.unwrap_or(usize::MAX); + let unvisited = !visited.contains_key(&n); + + if valid_neigh && valid_length && unvisited { + queue.push_back((n, l + 1)); + } + } + } +} + +fn get_cheat_neighbours_and_benchmark( + grid: &Grid, + start: Point, + cheat_limit: usize, +) -> HashMap> { + let mut map = HashMap::>::new(); + + let mut reuse_queue = VecDeque::new(); + let mut reuse_visited = HashMap::new(); + + let mut valid_points = HashMap::new(); + + bfs( + grid, + start, + None, + false, + &mut valid_points, + &mut reuse_queue, + ); + + for (&s, _) in valid_points.iter() { + bfs( + grid, + s, + Some(cheat_limit), + true, + &mut reuse_visited, + &mut reuse_queue, + ); + for (&point, &length) in reuse_visited.iter().filter(|(&p, _)| grid[p] == b'.') { + map.entry(s).or_default().push((point, length)); + } + } + + map +} + +fn cheated_bfs( + grid: &Grid, + start: Point, + end: Point, + cheat_limit: usize, + increase_limit: usize, +) -> usize { + let neighbours = get_cheat_neighbours_and_benchmark(grid, start, cheat_limit); + + let mut reuse_queue = VecDeque::new(); + + let mut from_start = HashMap::new(); + let mut from_end = HashMap::new(); + + bfs(grid, start, None, false, &mut from_start, &mut reuse_queue); + bfs(grid, end, None, false, &mut from_end, &mut reuse_queue); + + let benchmark = from_start[&end]; + + let mut count = 0; + + for node in from_start.keys() { + for (neigh, cheat_dist) in neighbours[node].iter() { + let start_dist = from_start[node]; + let end_dist = from_end[neigh]; + + let dist = start_dist + cheat_dist + end_dist; + if dist < benchmark && dist.abs_diff(benchmark) >= increase_limit { + count += 1; + } + } + } + + count +} + +pub fn part_one(input: &str) -> Option { + let (grid, start, end) = parse_input(input); + cheated_bfs(&grid, start, end, 2, 100).into() +} + +pub fn part_two(input: &str) -> Option { + let (grid, start, end) = parse_input(input); + cheated_bfs(&grid, start, end, 20, 100).into() +} + +aoc::solution!(20); + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_part_one() { + assert_eq!(part_one(&aoc::template::read_file("examples", 20)), None); + } + #[test] + fn test_part_two() { + assert_eq!(part_two(&aoc::template::read_file("examples", 20)), None); + } +} diff --git a/src/bin/21.rs b/src/bin/21.rs new file mode 100644 index 000000000..915cd24 --- /dev/null +++ b/src/bin/21.rs @@ -0,0 +1,262 @@ +use std::iter::once; + +use hashbrown::{HashMap, HashSet}; +use itertools::Itertools; + +const NUMPAD: [(&str, &str); 110] = [ + // 0 - everywhere + ("01", "^<"), + ("02", "^"), + ("03", "^>"), + ("04", "^^<"), + ("05", "^^"), + ("06", "^^>"), + ("07", "^^^<"), + ("08", "^^^"), + ("09", "^^^>"), + ("0A", ">"), + // 1 - everywhere + ("10", ">v"), + ("12", ">"), + ("13", ">>"), + ("14", "^"), + ("15", "^>"), + ("16", "^>>"), + ("17", "^^"), + ("18", "^^>"), + ("19", "^^>>"), + ("1A", ">>v"), + // 2 - everywhere + ("20", "v"), + ("21", "<"), + ("23", ">"), + ("24", "^<"), + ("25", "^"), + ("26", "^>"), + ("27", "^^<"), + ("28", "^^"), + ("29", "^^>"), + ("2A", ">v"), + // 3 - everywhere + ("30", "v<"), + ("31", "<<"), + ("32", "<"), + ("34", "^<<"), + ("35", "^<"), + ("36", "^"), + ("37", "^^<<"), + ("38", "^^<"), + ("39", "^^"), + ("3A", "v"), + // 4 - everywhere + ("40", ">vv"), + ("41", "v"), + ("42", "v>"), + ("43", "v>>"), + ("45", ">"), + ("46", ">>"), + ("47", "^"), + ("48", "^>"), + ("49", "^>>"), + ("4A", ">>vv"), + // 5 - everywhere + ("50", "vv"), + ("51", "v<"), + ("52", "v"), + ("53", "v>"), + ("54", "<"), + ("56", ">"), + ("57", "^<"), + ("58", "^"), + ("59", "^>"), + ("5A", ">vv"), + // 6 - everywhere + ("60", "vv<"), + ("61", "v<<"), + ("62", "v<"), + ("63", "v"), + ("64", "<<"), + ("65", "<"), + ("67", "^<<"), + ("68", "^<"), + ("69", "^"), + ("6A", "vv"), + // 7 - everywhere + ("70", ">vvv"), + ("71", "vv"), + ("72", "vv>"), + ("73", "vv>>"), + ("74", "v"), + ("75", "v>"), + ("76", "v>>"), + ("78", ">"), + ("79", ">>"), + ("7A", ">>vvv"), + // 8 - everywhere + ("80", "vvv"), + ("81", "vv<"), + ("82", "vv"), + ("83", "vv>"), + ("84", "v<"), + ("85", "v"), + ("86", "v>"), + ("87", "<"), + ("89", ">"), + ("8A", ">vvv"), + // 9 - everywhere + ("90", ""), + ("<>", ">>"), + ("<^", ">^"), + (">^"), + // v - everywhere + ("v<", "<"), + ("v>", ">"), + ("v^", "^"), + ("vA", ">^"), + // > - everywhere + ("><", "<<"), + (">v", "<"), + (">^", "^<"), + (">A", "^"), + // ^ - everywhere + ("^<", "v<"), + ("^v", "v"), + ("^>", "v>"), + ("^A", ">"), + // A - everywhere + ("A<", "v<<"), + ("Av", "v<"), + ("A>", "v"), + ("A^", "<"), +]; + +fn build_move_options(s: &str) -> Vec { + let mut set = HashSet::new(); + + for c in s.chars() { + set.insert(c); + } + + let mut v = vec![s.to_string()]; + + if set.len() == 2 { + v.push(s.chars().rev().collect()) + } + + v +} + +pub fn part_one(input: &str) -> Option { + let mut keypad = HashMap::new(); + + for (k, v) in KEYPAD.iter() { + let key = k.as_bytes(); + keypad.insert((key[0], key[1]), build_move_options(v)); + } + + let mut numpad = HashMap::new(); + + for (k, v) in NUMPAD.iter() { + let key = k.as_bytes(); + numpad.insert((key[0], key[1]), build_move_options(v)); + } + + let mut s1s = Vec::new(); + let mut s2s = Vec::new(); + let mut s3s = Vec::new(); + let mut s4s = Vec::new(); + + let mut c = 0; + + for line in input.lines() { + s1s.clear(); + s2s.clear(); + s3s.clear(); + s4s.clear(); + + s1s.push(once(&b'A').chain(line.as_bytes()).copied().collect_vec()); + + for mapper in [&numpad, &keypad, &keypad] { + for s1 in s1s.iter() { + s3s.push(vec![b'A']); + for i in 1..s1.len() { + let k2 = s1[i - 1]; + let k1 = s1[i]; + + if mapper.contains_key(&(k1, k2)) { + for path in mapper[&(k1, k2)].iter() { + for s3 in s3s.iter() { + let mut cs3 = s3.clone(); + cs3.extend(path.as_bytes()); + s4s.push(cs3); + } + } + } + + s3s.clear(); + s3s.append(&mut s4s); + + for s3 in s3s.iter_mut() { + s3.push(b'A'); + } + } + s2s.append(&mut s3s); + } + + s1s.clear(); + s1s.append(&mut s2s); + } + + let min_len = s1s.iter().map(|v| v.len()).min().unwrap(); + let n: usize = line.strip_suffix("A").unwrap().parse().unwrap(); + + println!("{min_len}, {n}"); + c += n * min_len; + } + + c.into() +} + +pub fn part_two(input: &str) -> Option { + None +} + +aoc::solution!(21); + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_part_one() { + assert_eq!(part_one(&aoc::template::read_file("examples", 21)), None); + } + #[test] + fn test_part_two() { + assert_eq!(part_two(&aoc::template::read_file("examples", 21)), None); + } +}