day 24 finsihed
This commit is contained in:
parent
62cb729aee
commit
abc1530bf3
9 changed files with 641 additions and 9 deletions
6
data/day24/example01.txt
Normal file
6
data/day24/example01.txt
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
#.######
|
||||
#>>.<^<#
|
||||
#.<..<<#
|
||||
#>v.><>#
|
||||
#<^v^^>#
|
||||
######.#
|
||||
7
data/day24/example02.txt
Normal file
7
data/day24/example02.txt
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
#.#####
|
||||
#.....#
|
||||
#>....#
|
||||
#.....#
|
||||
#...v.#
|
||||
#.....#
|
||||
#####.#
|
||||
37
data/day24/input.txt
Normal file
37
data/day24/input.txt
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
#.####################################################################################################
|
||||
#>^v^v>><.>>><>^v<>^v>v<>^<<>>v>>.>><v>^<>^.<>v.>v^>vvvv<<<<>>^v^^^>><><.^^^.<<<>><^v^vv^v<.v.v>>.vv<#
|
||||
#>^><>^v^<>.<v><.<.v<v>vv<^<vvvv.^^...<<<^^vv<<v<>>>>v^<>>^^^><.>>>>v<<>^.>.^^<v<.><<.<><.>>>^^^>>.<>#
|
||||
#<.vvv^^v>^<<^^^>.^<..v.v<<^>vv<>>>v^>vv<><v<<vv<v^>v<^^^^v>.<.vv><v>v<v<^v^>>^<^^<>^>v^<^.^^v><>.><>#
|
||||
#>..<^^<>>v>><>vv^<v.^v.>^<.v^<v><<^<><v^v<.vv^>>vvv.vvv<>v>><<.>^><v^<<<v<<>><v><^v.v.<>^>v><v<^<<.>#
|
||||
#>^^<v>v><^<^>^<vvv^.>^^><^>><^^.vv>v<>v<>^>^v><v>>.>>^<v><^v^v^>v^<.<v^^^<>vvv^^<.<v<>>>.^>><<.vvv.>#
|
||||
#><v>v^>^<>v^>>^^<>.><^v<^vvv>vv<.><>v>v<..<^>>v^v<<<<v>v^.<<<^^^^.^v^<<>^vvv>^><.^v<^<>vv><<^<>^<^<<#
|
||||
#.^vvv<vv>^^v<>v<<v^^>.<<<v><<vv<v>>^<vv<v>><<^>^vv<<>^<<>^v.>>.<>>^<^<v<^^^<.vv^vv><^><v^^v>v^v>^vv.#
|
||||
#<v.>^<^><^..>^.>>v>>vv<^<.<vv<<v^v<.vvv>>.^v^^<^v<^<.^vv>v>^><v<v^>^v>>>>^^^v>^^>>>v<v^vv<.^><vv<>^<#
|
||||
#><>v^>^v<.<.>.<^.<>>^^>v^<v^<v>vvv<<^>vv<^^>^<^>v<.^.v<<.v.v^v>v><>..^^<.v^^<.<<<<<><>v><v<^^v<^<vv<#
|
||||
#><<>v><>^^^.<.<<>^v>v^>^>>>>v>.^>.v>.^>^.v<^<>v.v>v^v^^>^v>>>.<^v^^.>>>>^<<v<.^^>>>>^><v^v>vvv^>^><<#
|
||||
#><vvvv.>v^.v>>><>^>><><v^<.>.^<^v.>.^<^>>^v<.<^^v<^><..<vv^v<>vv<<vv^v.>>^.<v^>^>^<^<^>><><v<<>.><<>#
|
||||
#>.<^^v^><<<.v<vv<>^<>v^vv<^^.<>>.^<vv>>^.v.>^><v^^>.<<>v<>^v<v><v^^<v.v><<>vvv.v<>v>vvv^<>v^>^v<<<><#
|
||||
#.>><<vv><>vv>v..<<^^vv^>^<>^^v>v^vv>.><>^<vv^^<v><^^>><>>v<<v>^v^^><>.^.vv>v.^^>^vv<><.>>^^<^<><>v>.#
|
||||
#><<v>v<><^<<^><..<^>v<<^.^^<.^^v<>>^v>.^^^<<v>v>><><.<>>^v<^.v<vv<<^>>>vv^<vv.>^^^<><>v.>.^<^v<vv^><#
|
||||
#<.>vvv<v>^.vv.>.<v^.<..<>^v<>>^^vv>.>^v^>>>vv^<v><^v.v^>>>>><>v>v<>v^^>.^^v>^v><<>vv^<><>>v<^<v<<<<>#
|
||||
#<v<><^v<>>.<v^><<>^>v<<.v^v.<^v.^<vv<.v.^<v><><><v^^>v<>.vv^>vv<<^^<^^^><<>^>.^v<<<^<^<^^>>vv.<v.v<.#
|
||||
#<>vv^^<<^^^v^^<v^vvv^>^^.vvv^^^^v^>^^^v<^v><<^^^.<><v.vv<<^v<^>^><.^><^<>.v>^vv^v<<^<.<^.^>vv>^v>^<.#
|
||||
#<.vv>><.<vv<^^^<.^v><>>>><<^^<><^<vv<<>^v..>v<^^v><^><<^^^<<>^^>.>v><^<v<><v>v^<v<v^v.vv>v^<<<v>.^>>#
|
||||
#<^vv<<v^><>^^<<^.<v^>^vv.><^>v.>^vv.<^v^^<v<<v<><^<>v<<v<<><>^v^>v^^^><^>>^.<v.^<.^^.v<^v<>>.<<<v>^<#
|
||||
#>>..^^^<v.v<>>^^<v^..^vv<>.<>>>^><>^<v><<.v.^v^^<^<><>..>^<>.>^^<v.^v<v<<^v>^vv.v>.<.^^<v^<<>^^<^v<.#
|
||||
#>>vvv^v^v^>v<<vv.^.^<<vv^>^>v<.^>^<v^>^^<^<>^.><^v<<<v>>..v><>v^>^>><^^<>.v<vv^>>>>v<^v.v>v.>^vv^.^<#
|
||||
#<^<>.^<>>^<^v>.^v.><.<v.v.^v><^>.>^<>^^>>^^^.<^<>><>^><vvv^>^.v^^v>vv<<vvv>^.^>><.<>^.^<>.>>>v>v<v^>#
|
||||
#<^^vv<<^<^.<^>^<vv.>>.>><^<^^^<<>.v>v>v>v.^<<v<.<>v<<<>v<>^<v.^<<<v<<>>>>.^><^^<>>>^<><^.^.v^^v^^>..#
|
||||
#..v>vv^<vv>><^>^^^<v>><v^^><>.^^><^<><>^>><v>^v<><v.^<>><.>^>v^>^v<><v^v>><><^>v>.v^v>v<><.^^.>.v.^<#
|
||||
#.<v>v<^v.<<<vv<>^v^vv<^v^>^^v^<>^>>vv<^^v^^^^<.^v<>^<<^<v^v<><v>v^<<>>.vvv.>>^v>>>vv<<><^v<v^v<<.>>>#
|
||||
#<<vv.v^.>..v><v<><vv<<<<.>vv>v>>^v^v<v^<v<>v<<v^v^v^<<v^>v<^^^>v<^^^^^>>><<v<>v<>.>v^v<><.<<v<>^.^^.#
|
||||
#>>>>>^^.><^<>^<>v>.<^vv^^<>>^v><^<^>vv^<v><.>^v>.v<v<>>>>^.^>^v^^^<vvv^.<<..^v<.>^><<>><<^<.<<v<>^v<#
|
||||
#<.v.^>.^<.v<^^<.<^<<^.^<>><^<<^<><.<>.><<<><^v<v>^^v<<v>><^<>>^>^^>vv>^<.^<v><>.><v^v<vv^<^^<v>>>^v<#
|
||||
#<>v^>^^v>>^<^>v>>>.>^.><>..>^v>><>v<>^>.^v<<<<^>v>v><v^<^>^^>^<<>^>vv><<<v>v^^v<vv<^<^>vv^<.v^<^v>^>#
|
||||
#><<vvv.<<<vv<vv^^^v^v<^>vv^<>^^v<>v<^^^^><>^^>^<<<>v.vv^<><<><.^<<<^>>^vv<.v^^>vv><<<>v.<><v^><^^^v.#
|
||||
#<><v<v><^<^^vvv^>><><<<.<<vv<^<>.^<>>^v^>>vvv>>v..><<^^.v<><^^><^>>^<<v<<^v^>v>vvvvv...^<v^<^<v<.^<.#
|
||||
#>><<<v^^v^>^<<.>>vvv^v<^<>.^v^<^v<^^>>v<.>v<v..^^v.^>.v>^>>v^<<.v<^<..>.v<<..^^^v>v<.^<.>v^v^<v^v>^>#
|
||||
#<^>v<>^<<v>>vv>.><^v<^.><<^v>v<^<<<v^.^v.><>v><>^v><<<v.>vv<>vv>><>>^><><v>>v^.^v<<v^vvv<.<<>.v^^v<>#
|
||||
#<^<^><<>v..>v^>v>.v<<<^<<<v^v<<>^<.<.<v.v^<><v^<<>^><vv.v>>vv^.v^.^^>^^<<^^>.vv<.>.^^<>.<<v><vv<<>>>#
|
||||
#>v>>vv<.v<v.<^.^^><><^^v>^>>>>^>vv><<v>vv^.>>v^^vv^v^.>^v<>>>^^^>..vv^.^><..>>v<>^>^^<<.v<>v^^^v>^v>#
|
||||
####################################################################################################.#
|
||||
49
src/common/abs.rs
Normal file
49
src/common/abs.rs
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
pub trait Abs {
|
||||
fn abs(&self) -> Self;
|
||||
fn abs_beween(&self, other: &Self) -> Self;
|
||||
fn is_negative(&self) -> bool;
|
||||
}
|
||||
|
||||
macro_rules! signed_impl {
|
||||
($($t:ty)*) => ($(
|
||||
impl Abs for $t {
|
||||
#[inline]
|
||||
fn abs(&self) -> $t {
|
||||
if self.is_negative() { -*self } else { *self }
|
||||
}
|
||||
#[inline]
|
||||
fn abs_beween(&self, other: &Self) -> Self {
|
||||
(*self - *other).abs()
|
||||
}
|
||||
#[inline]
|
||||
fn is_negative(&self) -> bool {
|
||||
*self < 0
|
||||
}
|
||||
}
|
||||
)*)
|
||||
}
|
||||
signed_impl!(isize i8 i16 i32 i64 i128);
|
||||
|
||||
macro_rules! unsigned_impl {
|
||||
($($t:ty)*) => ($(
|
||||
impl Abs for $t {
|
||||
#[inline]
|
||||
fn abs(&self) -> $t {
|
||||
*self
|
||||
}
|
||||
#[inline]
|
||||
fn abs_beween(&self, other: &Self) -> Self {
|
||||
if *self >= *other {
|
||||
*self - *other
|
||||
} else {
|
||||
*other-*self
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
fn is_negative(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
)*)
|
||||
}
|
||||
unsigned_impl!(usize u8 u16 u32 u64 u128);
|
||||
|
|
@ -6,10 +6,10 @@ use Turn::*;
|
|||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum Direction {
|
||||
East,
|
||||
North,
|
||||
West,
|
||||
South,
|
||||
East = 0,
|
||||
North = 1,
|
||||
West = 2,
|
||||
South = 3,
|
||||
}
|
||||
|
||||
impl Direction {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
pub mod abs;
|
||||
pub mod area;
|
||||
pub mod direction;
|
||||
pub mod file;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use super::direction::Direction;
|
||||
use super::math::gcd;
|
||||
use super::{abs::Abs, math::gcd};
|
||||
use num_traits::{CheckedAdd, CheckedSub, Float, Num, NumCast, Signed, Zero};
|
||||
use std::fmt;
|
||||
use std::ops::{Add, AddAssign, Div, Mul, Neg, Sub};
|
||||
|
|
@ -119,7 +119,7 @@ where
|
|||
|
||||
impl<T> Pos2<T>
|
||||
where
|
||||
T: Signed,
|
||||
T: Num + Abs,
|
||||
{
|
||||
pub fn abs(self) -> T {
|
||||
self.get_x().abs() + self.get_y().abs()
|
||||
|
|
@ -223,10 +223,10 @@ where
|
|||
|
||||
impl<T> Pos2<T>
|
||||
where
|
||||
T: Num + Signed + Copy,
|
||||
T: Num + Abs + Copy,
|
||||
{
|
||||
pub fn taxicab_between(self, other: Pos2<T>) -> T {
|
||||
(self - other).abs()
|
||||
self.x().abs_beween(&other.x()) + self.y().abs_beween(&other.y())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
530
src/days/day24/mod.rs
Normal file
530
src/days/day24/mod.rs
Normal file
|
|
@ -0,0 +1,530 @@
|
|||
use std::{
|
||||
collections::{BinaryHeap, HashSet},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use crate::common::{direction::Direction, file::split_lines, math::lcm, pos2::Pos2};
|
||||
|
||||
use super::template::{DayTrait, ResultType};
|
||||
use itertools::Itertools;
|
||||
use thiserror::Error;
|
||||
|
||||
const DAY_NUMBER: usize = 24;
|
||||
|
||||
pub struct Day;
|
||||
|
||||
impl DayTrait for Day {
|
||||
fn get_day_number(&self) -> usize {
|
||||
DAY_NUMBER
|
||||
}
|
||||
|
||||
fn part1(&self, lines: &str) -> anyhow::Result<ResultType> {
|
||||
let valley: Valley = lines.parse()?;
|
||||
Ok(ResultType::Integer(valley.cross(Trip(1))? as i64))
|
||||
}
|
||||
|
||||
fn part2(&self, lines: &str) -> anyhow::Result<ResultType> {
|
||||
let valley: Valley = lines.parse()?;
|
||||
Ok(ResultType::Integer(valley.cross(Trip(3))? as i64))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
enum BlizzardError {
|
||||
#[error("Not a valid description: {0}")]
|
||||
ParsingError(String),
|
||||
#[error("Illegal char: {0}")]
|
||||
IllegalChar(char),
|
||||
#[error("Need exacly one door. Found: {0}")]
|
||||
NeedExactlyOneDoor(String),
|
||||
#[error("Valley has no legal shape")]
|
||||
ValleyHasIllegalShape,
|
||||
#[error("No valid path was found")]
|
||||
NoPathFound,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
struct Trip(usize);
|
||||
impl Trip {
|
||||
#[inline]
|
||||
pub fn inc(&self) -> Trip {
|
||||
Trip(self.0 + 1)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
struct Time(usize);
|
||||
|
||||
impl Time {
|
||||
#[inline]
|
||||
pub fn get(&self) -> usize {
|
||||
self.0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn inc(&self) -> Time {
|
||||
Time(self.0 + 1)
|
||||
}
|
||||
}
|
||||
|
||||
struct Valley {
|
||||
height: usize,
|
||||
entry: usize,
|
||||
exit: usize,
|
||||
storm: Storm,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct State {
|
||||
time: Time,
|
||||
trip: Trip,
|
||||
position: Pos2<usize>,
|
||||
start: Pos2<usize>,
|
||||
target: Pos2<usize>,
|
||||
}
|
||||
|
||||
impl Eq for State {}
|
||||
|
||||
impl PartialEq for State {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.time == other.time
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for State {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.cmp(&other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for State {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
self.time.cmp(&other.time).reverse()
|
||||
}
|
||||
}
|
||||
|
||||
impl State {
|
||||
fn new(start: Pos2<usize>, target: Pos2<usize>) -> Self {
|
||||
Self {
|
||||
time: Time(0),
|
||||
trip: Trip(0),
|
||||
position: start,
|
||||
start,
|
||||
target,
|
||||
}
|
||||
}
|
||||
|
||||
fn wait(&self) -> Self {
|
||||
Self {
|
||||
time: self.time.inc(),
|
||||
trip: self.trip,
|
||||
position: self.position,
|
||||
start: self.start,
|
||||
target: self.target,
|
||||
}
|
||||
}
|
||||
|
||||
fn walk_to(&self, position: Pos2<usize>) -> Self {
|
||||
if position == self.target {
|
||||
Self {
|
||||
time: self.time.inc(),
|
||||
trip: self.trip.inc(),
|
||||
position,
|
||||
start: self.target,
|
||||
target: self.start,
|
||||
}
|
||||
} else {
|
||||
Self {
|
||||
time: self.time.inc(),
|
||||
trip: self.trip,
|
||||
position,
|
||||
start: self.start,
|
||||
target: self.target,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn next_states(&self, valley: &Valley) -> Vec<State> {
|
||||
let frame = valley.storm.get(self.time.inc());
|
||||
|
||||
let mut states = Vec::new();
|
||||
let mut dir = Direction::East;
|
||||
for _ in 0..4 {
|
||||
if let Some(next_pos) = self.position.check_add(dir) {
|
||||
if frame.is_empty(&next_pos) || next_pos == self.target {
|
||||
states.push(self.walk_to(next_pos))
|
||||
}
|
||||
}
|
||||
dir = dir.turn_left();
|
||||
}
|
||||
if frame.is_empty(&self.position)
|
||||
|| (states.is_empty() && (self.position == self.target || self.position == self.start))
|
||||
{
|
||||
states.push(self.wait());
|
||||
}
|
||||
states
|
||||
}
|
||||
}
|
||||
|
||||
impl Valley {
|
||||
fn doorline(input: &str) -> Result<usize, BlizzardError> {
|
||||
let doors = input
|
||||
.char_indices()
|
||||
.map(|(col, c)| match c {
|
||||
'.' => Ok(Some(col)),
|
||||
'#' => Ok(None),
|
||||
_ => Err(BlizzardError::IllegalChar(c)),
|
||||
})
|
||||
.filter_map_ok(|item| item)
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
if doors.len() != 1 {
|
||||
Err(BlizzardError::NeedExactlyOneDoor(input.to_owned()))
|
||||
} else {
|
||||
Ok(doors[0] - 1)
|
||||
}
|
||||
}
|
||||
|
||||
fn storm(input: &[&str]) -> Result<Blizzards, BlizzardError> {
|
||||
let height = input.len();
|
||||
if height == 0 {
|
||||
return Err(BlizzardError::ValleyHasIllegalShape);
|
||||
}
|
||||
let width = input[0].len();
|
||||
if width == 0
|
||||
|| input[0].chars().nth(0) != Some('#')
|
||||
|| input[0].chars().nth(width - 1) != Some('#')
|
||||
{
|
||||
return Err(BlizzardError::ValleyHasIllegalShape);
|
||||
}
|
||||
if !input
|
||||
.iter()
|
||||
.map(|row| {
|
||||
(
|
||||
row.len(),
|
||||
row.chars().nth(0),
|
||||
row.chars().nth(row.len() - 1),
|
||||
)
|
||||
})
|
||||
.all_equal()
|
||||
{
|
||||
return Err(BlizzardError::ValleyHasIllegalShape);
|
||||
}
|
||||
|
||||
let raw = input
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(y, row)| {
|
||||
row.char_indices().filter_map(move |(x, c)| match c {
|
||||
'#' => {
|
||||
if x != 0 && x != row.len() - 1 {
|
||||
Some(Err(BlizzardError::IllegalChar('#')))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
'>' => Some(Ok((Pos2::new(x - 1, y), Direction::East))),
|
||||
'^' => Some(Ok((Pos2::new(x - 1, y), Direction::North))),
|
||||
'<' => Some(Ok((Pos2::new(x - 1, y), Direction::West))),
|
||||
'v' => Some(Ok((Pos2::new(x - 1, y), Direction::South))),
|
||||
'.' => None,
|
||||
_ => Some(Err(BlizzardError::IllegalChar(c))),
|
||||
})
|
||||
})
|
||||
.flatten()
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
Ok(Blizzards::new(raw, width - 2, height))
|
||||
}
|
||||
|
||||
fn cross(&self, trips: Trip) -> Result<usize, BlizzardError> {
|
||||
let start = State::new(self.get_entry(), self.get_exit());
|
||||
let mut queue = BinaryHeap::new();
|
||||
queue.push(start);
|
||||
let mut seen = HashSet::new();
|
||||
let mut advanced = Trip(0);
|
||||
|
||||
while let Some(current) = queue.pop() {
|
||||
if current.trip == trips {
|
||||
return Ok(current.time.get());
|
||||
}
|
||||
if advanced > current.trip.inc() {
|
||||
continue;
|
||||
}
|
||||
advanced = advanced.max(current.trip);
|
||||
|
||||
let normalized_time = self.normalize_time(current.time);
|
||||
let fingerprint = (normalized_time, current.trip, current.position);
|
||||
if seen.contains(&fingerprint) {
|
||||
continue;
|
||||
}
|
||||
seen.insert(fingerprint);
|
||||
|
||||
queue.extend(current.next_states(&self));
|
||||
}
|
||||
Err(BlizzardError::NoPathFound)
|
||||
}
|
||||
|
||||
fn get_entry(&self) -> Pos2<usize> {
|
||||
Pos2::new(self.entry, 0)
|
||||
}
|
||||
fn get_exit(&self) -> Pos2<usize> {
|
||||
Pos2::new(self.exit, self.height + 1)
|
||||
}
|
||||
|
||||
fn normalize_time(&self, time: Time) -> Time {
|
||||
Time(time.get() % self.storm.len())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Valley {
|
||||
type Err = BlizzardError;
|
||||
|
||||
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||
let lines = split_lines(input).collect_vec();
|
||||
if lines.len() < 3 {
|
||||
return Err(BlizzardError::ParsingError(input.to_owned()));
|
||||
}
|
||||
let entry = Valley::doorline(lines[0])?;
|
||||
let exit = Valley::doorline(lines[lines.len() - 1])?;
|
||||
|
||||
let blizzards = Valley::storm(&lines[1..lines.len() - 1])?;
|
||||
let height = blizzards.height;
|
||||
|
||||
let storm = Storm::new(blizzards);
|
||||
|
||||
Ok(Self {
|
||||
entry,
|
||||
exit,
|
||||
height,
|
||||
storm,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct BlizzardLine {
|
||||
direction: Direction,
|
||||
blizzards: Vec<usize>,
|
||||
len: usize,
|
||||
}
|
||||
|
||||
impl BlizzardLine {
|
||||
fn new(direction: Direction, len: usize) -> Self {
|
||||
BlizzardLine {
|
||||
direction,
|
||||
blizzards: vec![],
|
||||
len,
|
||||
}
|
||||
}
|
||||
|
||||
fn iter(&self, pos: usize, time: usize) -> impl Iterator<Item = Pos2<usize>> + '_ {
|
||||
let time = time % self.len;
|
||||
self.blizzards
|
||||
.iter()
|
||||
.map(move |delta| match self.direction {
|
||||
Direction::East => Pos2::new((time + *delta) % self.len, pos),
|
||||
Direction::West => Pos2::new((self.len - time + *delta) % self.len, pos),
|
||||
Direction::North => Pos2::new(pos, (self.len - time + *delta) % self.len),
|
||||
Direction::South => Pos2::new(pos, (time + *delta) % self.len),
|
||||
})
|
||||
}
|
||||
|
||||
fn push(&mut self, x: usize) {
|
||||
self.blizzards.push(x);
|
||||
}
|
||||
}
|
||||
|
||||
struct Blizzards {
|
||||
blizzards: Vec<Vec<BlizzardLine>>,
|
||||
width: usize,
|
||||
height: usize,
|
||||
period: usize,
|
||||
}
|
||||
|
||||
impl Blizzards {
|
||||
pub fn new(raw: Vec<(Pos2<usize>, Direction)>, width: usize, height: usize) -> Self {
|
||||
let mut blizzards = vec![
|
||||
vec![BlizzardLine::new(Direction::East, width); height],
|
||||
vec![BlizzardLine::new(Direction::North, height); width],
|
||||
vec![BlizzardLine::new(Direction::West, width); height],
|
||||
vec![BlizzardLine::new(Direction::South, height); width],
|
||||
];
|
||||
|
||||
for (pos, direction) in raw {
|
||||
match direction {
|
||||
Direction::East | Direction::West => {
|
||||
blizzards[direction as usize][pos.y()].push(pos.x())
|
||||
}
|
||||
Direction::North | Direction::South => {
|
||||
blizzards[direction as usize][pos.x()].push(pos.y())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Self {
|
||||
blizzards,
|
||||
width,
|
||||
height,
|
||||
period: lcm(width, height),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn frame_at_time(&self, time: usize) -> Frame {
|
||||
let time = time % self.period;
|
||||
let mut valley = vec![vec![true; self.width]; self.height];
|
||||
for _ in 0..4 {
|
||||
for dir in self.blizzards.iter() {
|
||||
for (pos, blizzard) in dir.iter().enumerate() {
|
||||
for item in blizzard.iter(pos, time) {
|
||||
valley[item.y()][item.x()] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Frame(valley)
|
||||
}
|
||||
}
|
||||
|
||||
struct Frame(Vec<Vec<bool>>);
|
||||
|
||||
impl Frame {
|
||||
fn is_empty(&self, pos: &Pos2<usize>) -> bool {
|
||||
if pos.y() == 0 {
|
||||
false
|
||||
} else {
|
||||
self.0
|
||||
.get(pos.y() - 1)
|
||||
.and_then(|row| row.get(pos.x()))
|
||||
.copied()
|
||||
.unwrap_or(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Storm(Vec<Frame>);
|
||||
|
||||
impl Storm {
|
||||
fn new(blizzards: Blizzards) -> Self {
|
||||
let mut storm = Vec::with_capacity(blizzards.period);
|
||||
for time in 0..blizzards.period {
|
||||
storm.push(blizzards.frame_at_time(time));
|
||||
}
|
||||
Self(storm)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[allow(dead_code)]
|
||||
pub fn len(&self) -> usize {
|
||||
self.0.len()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get(&self, time: Time) -> &Frame {
|
||||
let index = time.get() % self.0.len();
|
||||
&self.0[index]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::common::file::read_string;
|
||||
use anyhow::Result;
|
||||
|
||||
#[test]
|
||||
fn test_part1() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||
let expected = ResultType::Integer(18);
|
||||
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(54);
|
||||
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 valley: Valley = lines.parse()?;
|
||||
assert_eq!(valley.entry, 0);
|
||||
assert_eq!(valley.exit, 4);
|
||||
assert_eq!(valley.height, 5);
|
||||
assert_eq!(valley.storm.len(), 5);
|
||||
assert_eq!(valley.get_entry(), Pos2::new(0, 0));
|
||||
assert_eq!(valley.get_exit(), Pos2::new(4, 6));
|
||||
assert_eq!(valley.storm.get(Time(3)).is_empty(&Pos2::new(3, 2)), false);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse2() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||
|
||||
let valley: Valley = lines.parse()?;
|
||||
assert_eq!(valley.entry, 0);
|
||||
assert_eq!(valley.exit, 5);
|
||||
assert_eq!(valley.height, 4);
|
||||
assert_eq!(valley.storm.len(), 12);
|
||||
assert_eq!(valley.get_entry(), Pos2::new(0, 0));
|
||||
assert_eq!(valley.get_exit(), Pos2::new(5, 5));
|
||||
let expected = vec![
|
||||
vec![true, false, false, true, false, true],
|
||||
vec![false, true, true, false, false, true],
|
||||
vec![false, false, true, false, false, true],
|
||||
vec![false, false, true, true, false, false],
|
||||
];
|
||||
assert_eq!(valley.storm.get(Time(1)).0, expected);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn possible_moves() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||
|
||||
let valley: Valley = lines.parse()?;
|
||||
let position = State {
|
||||
time: Time(17),
|
||||
trip: Trip(0),
|
||||
position: Pos2::new(5, 4),
|
||||
start: Pos2::new(0, 0),
|
||||
target: Pos2::new(5, 5),
|
||||
};
|
||||
let mut next_states = position.next_states(&valley);
|
||||
next_states.sort();
|
||||
let expected = vec![
|
||||
State {
|
||||
time: Time(18),
|
||||
trip: Trip(1),
|
||||
position: Pos2::new(5, 5),
|
||||
start: Pos2::new(5, 5),
|
||||
target: Pos2::new(0, 0),
|
||||
},
|
||||
State {
|
||||
time: Time(18),
|
||||
trip: Trip(0),
|
||||
position: Pos2::new(4, 4),
|
||||
start: Pos2::new(0, 0),
|
||||
target: Pos2::new(5, 5),
|
||||
},
|
||||
];
|
||||
assert_eq!(next_states, expected);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
@ -21,6 +21,7 @@ mod day20;
|
|||
mod day21;
|
||||
mod day22;
|
||||
mod day23;
|
||||
mod day24;
|
||||
mod template;
|
||||
|
||||
pub use template::DayTrait;
|
||||
|
|
@ -30,7 +31,7 @@ pub mod day_provider {
|
|||
use super::*;
|
||||
use thiserror::Error;
|
||||
|
||||
const MAX_DAY: usize = 23;
|
||||
const MAX_DAY: usize = 24;
|
||||
|
||||
pub fn get_day(day_num: usize) -> Result<Box<dyn DayTrait>, ProviderError> {
|
||||
match day_num {
|
||||
|
|
@ -57,6 +58,7 @@ pub mod day_provider {
|
|||
21 => Ok(Box::new(day21::Day)),
|
||||
22 => Ok(Box::new(day22::Day)),
|
||||
23 => Ok(Box::new(day23::Day)),
|
||||
24 => Ok(Box::new(day24::Day)),
|
||||
_ => Err(ProviderError::InvalidNumber(day_num)),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue