From 386b5fb0aee2905cb287b649e7d1d8cd4658d059 Mon Sep 17 00:00:00 2001 From: Daniel Lin Date: Thu, 22 Dec 2022 17:14:51 -0500 Subject: [PATCH] Day 22: Monkey Map --- README.md | 2 +- rs/benches/criterion.rs | 6 +- rs/src/day22.rs | 277 ++++++++++++++++++++++++++++++++++++++++ rs/src/lib.rs | 1 + rs/src/main.rs | 9 +- 5 files changed, 292 insertions(+), 3 deletions(-) create mode 100644 rs/src/day22.rs diff --git a/README.md b/README.md index 8e274d2..ccb837f 100644 --- a/README.md +++ b/README.md @@ -27,4 +27,4 @@ Development occurs in language-specific directories: |[Day19.hs](hs/src/Day19.hs)|[Day19.kt](kt/src/commonMain/kotlin/com/github/ephemient/aoc2022/Day19.kt)|[day19.py](py/aoc2022/day19.py)|[day19.rs](rs/src/day19.rs)| |[Day20.hs](hs/src/Day20.hs)|[Day20.kt](kt/src/commonMain/kotlin/com/github/ephemient/aoc2022/Day20.kt)|[day20.py](py/aoc2022/day20.py)|[day20.rs](rs/src/day20.rs)| |[Day21.hs](hs/src/Day21.hs)|[Day21.kt](kt/src/commonMain/kotlin/com/github/ephemient/aoc2022/Day21.kt)|[day21.py](py/aoc2022/day21.py)|[day21.rs](rs/src/day21.rs)| -|[Day22.hs](hs/src/Day22.hs)|[Day22.kt](kt/src/commonMain/kotlin/com/github/ephemient/aoc2022/Day22.kt)|[day22.py](py/aoc2022/day22.py)| +|[Day22.hs](hs/src/Day22.hs)|[Day22.kt](kt/src/commonMain/kotlin/com/github/ephemient/aoc2022/Day22.kt)|[day22.py](py/aoc2022/day22.py)|[day22.rs](rs/src/day22.rs)| diff --git a/rs/benches/criterion.rs b/rs/benches/criterion.rs index 3e9921e..df7d3cc 100644 --- a/rs/benches/criterion.rs +++ b/rs/benches/criterion.rs @@ -3,7 +3,7 @@ extern crate build_const; use aoc2022::{ day1, day10, day11, day12, day13, day13_fast, day14, day15, day16, day17, day18, day19, day2, - day20, day21, day3, day4, day5, day6, day7, day8, day9, + day20, day21, day22, day3, day4, day5, day6, day7, day8, day9, }; use criterion::{black_box, criterion_group, criterion_main, Criterion}; use gag::Gag; @@ -107,6 +107,10 @@ fn aoc2022_bench(c: &mut Criterion) { g.bench_function("part 1", |b| b.iter(|| day21::part1(black_box(DAY21)))); g.bench_function("part 2", |b| b.iter(|| day21::part2(black_box(DAY21)))); g.finish(); + let mut g = c.benchmark_group("day 22"); + g.bench_function("part 1", |b| b.iter(|| day22::part1(black_box(DAY22)))); + g.bench_function("part 2", |b| b.iter(|| day22::part2(black_box(DAY22)))); + g.finish(); } criterion_group!(aoc2022, aoc2022_bench); diff --git a/rs/src/day22.rs b/rs/src/day22.rs new file mode 100644 index 0000000..42e91b4 --- /dev/null +++ b/rs/src/day22.rs @@ -0,0 +1,277 @@ +use std::cmp::min; +use std::collections::BTreeMap; + +#[derive(Clone, Copy, Debug)] +enum Move { + Steps(usize), + Left, + Right, +} + +fn parse<'a, I, S>(lines: I) -> Option<(Vec<&'a [u8]>, Vec)> +where + I: IntoIterator, + S: AsRef + 'a, +{ + let mut iter = lines.into_iter(); + let mut board = vec![]; + for line in iter.by_ref() { + let line = line.as_ref(); + if line.is_empty() { + break; + } + board.push(line.as_bytes()); + } + let mut moves = vec![]; + let mut line = iter.next()?.as_ref(); + while !line.is_empty() { + if let Some(tail) = line.strip_prefix('L') { + moves.push(Move::Left); + line = tail; + } else if let Some(tail) = line.strip_prefix('R') { + moves.push(Move::Right); + line = tail; + } else { + let (digits, tail) = line.split_at( + line.find(|c: char| !c.is_ascii_digit()) + .unwrap_or(line.len()), + ); + moves.push(Move::Steps(digits.parse().ok()?)); + line = tail; + } + } + Some((board, moves)) +} + +struct Perimeter<'a> { + board: &'a [&'a [u8]], + initial: (usize, usize, u8), + next: Option<(usize, usize, u8)>, +} +fn perimeter<'a>(board: &'a [&'a [u8]]) -> Option> { + let initial = ( + board + .first()? + .iter() + .enumerate() + .find(|(_, &b)| b == b'.')? + .0, + 0, + 0, + ); + Some(Perimeter { + board, + initial, + next: Some(initial), + }) +} +impl<'a> Iterator for Perimeter<'a> { + type Item = (usize, usize, u8); + fn next(&mut self) -> Option { + let current @ (x, y, d) = self.next?; + let forward = match d { + 0 => Some((x + 1, y)), + 1 => Some((x, y + 1)), + 2 => x.checked_sub(1).map(|x| (x, y)), + 3 => y.checked_sub(1).map(|y| (x, y)), + _ => unreachable!(), + }; + self.next = if forward + .and_then(|(x, y)| Some(*self.board.get(y)?.get(x)?)) + .unwrap_or(b' ') + == b' ' + { + Some((x, y, (d + 1) % 4)) + } else { + let left = match d { + 0 => y.checked_sub(1).map(|y| (x + 1, y)), + 1 => Some((x + 1, y + 1)), + 2 => x.checked_sub(1).map(|x| (x, y + 1)), + 3 => x.checked_sub(1).zip(y.checked_sub(1)), + _ => unreachable!(), + }; + if left + .and_then(|(x, y)| Some(*self.board.get(y)?.get(x)?)) + .unwrap_or(b' ') + == b' ' + { + forward.map(|(x, y)| (x, y, d)) + } else { + left.map(|(x, y)| (x, y, (d + 3) % 4)) + } + } + .filter(|state| state != &self.initial); + Some(current) + } +} + +fn run( + board: &[&[u8]], + moves: &[Move], + warps: &BTreeMap<(usize, usize, u8), (usize, usize, u8)>, +) -> Option { + let (x, y, d) = moves.iter().fold( + ( + board + .first()? + .iter() + .enumerate() + .find(|(_, &b)| b == b'.')? + .0, + 0, + 0u8, + ), + |(x, y, d), &r#move| match r#move { + Move::Steps(n) => { + let mut pos = (x, y, d); + for _ in 0..n { + let Some(next) = warps + .get(&pos) + .copied() + .or_else(|| { + match pos.2 { + 0 => Some((pos.0 + 1, pos.1, pos.2)), + 1 => Some((pos.0, pos.1 + 1, pos.2)), + 2 => pos.0.checked_sub(1).map(|x| (x, pos.1, pos.2)), + 3 => pos.1.checked_sub(1).map(|y| (pos.0, y, pos.2)), + _ => unreachable!(), + } + }) + .filter(|&(x, y, _)| { + board.get(y).and_then(|line| line.get(x)) == Some(&b'.') + }) + else { return pos }; + pos = next; + } + pos + } + Move::Left => (x, y, (d + 3) % 4), + Move::Right => (x, y, (d + 1) % 4), + }, + ); + Some(1000 * (y + 1) + 4 * (x + 1) + d as usize) +} + +pub fn part1<'a, I, S>(lines: I) -> Option +where + I: IntoIterator, + S: AsRef + 'a, +{ + let (board, moves) = parse(lines)?; + let warps = perimeter(&board)? + .map(|(x, y, d)| match d { + 0 => board + .iter() + .enumerate() + .rev() + .find(|(_, &line)| line.get(x).filter(|&&b| b != b' ').is_some()) + .map(|(y2, _)| ((x, y, 3), (x, y2, 3))), + 1 => board.get(y).and_then(|&line| { + line.iter() + .enumerate() + .find(|(_, &b)| b != b' ') + .map(|(x2, _)| ((x, y, 0), (x2, y, 0))) + }), + 2 => board + .iter() + .enumerate() + .find(|(_, &line)| line.get(x).filter(|&&b| b != b' ').is_some()) + .map(|(y2, _)| ((x, y, 1), (x, y2, 1))), + 3 => board.get(y).and_then(|&line| { + line.iter() + .enumerate() + .rev() + .find(|(_, &b)| b != b' ') + .map(|(x2, _)| ((x, y, 2), (x2, y, 2))) + }), + _ => unreachable!(), + }) + .collect::>()?; + run(&board, &moves, &warps) +} + +pub fn part2<'a, I, S>(lines: I) -> Option +where + I: IntoIterator, + S: AsRef + 'a, +{ + let (board, moves) = parse(lines)?; + let perimeter = perimeter(&board)?.collect::>(); + let side_length = perimeter + .iter() + .map(|&(_, _, d)| Some(d)) + .chain(None) + .fold((Some(0), 0, usize::MAX), |(prev, count, size), cur| { + if prev == cur { + (cur, count + 1, size) + } else { + (cur, 1, min(size, count)) + } + }) + .2; + let mut unpaired_edges = perimeter + .iter() + .enumerate() + .step_by(side_length) + .map(|(i, &(_, _, d))| (i, d)) + .collect::>(); + let mut paired_edges = vec![]; + while !unpaired_edges.is_empty() { + let mut i = 0; + while i + 1 < unpaired_edges.len() { + if unpaired_edges[i].1 == (unpaired_edges[i + 1].1 + 1) % 4 { + paired_edges.push((unpaired_edges[i].0, unpaired_edges[i + 1].0)); + unpaired_edges.drain(i..i + 2); + for (_, d) in &mut unpaired_edges[i..] { + *d = (*d + 3) % 4; + } + } else { + i += 1; + } + } + } + let mut warps = BTreeMap::new(); + for (i, j) in paired_edges { + for (&(x1, y1, d1), &(x2, y2, d2)) in perimeter[i..i + side_length] + .iter() + .zip(perimeter[j..j + side_length].iter().rev()) + { + warps.insert((x1, y1, (d1 + 3) % 4), (x2, y2, (d2 + 1) % 4)); + warps.insert((x2, y2, (d2 + 3) % 4), (x1, y1, (d1 + 1) % 4)); + } + } + run(&board, &moves, &warps) +} + +#[cfg(test)] +mod tests { + use super::*; + use pretty_assertions::assert_eq; + + static EXAMPLE: &[&str] = &[ + " ...#", + " .#..", + " #...", + " ....", + "...#.......#", + "........#...", + "..#....#....", + "..........#.", + " ...#....", + " .....#..", + " .#......", + " ......#.", + "", + "10R5L5R10L4R5L5", + ]; + + #[test] + fn part1_examples() { + assert_eq!(Some(6032), part1(EXAMPLE)); + } + + #[test] + fn part2_examples() { + assert_eq!(Some(5031), part2(EXAMPLE)); + } +} diff --git a/rs/src/lib.rs b/rs/src/lib.rs index d267a88..7867640 100644 --- a/rs/src/lib.rs +++ b/rs/src/lib.rs @@ -13,6 +13,7 @@ pub mod day19; pub mod day2; pub mod day20; pub mod day21; +pub mod day22; pub mod day3; pub mod day4; pub mod day5; diff --git a/rs/src/main.rs b/rs/src/main.rs index 7a15b8a..83bd04e 100644 --- a/rs/src/main.rs +++ b/rs/src/main.rs @@ -3,7 +3,7 @@ extern crate build_const; use aoc2022::{ day1, day10, day11, day12, day13, day13_fast, day14, day15, day16, day17, day18, day19, day2, - day20, day21, day3, day4, day5, day6, day7, day8, day9, + day20, day21, day22, day3, day4, day5, day6, day7, day8, day9, }; use std::collections::HashSet; use std::env; @@ -170,5 +170,12 @@ fn main() -> io::Result<()> { println!(); } + if args.is_empty() || args.contains("22") { + println!("Day 22"); + println!("{:?}", day22::part1(DAY22).expect("error")); + println!("{:?}", day22::part2(DAY22).expect("error")); + println!(); + } + Ok(()) }