day 23 finished
This commit is contained in:
parent
80d724a6f6
commit
62cb729aee
6 changed files with 435 additions and 3 deletions
7
data/day23/example01.txt
Normal file
7
data/day23/example01.txt
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
....#..
|
||||
..###.#
|
||||
#...#.#
|
||||
.#...##
|
||||
#.###..
|
||||
##.#.##
|
||||
.#..#..
|
||||
6
data/day23/example02.txt
Normal file
6
data/day23/example02.txt
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
.....
|
||||
..##.
|
||||
..#..
|
||||
.....
|
||||
..##.
|
||||
.....
|
||||
70
data/day23/input.txt
Normal file
70
data/day23/input.txt
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
###.###..#.....#..#.##..##.#...###.#####..#.##.#####.###..######.#.#.#
|
||||
....##.#.#.#......#.###..##..###...#.#...####.##...#.#....#.##.##....#
|
||||
#..#.#.#.##.........#####....#..##.......#.#.#.##....#.#....##..##..##
|
||||
#.##.##....#.##.#.###.#...#..#.##.####.#.#.#.#.###.###.####....#.#.#.#
|
||||
.#.#.#####.##...##.##..#..#.#..####.#..#.#.#######..#.##.#.###.#.....#
|
||||
..#..#....###.#....###.##.#..#..#.#..#.#.#...##...#....##....#..##...#
|
||||
###..........#..#.###....##.##...#####.###.#.##...#.##.....#.##.#.####
|
||||
#.###.####...#..#.##.....#.####.##..##...#..##.....#...###..##.######.
|
||||
.....#.####..##.#...#..##.###..#.##.####..#...#.###..##..#.#.###...#.#
|
||||
.#.#.#..##.##..#.#.##.#.##.#.##..##.##.#..##.#.##..#####.#.###.#.##..#
|
||||
###.#.###.##.##....#.#.#...##...#.#..#.#..#.#..##.#....#####...#...###
|
||||
.#.#.#.#.#..#....###.#.##.#.#.####..#.#.#...#.#.#.###....#....#...##..
|
||||
#.#....#........#....######...#....#..#.###...#.....###..###...##..#..
|
||||
.###.##....#.#.#.###..###....#.#.#..###.##..#..#..#####.#.###.#..##..#
|
||||
..###.##..####.....#...##.#....#...###..#.###.#...##....####........#.
|
||||
#####.##..#..##..###....#.##....#.#...##.########...#.#.#..##..##..###
|
||||
.##..#...##.....###.#...#..###.######.#.#...#...##..#.....#..#.##..#.#
|
||||
.#####..#.######.#..#..#.#.#.##....##..#.......#....#.##.#.#..##...#.#
|
||||
.#.##..#..###..#.######........##.##.##..#....#.###.######.##.#.#..##.
|
||||
##.##.#..####.#....###.#..##..#...#.#####.#..#...#....##...#.###..#.##
|
||||
#....##..#...#.#.###.#.#...##.#.#..###...####..##.#..##.#.#####.....##
|
||||
.##.#..#.##.##.#.##.....#######..#...#.##.####...#.....#.##..#...##...
|
||||
#.#.#.#####...#####..#.##....#.#.####.#..#.##..#.#.#...#..##.#...##...
|
||||
#.#.#.#..#.#.#..#..###.#.....#.#.#..#.#####.#.#..#...#.###.###...#.#..
|
||||
.####..####.#.#..#...##.##.#....#.#.....#.##.###..#...###.###.#....##.
|
||||
....##.#.##.###.#..##.#..#.......##...#.###...#.......#..####.#.#.####
|
||||
....#.#......####..#.#..#..#..#.##..#..#..###..#.#.##.#.##..###..#..#.
|
||||
.##.#.....##.#..#..#..####..#.#.#...###.##.##.##...#..##..##.##...##..
|
||||
#..####..#...#.#....##...##..#.....##.#######..#.####...##..######.#..
|
||||
..#.##.##....##.#######..##...#..#..###.....##.#.#..#.#...##..###.####
|
||||
#####......##..#.#..#......#..####.####...#####..######...##.###..##..
|
||||
###..##.#.##....##..######.#.#.#......#..##.#.##.#.##...#.##...###..##
|
||||
.....#.#.#.##..#.#####.##.#.#.....#.#..#.###.##..#.###.##..##.###....#
|
||||
.###.####..####.###....####.#.##..##..#.....##.###.##.######.#..#.#...
|
||||
.#.#######.#.#.###....#####..#.#.#..#.#.#.#.#..##..#..#...##..##..#.#.
|
||||
.##..####..#.#...###.#...#....####...###.#..#.#.....#.#.##.#..##.#.#..
|
||||
.#.#.#.#####..##.####..#..##......#..#.###.#..#....#...#....#...#.####
|
||||
..##.##.###.#..#..#.#..#.###......##.#..#..#.#.####.#.#.#.####.##.....
|
||||
##.#..#..##.#######..#..#....##..#...########.##.#.#...##...##.##.....
|
||||
.#...#####.#..##.##.###.####.....###.#......########.#..##.##.#....###
|
||||
...####.###....##..#..#....#..##.####..##..#..###.......#.#.###.##...#
|
||||
.#.#......#####...##....#.#..#######.######.#.#.##.#..###..#....##..#.
|
||||
..###.#.#####..##.#.####....#.###..#..#.##.#####.##..##.##..##..#.##.#
|
||||
.####.#.######.###.###..#......#...#...#..#..##.###.##.#.####.#.#.##.#
|
||||
#####.###.##.#########..###..#.#.##.#..#..##...#.###.#.#......###..##.
|
||||
###...###.##.#.###.#.##.####.#######..##.#....#####.###.#..#.#...###..
|
||||
.######...#...#..###......#.#.##........#..##.#.#.##.#....###.....##.#
|
||||
#.#.....##.##.##..##..##.#.##.###.##.###.##.#.##.###....##..#.#.##....
|
||||
#.####.##...##....#.#.....#.#.###..#..####.....#.##..##...##.###.#..##
|
||||
#####.##.#...#....#..###.##..##...#....#....##.#.#....#####....####.##
|
||||
####...##...#.###.##.##.#####...#.....##..#####...########..####......
|
||||
#.#..##.##.#.######.###.#.##.......#......####..##.#.#.#..##.###.#..#.
|
||||
#..#..########..##..#.######..#.#...#..#.#####.###.####......##..###.#
|
||||
#..#.#...#..#....#..##.##.#..###.####.###########.#..#.###.#.##.###..#
|
||||
##.#..#..#.....#.##.#..######..#..#...#.#...###.#.#.#.#.#.########...#
|
||||
..###.#.##......###..#...##......#####.#.#....####.....##.#..#.#..#.#.
|
||||
..######....#..##.#.#..#.##.#..##.##.#....##....###.#.##.##..##....###
|
||||
.#####....##....###.#.#######.....##.###.#..#.#.#..###...#.##..#..#.#.
|
||||
#..#####...#..##..#..#..#....#.##....#..##.......##..#....#.#..#.#.###
|
||||
#.#..#####...#...#...##.#....#..##.#.##....#####.....#.##.....#..#.###
|
||||
##.#.#....#...#.####....###....#...#.##...##.#....#.#.#.##......##.###
|
||||
...##.#.#..#.###..#.#...##.#...##.#...##...#..###.#.###...###..#..##.#
|
||||
.#####...#.#..###.#.##.##.#.##..##.#.#.....##....#.#..#.#.#..#####.###
|
||||
#.####.####..###......##.....#.#.....##.#..##.##.#....###.....####..##
|
||||
#..#####...#....#...###..##...###..####..####..#...#####.#####..#.###.
|
||||
####.###.###....####....##..#..####...#....###.##.#.##...##.......####
|
||||
.#.###....####..#..#.###.##.##.####..##.#..###..#..#.#.#..#..#.##..##.
|
||||
....##......####.####.######...###.....###.#.#..##.#.##.......#...#...
|
||||
##.###.##...#.....#.##.#.......####.##.###..###.#.#..#.##..#.####....#
|
||||
....#..##.#...##.##.#.#.#..#.#..#..#.####....#####..#.....#####.#.....
|
||||
|
|
@ -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>([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
|
||||
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>
|
||||
where
|
||||
T: Num + Copy,
|
||||
|
|
|
|||
336
src/days/day23/mod.rs
Normal file
336
src/days/day23/mod.rs
Normal 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(())
|
||||
}
|
||||
}
|
||||
|
|
@ -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<Box<dyn DayTrait>, 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)),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue