summaryrefslogtreecommitdiffstats
path: root/src/day_8.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/day_8.rs')
-rw-r--r--src/day_8.rs326
1 files changed, 326 insertions, 0 deletions
diff --git a/src/day_8.rs b/src/day_8.rs
new file mode 100644
index 0000000..145bc22
--- /dev/null
+++ b/src/day_8.rs
@@ -0,0 +1,326 @@
+use std::{collections::HashSet, fmt::Display};
+
+use anyhow::Result;
+
+use crate::{Problem, Solution};
+
+type TreeHeight = i8;
+
+struct Visibility {
+ capacity_x: usize,
+ capacity_y: usize,
+ trees_seen: HashSet<(usize, usize)>,
+ max_north: Vec<TreeHeight>,
+ max_east: Vec<TreeHeight>,
+ max_south: Vec<TreeHeight>,
+ max_west: Vec<TreeHeight>,
+}
+
+impl Visibility {
+ fn with_capacity(capacity_x: usize, capacity_y: usize) -> Self {
+ Self {
+ capacity_x,
+ capacity_y,
+ trees_seen: HashSet::new(),
+ max_north: vec![-1; capacity_x],
+ max_east: vec![-1; capacity_y],
+ max_south: vec![-1; capacity_x],
+ max_west: vec![-1; capacity_y],
+ }
+ }
+
+ fn check_trees(&mut self, tree_grid: Vec<Vec<TreeHeight>>) {
+ self.check_trees_nw(&tree_grid);
+ self.check_trees_se(&tree_grid);
+ }
+
+ fn check_trees_nw(&mut self, tree_grid: &[Vec<TreeHeight>]) {
+ for (y, trees) in tree_grid.iter().enumerate() {
+ for (x, tree) in trees.iter().enumerate() {
+ self.check_tree_nw(x, y, *tree)
+ }
+ }
+ }
+
+ fn check_trees_se(&mut self, tree_grid: &[Vec<TreeHeight>]) {
+ for (y, trees) in tree_grid.iter().enumerate().rev() {
+ for (x, tree) in trees.iter().enumerate().rev() {
+ self.check_tree_se(x, y, *tree)
+ }
+ }
+ }
+
+ fn check_tree_nw(&mut self, x: usize, y: usize, tree: TreeHeight) {
+ if tree > self.max_north[x] {
+ self.trees_seen.insert((x, y));
+ self.max_north[x] = tree;
+ }
+
+ if tree > self.max_west[y] {
+ self.trees_seen.insert((x, y));
+ self.max_west[y] = tree;
+ }
+ }
+
+ fn check_tree_se(&mut self, x: usize, y: usize, tree: TreeHeight) {
+ if tree > self.max_south[x] {
+ self.trees_seen.insert((x, y));
+ self.max_south[x] = tree;
+ }
+
+ if tree > self.max_east[y] {
+ self.trees_seen.insert((x, y));
+ self.max_east[y] = tree;
+ }
+ }
+}
+
+impl Display for Visibility {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ for y in 0..self.capacity_y {
+ let mut l = String::new();
+ for x in 0..self.capacity_x {
+ l.push(if self.trees_seen.contains(&(x, y)) {
+ 'X'
+ } else {
+ ' '
+ })
+ }
+ writeln!(f, "{}", l)?;
+ }
+ Ok(())
+ }
+}
+
+#[derive(Debug, Default)]
+struct ScenicScore {
+ tree: Tree,
+ north: Vec<Tree>,
+ east: Vec<Tree>,
+ south: Vec<Tree>,
+ west: Vec<Tree>,
+}
+
+impl ScenicScore {
+ fn new(
+ tree: Tree,
+ north: Vec<Tree>,
+ east: Vec<Tree>,
+ south: Vec<Tree>,
+ west: Vec<Tree>,
+ ) -> Self {
+ Self {
+ tree,
+ north,
+ east,
+ south,
+ west,
+ }
+ }
+ fn score(&self) -> usize {
+ self.north.len() * self.east.len() * self.south.len() * self.west.len()
+ }
+}
+
+impl Display for ScenicScore {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ for t in &self.north {
+ for _ in 0..self.tree.x {
+ write!(f, "x")?;
+ }
+ write!(f, "{t:>width$}", width = self.tree.x)?;
+ for _ in self.tree.x..self.east.len() {
+ write!(f, "x")?;
+ }
+ writeln!(f)?;
+ }
+
+ let line = self
+ .west
+ .iter()
+ .chain([&self.tree])
+ .chain(self.east.iter())
+ .map(|t| t.to_string())
+ .collect::<Vec<_>>()
+ .join("");
+
+ let offset = self.tree.x - self.west.len();
+ writeln!(f, "{:offset$}", line)?;
+
+ for t in &self.south {
+ for _ in 0..self.tree.x {
+ write!(f, "x")?;
+ }
+ writeln!(f, "{t:>width$}", width = self.tree.x)?;
+ for _ in self.tree.x..self.east.len() {
+ write!(f, "x")?;
+ }
+ }
+
+ Ok(())
+ }
+}
+
+impl PartialEq for ScenicScore {
+ fn eq(&self, other: &Self) -> bool {
+ self.score() == other.score()
+ }
+}
+
+impl Eq for ScenicScore {}
+
+impl PartialOrd for ScenicScore {
+ fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+ self.score().partial_cmp(&other.score())
+ }
+}
+
+impl Ord for ScenicScore {
+ fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+ self.score().cmp(&other.score())
+ }
+}
+
+#[derive(Debug, Default, Clone, Copy)]
+struct Tree {
+ x: usize,
+ y: usize,
+ height: TreeHeight,
+}
+
+impl Tree {
+ fn new(x: usize, y: usize, height: TreeHeight) -> Self {
+ Self { x, y, height }
+ }
+
+ fn scenic_score(&self, tree_grid: &[Vec<Tree>]) -> ScenicScore {
+ let mut north: Vec<Tree> = Vec::new();
+ for t in tree_grid.iter().map(|v| v[self.x]).take(self.y).rev() {
+ north.push(t);
+ if t >= *self {
+ break;
+ }
+ }
+ north.reverse();
+
+ let mut east: Vec<Tree> = Vec::new();
+ for t in tree_grid[self.y].iter().skip(self.x + 1) {
+ east.push(*t);
+ if t >= self {
+ break;
+ }
+ }
+
+ let mut south: Vec<Tree> = Vec::new();
+ for t in tree_grid.iter().map(|v| v[self.x]).skip(self.y + 1) {
+ south.push(t);
+ if t >= *self {
+ break;
+ }
+ }
+
+ let mut west: Vec<Tree> = Vec::new();
+ for t in tree_grid[self.y].iter().take(self.x).rev() {
+ west.push(*t);
+ if t >= self {
+ break;
+ }
+ }
+ west.reverse();
+
+ ScenicScore::new(*self, north, east, south, west)
+ }
+}
+
+impl PartialEq for Tree {
+ fn eq(&self, other: &Self) -> bool {
+ self.height == other.height
+ }
+}
+
+impl PartialOrd for Tree {
+ fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+ self.height.partial_cmp(&other.height)
+ }
+}
+
+impl Display for Tree {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "{}", self.height)
+ }
+}
+
+pub struct Day8;
+
+impl Problem for Day8 {
+ const DAY: u8 = 8;
+
+ const INPUT: &'static str = include_str!("../input/day_8.txt");
+}
+
+impl Solution for Day8 {
+ type Answer1 = usize;
+
+ type Answer2 = usize;
+
+ fn part_1(input: &str) -> Result<Self::Answer1, anyhow::Error> {
+ let tree_grid: Vec<Vec<TreeHeight>> = input
+ .lines()
+ .map(|l| {
+ l.chars()
+ .filter_map(|c| c.to_digit(10).map(|d| d as TreeHeight))
+ .collect()
+ })
+ .collect();
+
+ let mut visibility = Visibility::with_capacity(tree_grid[0].len(), tree_grid.len());
+ visibility.check_trees(tree_grid);
+ Ok(visibility.trees_seen.len())
+ }
+
+ fn part_2(input: &str) -> Result<Self::Answer2, anyhow::Error> {
+ let tree_grid: Vec<Vec<Tree>> = input
+ .lines()
+ .enumerate()
+ .map(|(y, l)| {
+ l.chars()
+ .enumerate()
+ .filter_map(|(x, c)| c.to_digit(10).map(|d| Tree::new(x, y, d as TreeHeight)))
+ .collect()
+ })
+ .collect();
+
+ let trees = tree_grid.iter().flatten();
+
+ let max_score = trees
+ .map(|t| t.scenic_score(&tree_grid))
+ .max()
+ .ok_or_else(|| anyhow::anyhow!("Failed to find max"))?;
+
+ Ok(max_score.score())
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ const TEST_INPUT: &str = indoc::indoc! {r#"
+ 30373
+ 25512
+ 65332
+ 33549
+ 35390
+ "#};
+
+ #[test]
+ fn test_part_1_example() -> Result<()> {
+ Ok(assert_eq!(21, Day8::part_1(TEST_INPUT)?))
+ }
+
+ #[test]
+ fn test_part_2_example() -> Result<()> {
+ println!("{TEST_INPUT}");
+ Ok(assert_eq!(8, Day8::part_2(TEST_INPUT)?))
+ }
+}