From 62cb729aee1b6dac94be20ae68b688b256788fdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Ludwig?= Date: Sat, 12 Aug 2023 16:54:10 +0200 Subject: [PATCH] day 23 finished --- data/day23/example01.txt | 7 + data/day23/example02.txt | 6 + data/day23/input.txt | 70 ++++++++ src/common/pos2.rs | 15 +- src/days/day23/mod.rs | 336 +++++++++++++++++++++++++++++++++++++++ src/days/mod.rs | 4 +- 6 files changed, 435 insertions(+), 3 deletions(-) create mode 100644 data/day23/example01.txt create mode 100644 data/day23/example02.txt create mode 100644 data/day23/input.txt create mode 100644 src/days/day23/mod.rs diff --git a/data/day23/example01.txt b/data/day23/example01.txt new file mode 100644 index 0000000..7ac3ba9 --- /dev/null +++ b/data/day23/example01.txt @@ -0,0 +1,7 @@ +....#.. +..###.# +#...#.# +.#...## +#.###.. +##.#.## +.#..#.. \ No newline at end of file diff --git a/data/day23/example02.txt b/data/day23/example02.txt new file mode 100644 index 0000000..e2a080c --- /dev/null +++ b/data/day23/example02.txt @@ -0,0 +1,6 @@ +..... +..##. +..#.. +..... +..##. +..... \ No newline at end of file diff --git a/data/day23/input.txt b/data/day23/input.txt new file mode 100644 index 0000000..fe9b65e --- /dev/null +++ b/data/day23/input.txt @@ -0,0 +1,70 @@ +###.###..#.....#..#.##..##.#...###.#####..#.##.#####.###..######.#.#.# +....##.#.#.#......#.###..##..###...#.#...####.##...#.#....#.##.##....# +#..#.#.#.##.........#####....#..##.......#.#.#.##....#.#....##..##..## +#.##.##....#.##.#.###.#...#..#.##.####.#.#.#.#.###.###.####....#.#.#.# +.#.#.#####.##...##.##..#..#.#..####.#..#.#.#######..#.##.#.###.#.....# +..#..#....###.#....###.##.#..#..#.#..#.#.#...##...#....##....#..##...# +###..........#..#.###....##.##...#####.###.#.##...#.##.....#.##.#.#### +#.###.####...#..#.##.....#.####.##..##...#..##.....#...###..##.######. +.....#.####..##.#...#..##.###..#.##.####..#...#.###..##..#.#.###...#.# +.#.#.#..##.##..#.#.##.#.##.#.##..##.##.#..##.#.##..#####.#.###.#.##..# +###.#.###.##.##....#.#.#...##...#.#..#.#..#.#..##.#....#####...#...### +.#.#.#.#.#..#....###.#.##.#.#.####..#.#.#...#.#.#.###....#....#...##.. +#.#....#........#....######...#....#..#.###...#.....###..###...##..#.. +.###.##....#.#.#.###..###....#.#.#..###.##..#..#..#####.#.###.#..##..# +..###.##..####.....#...##.#....#...###..#.###.#...##....####........#. +#####.##..#..##..###....#.##....#.#...##.########...#.#.#..##..##..### +.##..#...##.....###.#...#..###.######.#.#...#...##..#.....#..#.##..#.# +.#####..#.######.#..#..#.#.#.##....##..#.......#....#.##.#.#..##...#.# +.#.##..#..###..#.######........##.##.##..#....#.###.######.##.#.#..##. +##.##.#..####.#....###.#..##..#...#.#####.#..#...#....##...#.###..#.## +#....##..#...#.#.###.#.#...##.#.#..###...####..##.#..##.#.#####.....## +.##.#..#.##.##.#.##.....#######..#...#.##.####...#.....#.##..#...##... +#.#.#.#####...#####..#.##....#.#.####.#..#.##..#.#.#...#..##.#...##... +#.#.#.#..#.#.#..#..###.#.....#.#.#..#.#####.#.#..#...#.###.###...#.#.. +.####..####.#.#..#...##.##.#....#.#.....#.##.###..#...###.###.#....##. +....##.#.##.###.#..##.#..#.......##...#.###...#.......#..####.#.#.#### +....#.#......####..#.#..#..#..#.##..#..#..###..#.#.##.#.##..###..#..#. +.##.#.....##.#..#..#..####..#.#.#...###.##.##.##...#..##..##.##...##.. +#..####..#...#.#....##...##..#.....##.#######..#.####...##..######.#.. +..#.##.##....##.#######..##...#..#..###.....##.#.#..#.#...##..###.#### +#####......##..#.#..#......#..####.####...#####..######...##.###..##.. +###..##.#.##....##..######.#.#.#......#..##.#.##.#.##...#.##...###..## +.....#.#.#.##..#.#####.##.#.#.....#.#..#.###.##..#.###.##..##.###....# +.###.####..####.###....####.#.##..##..#.....##.###.##.######.#..#.#... +.#.#######.#.#.###....#####..#.#.#..#.#.#.#.#..##..#..#...##..##..#.#. +.##..####..#.#...###.#...#....####...###.#..#.#.....#.#.##.#..##.#.#.. +.#.#.#.#####..##.####..#..##......#..#.###.#..#....#...#....#...#.#### +..##.##.###.#..#..#.#..#.###......##.#..#..#.#.####.#.#.#.####.##..... +##.#..#..##.#######..#..#....##..#...########.##.#.#...##...##.##..... +.#...#####.#..##.##.###.####.....###.#......########.#..##.##.#....### +...####.###....##..#..#....#..##.####..##..#..###.......#.#.###.##...# +.#.#......#####...##....#.#..#######.######.#.#.##.#..###..#....##..#. +..###.#.#####..##.#.####....#.###..#..#.##.#####.##..##.##..##..#.##.# +.####.#.######.###.###..#......#...#...#..#..##.###.##.#.####.#.#.##.# +#####.###.##.#########..###..#.#.##.#..#..##...#.###.#.#......###..##. +###...###.##.#.###.#.##.####.#######..##.#....#####.###.#..#.#...###.. +.######...#...#..###......#.#.##........#..##.#.#.##.#....###.....##.# +#.#.....##.##.##..##..##.#.##.###.##.###.##.#.##.###....##..#.#.##.... +#.####.##...##....#.#.....#.#.###..#..####.....#.##..##...##.###.#..## +#####.##.#...#....#..###.##..##...#....#....##.#.#....#####....####.## +####...##...#.###.##.##.#####...#.....##..#####...########..####...... +#.#..##.##.#.######.###.#.##.......#......####..##.#.#.#..##.###.#..#. +#..#..########..##..#.######..#.#...#..#.#####.###.####......##..###.# +#..#.#...#..#....#..##.##.#..###.####.###########.#..#.###.#.##.###..# +##.#..#..#.....#.##.#..######..#..#...#.#...###.#.#.#.#.#.########...# +..###.#.##......###..#...##......#####.#.#....####.....##.#..#.#..#.#. +..######....#..##.#.#..#.##.#..##.##.#....##....###.#.##.##..##....### +.#####....##....###.#.#######.....##.###.#..#.#.#..###...#.##..#..#.#. +#..#####...#..##..#..#..#....#.##....#..##.......##..#....#.#..#.#.### +#.#..#####...#...#...##.#....#..##.#.##....#####.....#.##.....#..#.### +##.#.#....#...#.####....###....#...#.##...##.#....#.#.#.##......##.### +...##.#.#..#.###..#.#...##.#...##.#...##...#..###.#.###...###..#..##.# +.#####...#.#..###.#.##.##.#.##..##.#.#.....##....#.#..#.#.#..#####.### +#.####.####..###......##.....#.#.....##.#..##.##.#....###.....####..## +#..#####...#....#...###..##...###..####..####..#...#####.#####..#.###. +####.###.###....####....##..#..####...#....###.##.#.##...##.......#### +.#.###....####..#..#.###.##.##.####..##.#..###..#..#.#.#..#..#.##..##. +....##......####.####.######...###.....###.#.#..##.#.##.......#...#... +##.###.##...#.....#.##.#.......####.##.###..###.#.#..#.##..#.####....# +....#..##.#...##.##.#.#.#..#.#..#..#.####....#####..#.....#####.#..... \ No newline at end of file diff --git a/src/common/pos2.rs b/src/common/pos2.rs index a4cb0a7..1ba2520 100644 --- a/src/common/pos2.rs +++ b/src/common/pos2.rs @@ -2,7 +2,7 @@ use super::direction::Direction; use super::math::gcd; use num_traits::{CheckedAdd, CheckedSub, Float, Num, NumCast, Signed, Zero}; use std::fmt; -use std::ops::{Add, Div, Mul, Neg, Sub}; +use std::ops::{Add, AddAssign, Div, Mul, Neg, Sub}; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] pub struct Pos2([T; 2]); @@ -157,7 +157,7 @@ where } } -impl<'a, T, P: Into>> Add

for Pos2 +impl>> Add

for Pos2 where T: Num + Copy, { @@ -168,6 +168,17 @@ where } } +impl>> AddAssign

for Pos2 +where + T: AddAssign + Copy, +{ + fn add_assign(&mut self, rhs: P) { + let rhs = rhs.into(); + self.0[0] += rhs.0[0]; + self.0[1] += rhs.0[1]; + } +} + impl>> Sub

for Pos2 where T: Num + Copy, diff --git a/src/days/day23/mod.rs b/src/days/day23/mod.rs new file mode 100644 index 0000000..dca05af --- /dev/null +++ b/src/days/day23/mod.rs @@ -0,0 +1,336 @@ +use super::template::{DayTrait, ResultType}; +use crate::common::{area::Area, direction::Direction, pos2::Pos2}; +use itertools::Itertools; +use std::{collections::HashMap, str::FromStr}; +use thiserror::Error; + +const DAY_NUMBER: usize = 23; + +pub struct Day; + +impl DayTrait for Day { + fn get_day_number(&self) -> usize { + DAY_NUMBER + } + + fn part1(&self, lines: &str) -> anyhow::Result { + let field: Field = lines.parse()?; + let elves = field.rounds(10); + + Ok(ResultType::Integer(count_empty_tiles(&elves))) + } + + fn part2(&self, lines: &str) -> anyhow::Result { + let field: Field = lines.parse()?; + let round = field.forever() as i64; + Ok(ResultType::Integer(round)) + } +} + +fn count_empty_tiles(elves: &[Pos2]) -> i64 { + let Some(area) = Area::from_iterator(elves.iter()) else { + return 0; + }; + + area.area() as i64 - elves.len() as i64 +} + +#[derive(Debug, Error)] +enum FieldError { + #[error("Unknown Char: {0}")] + UnknownChar(char), +} + +fn next_direction(current: Direction) -> Direction { + match current { + Direction::North => Direction::South, + Direction::South => Direction::West, + Direction::West => Direction::East, + Direction::East => Direction::North, + } +} + +struct DirectionDispenser(Direction); + +impl DirectionDispenser { + fn new() -> DirectionDispenser { + Self(Direction::North) + } + + fn progress(&mut self) { + self.0 = next_direction(self.0); + } + + fn iter(&self) -> DispenserIterator { + DispenserIterator::new(self.0) + } +} + +struct DispenserIterator { + start: Direction, + current: Option, +} + +impl DispenserIterator { + pub fn new(start: Direction) -> Self { + DispenserIterator { + start, + current: Some(start), + } + } +} + +impl Iterator for DispenserIterator { + type Item = Direction; + + fn next(&mut self) -> Option { + let result = self.current; + if let Some(current) = self.current { + let next = next_direction(current); + if next == self.start { + self.current = None; + } else { + self.current = Some(next); + } + } + result + } +} + +struct PosIterator { + pos: Pos2, + inc: Pos2, + count: usize, +} + +impl PosIterator { + fn new(pos: Pos2, direction: Direction) -> Self { + let inc = direction.turn_right().into(); + let pos = pos + direction - inc * 2; + Self { pos, inc, count: 0 } + } +} + +impl Iterator for PosIterator { + type Item = Pos2; + + fn next(&mut self) -> Option { + if self.count > 2 { + return None; + } + + self.pos += self.inc; + self.count += 1; + Some(self.pos) + } +} + +enum MoveCheck { + NoNeighbors, + NoFreeSpace, + MoveTo(Pos2, Direction), +} + +struct Field { + direction: DirectionDispenser, + elves: HashMap, bool>, +} + +impl FromStr for Field { + type Err = FieldError; + + fn from_str(lines: &str) -> Result { + let elves: HashMap, bool> = lines + .split('\n') + .enumerate() + .map(|(y, row)| { + row.chars().enumerate().filter_map(move |(x, c)| match c { + '.' => None, + '#' => Some(Ok((Pos2::new(x as i32, y as i32), true))), + _ => Some(Err(FieldError::UnknownChar(c))), + }) + }) + .flatten() + .try_collect()?; + + Ok(Field { + direction: DirectionDispenser::new(), + elves, + }) + } +} + +impl Field { + fn has_neighbors(&self, pos: Pos2) -> bool { + for x in -1..=1 { + for y in -1..=1 { + if x != 0 || y != 0 { + let check_pos = pos + (x, y); + if self.elves.contains_key(&check_pos) { + return true; + } + } + } + } + false + } + + fn check(&self, pos: Pos2, directions: DispenserIterator) -> MoveCheck { + if !self.has_neighbors(pos) { + return MoveCheck::NoNeighbors; + } + + for direction in directions { + if self.is_empty(pos, direction) { + return MoveCheck::MoveTo(pos + direction, direction); + } + } + MoveCheck::NoFreeSpace + } + + fn one_round(&mut self) -> bool { + let mut proposals: HashMap, (Pos2, Direction)> = HashMap::new(); + let mut deactivate = HashMap::new(); + for elf in self + .elves + .iter() + .filter_map(|(elf, active)| active.then_some(*elf)) + { + match self.check(elf, self.direction.iter()) { + MoveCheck::MoveTo(proposal, direction) => { + if proposals.contains_key(&proposal) { + proposals.remove(&proposal); + } else { + proposals.insert(proposal, (elf, direction)); + } + } + MoveCheck::NoNeighbors => { + deactivate.insert(elf, false); + } + MoveCheck::NoFreeSpace => {} + } + } + if proposals.is_empty() { + return false; + } + + self.elves.extend(deactivate.into_iter()); + + for (to, (from, direction)) in proposals { + self.elves.remove(&from); + self.elves.insert(to, true); + for pos in PosIterator::new(to, direction) { + if let Some(active) = self.elves.get_mut(&pos) { + *active = true; + } + } + } + self.direction.progress(); + true + } + + fn is_empty(&self, pos: Pos2, direction: Direction) -> bool { + PosIterator::new(pos, direction).all(|pos| !self.elves.contains_key(&pos)) + } + + fn forever(mut self) -> usize { + for round in 0.. { + if !self.one_round() { + return round + 1; + } + } + unreachable!() + } + + fn rounds(mut self, arg: usize) -> Vec> { + for _ in 0..arg { + self.one_round(); + } + self.elves.into_keys().collect_vec() + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::{common::file::read_string, hashmap, hashset}; + use anyhow::Result; + use std::collections::HashSet; + + #[test] + fn test_part1() -> Result<()> { + let day = Day {}; + let lines = read_string(day.get_day_number(), "example01.txt")?; + let expected = ResultType::Integer(110); + let result = day.part1(&lines)?; + assert_eq!(result, expected); + + Ok(()) + } + + #[test] + fn test_part2() -> Result<()> { + let day = Day {}; + let lines = read_string(day.get_day_number(), "example01.txt")?; + let expected = ResultType::Integer(20); + let result = day.part2(&lines)?; + assert_eq!(result, expected); + + Ok(()) + } + + #[test] + fn parse() -> Result<()> { + let day = Day {}; + let lines = read_string(day.get_day_number(), "example02.txt")?; + let expected = hashmap!( + Pos2::new(2, 1) => true, + Pos2::new(3, 1) => true, + Pos2::new(2, 2) => true, + Pos2::new(2, 4) => true, + Pos2::new(3, 4) => true + ); + let field: Field = lines.parse()?; + assert_eq!(field.elves, expected); + assert_eq!(field.direction.0, Direction::North); + + Ok(()) + } + + #[test] + fn round1() -> Result<()> { + let day = Day {}; + let lines = read_string(day.get_day_number(), "example02.txt")?; + let expected = hashmap!( + Pos2::new(2, 0) => true, + Pos2::new(3, 0) => true, + Pos2::new(2, 2) => true, + Pos2::new(3, 3) => true, + Pos2::new(2, 4) => true + ); + let mut field: Field = lines.parse()?; + field.one_round(); + assert_eq!(field.elves, expected); + assert_eq!(field.direction.0, Direction::South); + + Ok(()) + } + + #[test] + fn round2() -> Result<()> { + let day = Day {}; + let lines = read_string(day.get_day_number(), "example02.txt")?; + let expected = hashset!( + Pos2::new(2, 1), + Pos2::new(3, 1), + Pos2::new(1, 2), + Pos2::new(4, 3), + Pos2::new(2, 5) + ); + let field: Field = lines.parse()?; + let elves = HashSet::from_iter(field.rounds(2).into_iter()); + assert_eq!(elves, expected); + + Ok(()) + } +} diff --git a/src/days/mod.rs b/src/days/mod.rs index ac8442e..00daa3f 100644 --- a/src/days/mod.rs +++ b/src/days/mod.rs @@ -20,6 +20,7 @@ mod day19; mod day20; mod day21; mod day22; +mod day23; mod template; pub use template::DayTrait; @@ -29,7 +30,7 @@ pub mod day_provider { use super::*; use thiserror::Error; - const MAX_DAY: usize = 22; + const MAX_DAY: usize = 23; pub fn get_day(day_num: usize) -> Result, ProviderError> { match day_num { @@ -55,6 +56,7 @@ pub mod day_provider { 20 => Ok(Box::new(day20::Day)), 21 => Ok(Box::new(day21::Day)), 22 => Ok(Box::new(day22::Day)), + 23 => Ok(Box::new(day23::Day)), _ => Err(ProviderError::InvalidNumber(day_num)), } }