day 23 finished

This commit is contained in:
Rüdiger Ludwig 2023-08-12 16:54:10 +02:00
parent 80d724a6f6
commit 62cb729aee
6 changed files with 435 additions and 3 deletions

7
data/day23/example01.txt Normal file
View file

@ -0,0 +1,7 @@
....#..
..###.#
#...#.#
.#...##
#.###..
##.#.##
.#..#..

6
data/day23/example02.txt Normal file
View file

@ -0,0 +1,6 @@
.....
..##.
..#..
.....
..##.
.....

70
data/day23/input.txt Normal file
View file

@ -0,0 +1,70 @@
###.###..#.....#..#.##..##.#...###.#####..#.##.#####.###..######.#.#.#
....##.#.#.#......#.###..##..###...#.#...####.##...#.#....#.##.##....#
#..#.#.#.##.........#####....#..##.......#.#.#.##....#.#....##..##..##
#.##.##....#.##.#.###.#...#..#.##.####.#.#.#.#.###.###.####....#.#.#.#
.#.#.#####.##...##.##..#..#.#..####.#..#.#.#######..#.##.#.###.#.....#
..#..#....###.#....###.##.#..#..#.#..#.#.#...##...#....##....#..##...#
###..........#..#.###....##.##...#####.###.#.##...#.##.....#.##.#.####
#.###.####...#..#.##.....#.####.##..##...#..##.....#...###..##.######.
.....#.####..##.#...#..##.###..#.##.####..#...#.###..##..#.#.###...#.#
.#.#.#..##.##..#.#.##.#.##.#.##..##.##.#..##.#.##..#####.#.###.#.##..#
###.#.###.##.##....#.#.#...##...#.#..#.#..#.#..##.#....#####...#...###
.#.#.#.#.#..#....###.#.##.#.#.####..#.#.#...#.#.#.###....#....#...##..
#.#....#........#....######...#....#..#.###...#.....###..###...##..#..
.###.##....#.#.#.###..###....#.#.#..###.##..#..#..#####.#.###.#..##..#
..###.##..####.....#...##.#....#...###..#.###.#...##....####........#.
#####.##..#..##..###....#.##....#.#...##.########...#.#.#..##..##..###
.##..#...##.....###.#...#..###.######.#.#...#...##..#.....#..#.##..#.#
.#####..#.######.#..#..#.#.#.##....##..#.......#....#.##.#.#..##...#.#
.#.##..#..###..#.######........##.##.##..#....#.###.######.##.#.#..##.
##.##.#..####.#....###.#..##..#...#.#####.#..#...#....##...#.###..#.##
#....##..#...#.#.###.#.#...##.#.#..###...####..##.#..##.#.#####.....##
.##.#..#.##.##.#.##.....#######..#...#.##.####...#.....#.##..#...##...
#.#.#.#####...#####..#.##....#.#.####.#..#.##..#.#.#...#..##.#...##...
#.#.#.#..#.#.#..#..###.#.....#.#.#..#.#####.#.#..#...#.###.###...#.#..
.####..####.#.#..#...##.##.#....#.#.....#.##.###..#...###.###.#....##.
....##.#.##.###.#..##.#..#.......##...#.###...#.......#..####.#.#.####
....#.#......####..#.#..#..#..#.##..#..#..###..#.#.##.#.##..###..#..#.
.##.#.....##.#..#..#..####..#.#.#...###.##.##.##...#..##..##.##...##..
#..####..#...#.#....##...##..#.....##.#######..#.####...##..######.#..
..#.##.##....##.#######..##...#..#..###.....##.#.#..#.#...##..###.####
#####......##..#.#..#......#..####.####...#####..######...##.###..##..
###..##.#.##....##..######.#.#.#......#..##.#.##.#.##...#.##...###..##
.....#.#.#.##..#.#####.##.#.#.....#.#..#.###.##..#.###.##..##.###....#
.###.####..####.###....####.#.##..##..#.....##.###.##.######.#..#.#...
.#.#######.#.#.###....#####..#.#.#..#.#.#.#.#..##..#..#...##..##..#.#.
.##..####..#.#...###.#...#....####...###.#..#.#.....#.#.##.#..##.#.#..
.#.#.#.#####..##.####..#..##......#..#.###.#..#....#...#....#...#.####
..##.##.###.#..#..#.#..#.###......##.#..#..#.#.####.#.#.#.####.##.....
##.#..#..##.#######..#..#....##..#...########.##.#.#...##...##.##.....
.#...#####.#..##.##.###.####.....###.#......########.#..##.##.#....###
...####.###....##..#..#....#..##.####..##..#..###.......#.#.###.##...#
.#.#......#####...##....#.#..#######.######.#.#.##.#..###..#....##..#.
..###.#.#####..##.#.####....#.###..#..#.##.#####.##..##.##..##..#.##.#
.####.#.######.###.###..#......#...#...#..#..##.###.##.#.####.#.#.##.#
#####.###.##.#########..###..#.#.##.#..#..##...#.###.#.#......###..##.
###...###.##.#.###.#.##.####.#######..##.#....#####.###.#..#.#...###..
.######...#...#..###......#.#.##........#..##.#.#.##.#....###.....##.#
#.#.....##.##.##..##..##.#.##.###.##.###.##.#.##.###....##..#.#.##....
#.####.##...##....#.#.....#.#.###..#..####.....#.##..##...##.###.#..##
#####.##.#...#....#..###.##..##...#....#....##.#.#....#####....####.##
####...##...#.###.##.##.#####...#.....##..#####...########..####......
#.#..##.##.#.######.###.#.##.......#......####..##.#.#.#..##.###.#..#.
#..#..########..##..#.######..#.#...#..#.#####.###.####......##..###.#
#..#.#...#..#....#..##.##.#..###.####.###########.#..#.###.#.##.###..#
##.#..#..#.....#.##.#..######..#..#...#.#...###.#.#.#.#.#.########...#
..###.#.##......###..#...##......#####.#.#....####.....##.#..#.#..#.#.
..######....#..##.#.#..#.##.#..##.##.#....##....###.#.##.##..##....###
.#####....##....###.#.#######.....##.###.#..#.#.#..###...#.##..#..#.#.
#..#####...#..##..#..#..#....#.##....#..##.......##..#....#.#..#.#.###
#.#..#####...#...#...##.#....#..##.#.##....#####.....#.##.....#..#.###
##.#.#....#...#.####....###....#...#.##...##.#....#.#.#.##......##.###
...##.#.#..#.###..#.#...##.#...##.#...##...#..###.#.###...###..#..##.#
.#####...#.#..###.#.##.##.#.##..##.#.#.....##....#.#..#.#.#..#####.###
#.####.####..###......##.....#.#.....##.#..##.##.#....###.....####..##
#..#####...#....#...###..##...###..####..####..#...#####.#####..#.###.
####.###.###....####....##..#..####...#....###.##.#.##...##.......####
.#.###....####..#..#.###.##.##.####..##.#..###..#..#.#.#..#..#.##..##.
....##......####.####.######...###.....###.#.#..##.#.##.......#...#...
##.###.##...#.....#.##.#.......####.##.###..###.#.#..#.##..#.####....#
....#..##.#...##.##.#.#.#..#.#..#..#.####....#####..#.....#####.#.....

View file

@ -2,7 +2,7 @@ use super::direction::Direction;
use super::math::gcd; use super::math::gcd;
use num_traits::{CheckedAdd, CheckedSub, Float, Num, NumCast, Signed, Zero}; use num_traits::{CheckedAdd, CheckedSub, Float, Num, NumCast, Signed, Zero};
use std::fmt; 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)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct Pos2<T>([T; 2]); pub struct Pos2<T>([T; 2]);
@ -157,7 +157,7 @@ where
} }
} }
impl<'a, T, P: Into<Pos2<T>>> Add<P> for Pos2<T> impl<T, P: Into<Pos2<T>>> Add<P> for Pos2<T>
where where
T: Num + Copy, T: Num + Copy,
{ {
@ -168,6 +168,17 @@ where
} }
} }
impl<T, P: Into<Pos2<T>>> AddAssign<P> for Pos2<T>
where
T: AddAssign<T> + 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<T, P: Into<Pos2<T>>> Sub<P> for Pos2<T> impl<T, P: Into<Pos2<T>>> Sub<P> for Pos2<T>
where where
T: Num + Copy, T: Num + Copy,

336
src/days/day23/mod.rs Normal file
View file

@ -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<ResultType> {
let field: Field = lines.parse()?;
let elves = field.rounds(10);
Ok(ResultType::Integer(count_empty_tiles(&elves)))
}
fn part2(&self, lines: &str) -> anyhow::Result<ResultType> {
let field: Field = lines.parse()?;
let round = field.forever() as i64;
Ok(ResultType::Integer(round))
}
}
fn count_empty_tiles(elves: &[Pos2<i32>]) -> 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<Direction>,
}
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<Self::Item> {
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<i32>,
inc: Pos2<i32>,
count: usize,
}
impl PosIterator {
fn new(pos: Pos2<i32>, 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<i32>;
fn next(&mut self) -> Option<Self::Item> {
if self.count > 2 {
return None;
}
self.pos += self.inc;
self.count += 1;
Some(self.pos)
}
}
enum MoveCheck {
NoNeighbors,
NoFreeSpace,
MoveTo(Pos2<i32>, Direction),
}
struct Field {
direction: DirectionDispenser,
elves: HashMap<Pos2<i32>, bool>,
}
impl FromStr for Field {
type Err = FieldError;
fn from_str(lines: &str) -> Result<Self, Self::Err> {
let elves: HashMap<Pos2<i32>, 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<i32>) -> 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<i32>, 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<i32>, (Pos2<i32>, 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<i32>, 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<Pos2<i32>> {
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(())
}
}

View file

@ -20,6 +20,7 @@ mod day19;
mod day20; mod day20;
mod day21; mod day21;
mod day22; mod day22;
mod day23;
mod template; mod template;
pub use template::DayTrait; pub use template::DayTrait;
@ -29,7 +30,7 @@ pub mod day_provider {
use super::*; use super::*;
use thiserror::Error; use thiserror::Error;
const MAX_DAY: usize = 22; const MAX_DAY: usize = 23;
pub fn get_day(day_num: usize) -> Result<Box<dyn DayTrait>, ProviderError> { pub fn get_day(day_num: usize) -> Result<Box<dyn DayTrait>, ProviderError> {
match day_num { match day_num {
@ -55,6 +56,7 @@ pub mod day_provider {
20 => Ok(Box::new(day20::Day)), 20 => Ok(Box::new(day20::Day)),
21 => Ok(Box::new(day21::Day)), 21 => Ok(Box::new(day21::Day)),
22 => Ok(Box::new(day22::Day)), 22 => Ok(Box::new(day22::Day)),
23 => Ok(Box::new(day23::Day)),
_ => Err(ProviderError::InvalidNumber(day_num)), _ => Err(ProviderError::InvalidNumber(day_num)),
} }
} }