diff --git a/README.md b/README.md index 0e3a670..c0e0089 100644 --- a/README.md +++ b/README.md @@ -28,4 +28,4 @@ Development occurs in language-specific directories: |[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.rs](rs/src/day22.rs)| -|[Day23.hs](hs/src/Day23.hs)|[Day23.kt](kt/src/commonMain/kotlin/com/github/ephemient/aoc2022/Day23.kt)|[day23.py](py/aoc2022/day23.py)| +|[Day23.hs](hs/src/Day23.hs)|[Day23.kt](kt/src/commonMain/kotlin/com/github/ephemient/aoc2022/Day23.kt)|[day23.py](py/aoc2022/day23.py)|[day23.rs](rs/src/day23.rs)| diff --git a/rs/benches/criterion.rs b/rs/benches/criterion.rs index df7d3cc..ab3e26e 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, day22, day3, day4, day5, day6, day7, day8, day9, + day20, day21, day22, day23, day3, day4, day5, day6, day7, day8, day9, }; use criterion::{black_box, criterion_group, criterion_main, Criterion}; use gag::Gag; @@ -111,6 +111,10 @@ fn aoc2022_bench(c: &mut Criterion) { 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(); + let mut g = c.benchmark_group("day 23"); + g.bench_function("part 1", |b| b.iter(|| day23::part1(black_box(DAY23)))); + g.bench_function("part 2", |b| b.iter(|| day23::part2(black_box(DAY23)))); + g.finish(); } criterion_group!(aoc2022, aoc2022_bench); diff --git a/rs/src/day23.rs b/rs/src/day23.rs new file mode 100644 index 0000000..d08f4d7 --- /dev/null +++ b/rs/src/day23.rs @@ -0,0 +1,150 @@ +use self::Direction::*; +use std::array; +use std::cmp::{max, min}; +use std::collections::{BTreeMap, BTreeSet}; +use std::iter::Cycle; + +#[derive(Clone, Copy)] +enum Direction { + N, + S, + W, + E, +} + +fn directions() -> Cycle> { + [[N, S, W, E], [S, W, E, N], [W, E, N, S], [E, N, S, W]] + .into_iter() + .cycle() +} + +fn sides(direction: Direction, x: isize, y: isize) -> [(isize, isize); 3] { + match direction { + N => [x - 1, x, x + 1].map(|x| (x, y - 1)), + S => [x - 1, x, x + 1].map(|x| (x, y + 1)), + W => [y - 1, y, y + 1].map(|y| (x - 1, y)), + E => [y - 1, y, y + 1].map(|y| (x + 1, y)), + } +} + +fn r#move(direction: Direction, x: isize, y: isize) -> (isize, isize) { + match direction { + N => (x, y - 1), + S => (x, y + 1), + W => (x - 1, y), + E => (x + 1, y), + } +} + +fn neighbors(x: isize, y: isize) -> [(isize, isize); 8] { + [ + (x - 1, y - 1), + (x - 1, y), + (x - 1, y + 1), + (x, y - 1), + (x, y + 1), + (x + 1, y - 1), + (x + 1, y), + (x + 1, y + 1), + ] +} + +fn parse<'a, I, S>(lines: I) -> BTreeSet<(isize, isize)> +where + I: IntoIterator, + S: AsRef + 'a, +{ + lines + .into_iter() + .enumerate() + .flat_map(|(y, line)| { + line.as_ref() + .chars() + .enumerate() + .filter(|(_, c)| c == &'#') + .map(move |(x, _)| (x as isize, y as isize)) + }) + .collect() +} + +fn step(state: &BTreeSet<(isize, isize)>, directions: &[Direction]) -> BTreeSet<(isize, isize)> { + let mut proposals = BTreeMap::new(); + for &(x, y) in state { + if !neighbors(x, y).iter().any(|point| state.contains(point)) { + continue; + } + if let Some(&direction) = directions.iter().find(|&&direction| { + !sides(direction, x, y) + .iter() + .any(|point| state.contains(point)) + }) { + proposals + .entry(r#move(direction, x, y)) + .and_modify(|e: &mut Vec<_>| e.push((x, y))) + .or_insert_with(|| vec![(x, y)]); + } + } + proposals.retain(|_, old| old.len() == 1); + let mut state = state.clone(); + for (new, old) in proposals { + state.remove(&old[0]); + state.insert(new); + } + state +} + +pub fn part1<'a, I, S>(lines: I) -> usize +where + I: IntoIterator, + S: AsRef + 'a, +{ + let state = directions() + .take(10) + .fold(parse(lines), |state, directions| step(&state, &directions)); + let (min_x, max_x, min_y, max_y) = state.iter().fold( + (isize::MAX, isize::MIN, isize::MAX, isize::MIN), + |(min_x, max_x, min_y, max_y), &(x, y)| { + (min(min_x, x), max(max_x, x), min(min_y, y), max(max_y, y)) + }, + ); + (max_x - min_x + 1) as usize * (max_y - min_y + 1) as usize - state.len() +} + +pub fn part2<'a, I, S>(lines: I) -> usize +where + I: IntoIterator, + S: AsRef + 'a, +{ + directions() + .scan(parse(lines), |state, directions| { + let next = step(state, &directions); + if state != &next { + *state = next; + Some(()) + } else { + None + } + }) + .count() + + 1 +} + +#[cfg(test)] +mod tests { + use super::*; + use pretty_assertions::assert_eq; + + static EXAMPLE: &[&str] = &[ + "....#..", "..###.#", "#...#.#", ".#...##", "#.###..", "##.#.##", ".#..#..", + ]; + + #[test] + fn part1_examples() { + assert_eq!(110, part1(EXAMPLE)); + } + + #[test] + fn part2_examples() { + assert_eq!(20, part2(EXAMPLE)); + } +} diff --git a/rs/src/lib.rs b/rs/src/lib.rs index 7867640..7fc0c01 100644 --- a/rs/src/lib.rs +++ b/rs/src/lib.rs @@ -14,6 +14,7 @@ pub mod day2; pub mod day20; pub mod day21; pub mod day22; +pub mod day23; pub mod day3; pub mod day4; pub mod day5; diff --git a/rs/src/main.rs b/rs/src/main.rs index 83bd04e..5464053 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, day22, day3, day4, day5, day6, day7, day8, day9, + day20, day21, day22, day23, day3, day4, day5, day6, day7, day8, day9, }; use std::collections::HashSet; use std::env; @@ -177,5 +177,12 @@ fn main() -> io::Result<()> { println!(); } + if args.is_empty() || args.contains("23") { + println!("Day 23"); + println!("{:?}", day23::part1(DAY23)); + println!("{:?}", day23::part2(DAY23)); + println!(); + } + Ok(()) }