day 24 finsihed

This commit is contained in:
Rüdiger Ludwig 2023-08-13 12:26:59 +02:00
parent 62cb729aee
commit abc1530bf3
9 changed files with 641 additions and 9 deletions

6
data/day24/example01.txt Normal file
View file

@ -0,0 +1,6 @@
#.######
#>>.<^<#
#.<..<<#
#>v.><>#
#<^v^^>#
######.#

7
data/day24/example02.txt Normal file
View file

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

37
data/day24/input.txt Normal file
View 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
View 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);

View file

@ -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 {

View file

@ -1,3 +1,4 @@
pub mod abs;
pub mod area;
pub mod direction;
pub mod file;

View 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
View 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(())
}
}

View file

@ -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)),
}
}