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)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||||
pub enum Direction {
|
pub enum Direction {
|
||||||
East,
|
East = 0,
|
||||||
North,
|
North = 1,
|
||||||
West,
|
West = 2,
|
||||||
South,
|
South = 3,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Direction {
|
impl Direction {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
pub mod abs;
|
||||||
pub mod area;
|
pub mod area;
|
||||||
pub mod direction;
|
pub mod direction;
|
||||||
pub mod file;
|
pub mod file;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use super::direction::Direction;
|
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 num_traits::{CheckedAdd, CheckedSub, Float, Num, NumCast, Signed, Zero};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::ops::{Add, AddAssign, Div, Mul, Neg, Sub};
|
use std::ops::{Add, AddAssign, Div, Mul, Neg, Sub};
|
||||||
|
|
@ -119,7 +119,7 @@ where
|
||||||
|
|
||||||
impl<T> Pos2<T>
|
impl<T> Pos2<T>
|
||||||
where
|
where
|
||||||
T: Signed,
|
T: Num + Abs,
|
||||||
{
|
{
|
||||||
pub fn abs(self) -> T {
|
pub fn abs(self) -> T {
|
||||||
self.get_x().abs() + self.get_y().abs()
|
self.get_x().abs() + self.get_y().abs()
|
||||||
|
|
@ -223,10 +223,10 @@ where
|
||||||
|
|
||||||
impl<T> Pos2<T>
|
impl<T> Pos2<T>
|
||||||
where
|
where
|
||||||
T: Num + Signed + Copy,
|
T: Num + Abs + Copy,
|
||||||
{
|
{
|
||||||
pub fn taxicab_between(self, other: Pos2<T>) -> T {
|
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 day21;
|
||||||
mod day22;
|
mod day22;
|
||||||
mod day23;
|
mod day23;
|
||||||
|
mod day24;
|
||||||
mod template;
|
mod template;
|
||||||
|
|
||||||
pub use template::DayTrait;
|
pub use template::DayTrait;
|
||||||
|
|
@ -30,7 +31,7 @@ pub mod day_provider {
|
||||||
use super::*;
|
use super::*;
|
||||||
use thiserror::Error;
|
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> {
|
pub fn get_day(day_num: usize) -> Result<Box<dyn DayTrait>, ProviderError> {
|
||||||
match day_num {
|
match day_num {
|
||||||
|
|
@ -57,6 +58,7 @@ pub mod day_provider {
|
||||||
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)),
|
23 => Ok(Box::new(day23::Day)),
|
||||||
|
24 => Ok(Box::new(day24::Day)),
|
||||||
_ => Err(ProviderError::InvalidNumber(day_num)),
|
_ => Err(ProviderError::InvalidNumber(day_num)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue