diff --git a/data/examples/15.txt b/data/examples/15.txt new file mode 100644 index 000000000..84cf1fb --- /dev/null +++ b/data/examples/15.txt @@ -0,0 +1,21 @@ +########## +#..O..O.O# +#......O.# +#.OO..O.O# +#..O@..O.# +#O#..O...# +#O..O..O.# +#.OO.O.OO# +#....O...# +########## + +^v>^vv^v>v<>v^v<<><>>v^v^>^<<<><^ +vvv<<^>^v^^><<>>><>^<<><^vv^^<>vvv<>><^^v>^>vv<>v<<<^<^^>>>^<>vv>v^v^<>><>>>><^^>vv>v<^^^>>v^v^<^^>v^^>v^<^v>v<>>v^v^v^^<^^vv< +<>^^^^>>>v^<>vvv^>^^^vv^^>v<^^^^v<>^>vvvv><>>v^<<^^^^^ +^><^><>>><>^^<<^^v>>><^^>v>>>^v><>^v><<<>vvvv>^<><<>^>< +^>><>^v<><^vvv<^^<><^v<<<><<<^^<^>>^<<<^>>^v^>>^v>vv>^<<^v<>><<><<>v<^vv<<<>^^v^>^^>>><<^v>>v^v><^^>>^<>vv^ +<><^^>^^^<>^vv<<^><<><<><<<^^<<<^<<>><<><^^^>^^<>^>v<> +^^>vv<^v^v^<>^^^>>>^^vvv^>vvv<>>>^<^>>>>>^<<^v>^vvv<>^<>< +v^^>>><<^^<>>^v^v^<<>^<^v^v><^<<<><<^vv>>v>v^<<^ diff --git a/src/bin/15.rs b/src/bin/15.rs new file mode 100644 index 000000000..5198a5c --- /dev/null +++ b/src/bin/15.rs @@ -0,0 +1,209 @@ +use hashbrown::HashSet; +use itertools::Itertools; + +struct ParsedInput { + map: Vec>, + dirs: Vec<(isize, isize)>, + start: (usize, usize), +} + +fn dir_mapper(c: char) -> (isize, isize) { + match c { + '>' => (0, 1), + '<' => (0, -1), + 'v' => (1, 0), + '^' => (-1, 0), + _ => unreachable!("found dir {c}"), + } +} + +fn parse_input(input: &str, extend: bool) -> ParsedInput { + let (umap, uins) = input.split_once("\n\n").unwrap(); + + let mut map = Vec::new(); + + for line in umap.lines() { + let mut row: Vec = line.chars().collect(); + if extend { + row = line + .chars() + .flat_map(|x| { + let y = match x { + '#' => "##", + 'O' => "[]", + '.' => "..", + '@' => "@.", + _ => unreachable!("found {x}"), + }; + y.chars() + }) + .collect() + }; + + map.push(row); + } + + let dirs = uins + .lines() + .flat_map(|x| x.chars().map(dir_mapper)) + .collect_vec(); + + let (si, sj, _) = map + .iter() + .enumerate() + .flat_map(|(i, x)| x.iter().copied().enumerate().map(move |(j, y)| (i, j, y))) + .find(|x| x.2 == '@') + .unwrap(); + + ParsedInput { + map, + dirs, + start: (si, sj), + } +} + +fn boxes_gps(map: &[Vec]) -> usize { + map.iter() + .enumerate() + .flat_map(|(i, x)| { + x.iter() + .enumerate() + .filter(|(_, y)| ['O', '['].contains(y)) + .map(move |(j, _)| 100 * i + j) + }) + .sum() +} + +pub fn part_one(input: &str) -> Option { + let ParsedInput { + mut map, + dirs, + start: (mut si, mut sj), + } = parse_input(input, false); + + for (di, dj) in dirs.iter().copied() { + let mut box_count = 0; + + loop { + if map[si][sj] == 'O' { + box_count += 1; + map[si][sj] = '.'; + } + + map[si][sj] = '.'; + + let ni = si.wrapping_add(di as usize); + let nj = sj.wrapping_add(dj as usize); + + if map[ni][nj] == '#' { + break; + } + + (si, sj) = (ni, nj); + + if map[si][sj] == '.' { + break; + } + } + + let (di, dj) = (-di, -dj); + + for _ in 0..box_count { + map[si][sj] = 'O'; + si = si.wrapping_add(di as usize); + sj = sj.wrapping_add(dj as usize); + } + + map[si][sj] = '@'; + } + + boxes_gps(&map).into() +} + +fn can_move_and_mark( + acc: &mut HashSet<(usize, usize, usize)>, + mark: usize, + map: &[Vec], + loc: (usize, usize), + dir: (isize, isize), +) -> bool { + let (li, mut lj) = loc; + + match map[li][lj] { + '.' => return true, + '#' => return false, + ']' => lj -= 1, + _ => (), + } + + let mut cmm = |l, dir| can_move_and_mark(acc, mark + 1, map, l, dir); + + let can_move = match dir { + (0, -1) => cmm((li, lj - 1), dir), + (0, 1) => cmm((li, lj + 2), dir), + (-1, 0) => cmm((li - 1, lj), dir) && cmm((li - 1, lj + 1), dir), + (1, 0) => cmm((li + 1, lj), dir) && cmm((li + 1, lj + 1), dir), + x => unreachable!("found dir {x:?}"), + }; + + if can_move { + acc.insert((li, lj, mark)); + } + + can_move +} + +pub fn part_two(input: &str) -> Option { + let ParsedInput { + mut map, + dirs, + start: (mut si, mut sj), + } = parse_input(input, true); + + let mut marker = HashSet::new(); + let mut sorter = Vec::new(); + + for (di, dj) in dirs.iter().copied() { + marker.clear(); + sorter.clear(); + + let ni = si.wrapping_add(di as usize); + let nj = sj.wrapping_add(dj as usize); + + if can_move_and_mark(&mut marker, 0, &map, (ni, nj), (di, dj)) { + sorter.extend(marker.drain()); + sorter.sort_by_key(|x| -(x.2 as isize)); + + for (mi, mj, _) in sorter.iter().copied() { + let nmi = mi.wrapping_add(di as usize); + let nmj = mj.wrapping_add(dj as usize); + + map[mi][mj] = '.'; + map[mi][mj + 1] = '.'; + map[nmi][nmj] = '['; + map[nmi][nmj + 1] = ']'; + } + + map[si][sj] = '.'; + (si, sj) = (ni, nj); + map[si][sj] = '@'; + } + } + + boxes_gps(&map).into() +} + +aoc::solution!(15); + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_part_one() { + assert_eq!(part_one(&aoc::template::read_file("examples", 15)), Some(10092)); + } + #[test] + fn test_part_two() { + assert_eq!(part_two(&aoc::template::read_file("examples", 15)), Some(9021)); + } +}