day08 finished

This commit is contained in:
Ruediger Ludwig 2023-02-05 09:36:50 +01:00
parent 745abc936e
commit a2f7808f6d
4 changed files with 339 additions and 1 deletions

232
src/days/day08/mod.rs Normal file
View file

@ -0,0 +1,232 @@
use super::template::{DayTrait, ResultType};
use itertools::{iproduct, FoldWhile, Itertools};
use thiserror::Error;
const DAY_NUMBER: usize = 8;
pub struct Day;
impl DayTrait for Day {
fn get_day_number(&self) -> usize {
DAY_NUMBER
}
fn part1(&self, lines: &[String]) -> anyhow::Result<ResultType> {
let forest = Forest::parse(lines)?;
let result = forest.count_visible();
Ok(ResultType::Integer(result))
}
fn part2(&self, lines: &[String]) -> anyhow::Result<ResultType> {
let forest = Forest::parse(lines)?;
let result = forest.best_score();
Ok(ResultType::Integer(result))
}
}
#[derive(Debug, Error)]
enum ForestError {
#[error("Not a legal digit: {0}")]
NoLegalDigit(char),
#[error("The Forst is not a rectangle")]
NonRectangleForest,
#[error("Zero Sized Forest not allowed")]
NoZeroSizedForest,
}
#[derive(Debug)]
struct Forest {
trees: Vec<Vec<u32>>,
width: usize,
height: usize,
}
impl Forest {
pub fn create(trees: Vec<Vec<u32>>) -> Result<Self, ForestError> {
let height = trees.len();
if height == 0 {
return Err(ForestError::NoZeroSizedForest);
}
let width = trees[0].len();
if width == 0 {
return Err(ForestError::NoZeroSizedForest);
}
for row in trees.iter() {
if row.len() != width {
return Err(ForestError::NonRectangleForest);
}
}
Ok(Forest {
trees,
width,
height,
})
}
pub fn parse(lines: &[String]) -> Result<Forest, ForestError> {
lines
.iter()
.map(|line| {
line.chars()
.map(|height| height.to_digit(10).ok_or(ForestError::NoLegalDigit(height)))
.collect::<Result<Vec<_>, _>>()
})
.collect::<Result<Vec<_>, _>>()
.and_then(Forest::create)
}
pub fn count_visible(&self) -> i64 {
let mut vis_count = 2 * (self.width + self.height - 2) as i64;
let mut visible = vec![vec![false; self.width - 2]; self.height - 2];
for y in 1..self.height - 1 {
let mut talest_seen = self.trees[y][0];
for x in 1..self.width - 1 {
let tree = self.trees[y][x];
if tree > talest_seen {
vis_count += 1;
visible[y - 1][x - 1] = true;
talest_seen = self.trees[y][x];
}
}
let mut talest_seen = self.trees[y][self.width - 1];
for x in (1..self.width - 1).rev() {
let tree = self.trees[y][x];
if tree > talest_seen {
if !visible[y - 1][x - 1] {
vis_count += 1;
visible[y - 1][x - 1] = true;
}
talest_seen = self.trees[y][x];
}
}
}
for x in 1..self.width - 1 {
let mut talest_seen = self.trees[0][x];
for y in 1..self.height - 1 {
let tree = self.trees[y][x];
if tree > talest_seen {
if !visible[y - 1][x - 1] {
vis_count += 1;
visible[y - 1][x - 1] = true;
}
talest_seen = self.trees[y][x];
}
}
let mut talest_seen = self.trees[self.height - 1][x];
for y in (1..self.height - 1).rev() {
let tree = self.trees[y][x];
if tree > talest_seen {
if !visible[y - 1][x - 1] {
vis_count += 1;
visible[y - 1][x - 1] = true;
}
talest_seen = self.trees[y][x];
}
}
}
vis_count
}
pub fn best_score(&self) -> i64 {
iproduct!(1..self.width - 1, 1..self.height - 1)
.map(|(x, y)| self.score(x, y))
.max()
// None can only happen when we have a forrest with no inner trees.
// In that case 0 is the correct solution.
// We already forbid zero height and zero width forests during create.
.unwrap_or(0)
}
fn score(&self, x: usize, y: usize) -> i64 {
let tree = self.trees[y][x];
let left = (0..x)
.rev()
.fold_while(0, |acc, x| {
if self.trees[y][x] < tree {
FoldWhile::Continue(acc + 1)
} else {
FoldWhile::Done(acc + 1)
}
})
.into_inner();
let right = (x + 1..self.width)
.fold_while(0, |acc, x| {
if self.trees[y][x] < tree {
FoldWhile::Continue(acc + 1)
} else {
FoldWhile::Done(acc + 1)
}
})
.into_inner();
let up = (0..y)
.rev()
.fold_while(0, |acc, y| {
if self.trees[y][x] < tree {
FoldWhile::Continue(acc + 1)
} else {
FoldWhile::Done(acc + 1)
}
})
.into_inner();
let down = (y + 1..self.height)
.fold_while(0, |acc, y| {
if self.trees[y][x] < tree {
FoldWhile::Continue(acc + 1)
} else {
FoldWhile::Done(acc + 1)
}
})
.into_inner();
left * right * up * down
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::common::file::read_lines;
use anyhow::Result;
#[test]
fn test_parse() -> Result<()> {
let day = Day {};
let lines = read_lines(day.get_day_number(), "example01.txt")?;
let forest = Forest::parse(&lines)?;
assert_eq!(forest.width, 5);
assert_eq!(forest.height, 5);
Ok(())
}
#[test]
fn test_part1() -> Result<()> {
let day = Day {};
let lines = read_lines(day.get_day_number(), "example01.txt")?;
let expected = ResultType::Integer(21);
let result = day.part1(&lines)?;
assert_eq!(result, expected);
Ok(())
}
#[test]
fn test_part2() -> Result<()> {
let day = Day {};
let lines = read_lines(day.get_day_number(), "example01.txt")?;
let expected = ResultType::Integer(8);
let result = day.part2(&lines)?;
assert_eq!(result, expected);
Ok(())
}
}

View file

@ -5,6 +5,7 @@ mod day04;
mod day05;
mod day06;
mod day07;
mod day08;
mod template;
pub use template::DayTrait;
@ -14,7 +15,7 @@ pub mod day_provider {
use super::*;
use thiserror::Error;
const MAX_DAY: usize = 7;
const MAX_DAY: usize = 8;
pub fn get_day(day_num: usize) -> Result<Box<dyn DayTrait>, ProviderError> {
match day_num {
@ -25,6 +26,7 @@ pub mod day_provider {
5 => Ok(Box::new(day05::Day)),
6 => Ok(Box::new(day06::Day)),
7 => Ok(Box::new(day07::Day)),
8 => Ok(Box::new(day08::Day)),
_ => Err(ProviderError::InvalidNumber(day_num)),
}
}