use super::template::{DayTrait, ResultType}; use crate::common::file::split_lines; use itertools::{iproduct, FoldWhile, Itertools}; use std::str::FromStr; 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: &str) -> anyhow::Result { let forest: Forest = lines.parse()?; let result = forest.count_visible(); Ok(ResultType::Integer(result)) } fn part2(&self, lines: &str) -> anyhow::Result { let forest: Forest = lines.parse()?; 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>, width: usize, height: usize, } impl Forest { pub fn create(trees: Vec>) -> Result { 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 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 } } impl FromStr for Forest { type Err = ForestError; fn from_str(lines: &str) -> Result { split_lines(lines) .map(|line| { line.chars() .map(|height| height.to_digit(10).ok_or(ForestError::NoLegalDigit(height))) .collect::, _>>() }) .collect::, _>>() .and_then(Forest::create) } } #[cfg(test)] mod test { use super::*; use crate::common::file::read_string; use anyhow::Result; #[test] fn test_parse() -> Result<()> { let day = Day {}; let lines = read_string(day.get_day_number(), "example01.txt")?; let forest: Forest = lines.parse()?; assert_eq!(forest.width, 5); assert_eq!(forest.height, 5); Ok(()) } #[test] fn test_part1() -> Result<()> { let day = Day {}; let lines = read_string(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_string(day.get_day_number(), "example01.txt")?; let expected = ResultType::Integer(8); let result = day.part2(&lines)?; assert_eq!(result, expected); Ok(()) } }