use std::collections::HashSet; #[derive(Debug, Copy, Clone, PartialOrd, PartialEq, Eq, Hash)] enum VisibleDirection { Top, Bottom, Left, Right, } use VisibleDirection::*; #[derive(Debug, Default)] struct Tree { height: usize, visible: HashSet, } impl Tree { fn new(height: usize) -> Self { Tree { height, ..Tree::default() } } fn is_visible(&self) -> bool { !self.visible.is_empty() } fn set_visible(&mut self, dir: VisibleDirection) -> &mut Self { self.visible.insert(dir); self } fn set_all_visible(&mut self) -> &mut Self { self.set_visible(Top) .set_visible(Bottom) .set_visible(Left) .set_visible(Right) } } // ---------------------------------------------------------------------------- #[derive(Debug)] struct Grid { width: usize, vec: Vec, } impl Grid { pub fn new(width: usize) -> Self { Grid { width, vec: Vec::new(), } } // Convert x,y coordinate into linear array index pub fn xy_idx(&self, x: usize, y: usize) -> usize { (y * self.width) + x } /// Convert linear array index to x,y coordinate pub fn idx_xy(&self, idx: usize) -> (usize, usize) { (idx % self.width, idx / self.width) } pub fn get(&self, idx: usize) -> Option<&T> { self.vec.get(idx) } pub fn get_mut(&mut self, idx: usize) -> Option<&mut T> { self.vec.get_mut(idx) } pub fn row_first_idx(&self, row: usize) -> usize { let idx = row * self.width; if idx < self.vec.len() { idx } else { self.vec.len() } } pub fn row_last_idx(&self, row: usize) -> usize { if (row + 1) > self.num_rows() { return self.vec.len(); } self.row_first_idx(row + 1) - 1 } pub fn num_rows(&self) -> usize { self.vec.len() / self.width } pub fn num_cols(&self) -> usize { self.width } pub fn get_row(&mut self, row_num: usize) -> &mut [T] { let start = self.row_first_idx(row_num); let end = self.row_last_idx(row_num); &mut self.vec[start..=end] } pub fn get_row_indexes(&self, row_num: usize) -> Vec { let start = self.row_first_idx(row_num); let end = self.row_last_idx(row_num); (start..=end).collect() } pub fn get_column_indexes(&self, col_num: usize) -> Vec { let mut indexes = Vec::new(); if col_num >= self.num_cols() { panic!( "Asked for column {}, there are {} columns", col_num, self.num_cols() ); } for r in 0..self.num_rows() { let idx = self.width * r + col_num; indexes.push(idx); } indexes } } impl Grid { pub fn from_file_str(file_str: &'static str) -> Grid { let lines: Vec<&str> = file_str.lines().collect(); let width = lines[0].len(); let mut grid: Grid = Grid::new(width); for line in lines { let mut row: Vec = line .chars() .map(|ch| Tree::new(ch.to_digit(10).unwrap() as usize)) .collect(); grid.vec.append(&mut row); } grid } fn mark_outer_trees_visible(&mut self) -> &mut Self { fn set_row_visible(row: &mut [Tree]) { row.iter_mut().for_each(|tree| { tree.set_all_visible(); }) } // Set top/bottom rows as visible set_row_visible(self.get_row(0)); set_row_visible(self.get_row(self.num_rows() - 1)); // Set left/right cols as visible self.get_column_indexes(0).into_iter().for_each(|id| { self.vec[id].set_all_visible(); }); self.get_column_indexes(self.num_cols() - 1) .into_iter() .for_each(|id| { self.vec[id].set_all_visible(); }); self } fn mark_visible(&mut self, dir: VisibleDirection) -> &mut Self { let indexes: Vec> = match dir { Top | Bottom => { // Skip outer columns, as those are already marked visible (1..(self.num_cols() - 1)) .map(|c| self.get_column_indexes(c)) .map(|column| { if dir == Bottom { column.into_iter().rev().collect() } else { column } }) .collect() } Left | Right => { // Skip first and last rows, as those are already marked visible (1..(self.num_rows() - 1)) .map(|r| self.get_row_indexes(r)) .map(|row| { if dir == Right { row.into_iter().rev().collect() } else { row } }) .collect() } }; for row_or_col in indexes { let mut tallest = 0usize; for idx in row_or_col { let tree = self.get_mut(idx).unwrap(); if tallest < tree.height { tree.set_visible(dir); tallest = tree.height; } } } self } pub fn mark_visible_trees(&mut self) { self.mark_outer_trees_visible() .mark_visible(Top) .mark_visible(Right) .mark_visible(Bottom) .mark_visible(Left); } pub fn get_visible_trees(&self) -> usize { self.vec .iter() .filter(|tree| tree.is_visible()) .collect::>() .len() } fn get_surrounding_trees( &self, reference: usize, ) -> (Vec, Vec, Vec, Vec) { let (x, y) = self.idx_xy(reference); let mut top = self.get_column_indexes(x); // col[0..=x] let bottom = top.split_off(y + 1); // col[(x+1)..] let mut left = self.get_row_indexes(y); // row[0..=y] let right = left.split_off(x + 1); // row[(y+1)..] // Remove the index for the current tree let _ = top.pop(); let _ = left.pop(); // Reverse the top and left so the perspective is from the reference top.reverse(); left.reverse(); (top, right, bottom, left) } fn get_viewing_distances(&self, reference: usize) -> [usize; 4] { let (t, r, b, l) = self.get_surrounding_trees(reference); let ref_tree_height = self.get(reference).unwrap().height; [t, r, b, l] .iter() .map(|search| { let mut count = 0usize; for i in search { let height = match self.get(*i) { Some(h) => h.height, None => 0, }; count += 1; if ref_tree_height <= height { break; } } count }) .collect::>() .try_into() .unwrap() } fn get_scenic_score(&self, reference: usize) -> usize { let [t, r, b, l] = self.get_viewing_distances(reference); t * r * b * l } pub fn get_max_scenic_score(&self) -> usize { self.vec .iter() .enumerate() .map(|(idx, _)| idx) .map(|idx| self.get_scenic_score(idx)) .max() .unwrap() } } // ---------------------------------------------------------------------------- fn main() { let file_str = include_str!("input.txt"); let mut grid = Grid::from_file_str(file_str); grid.mark_visible_trees(); let visible_num = grid.get_visible_trees(); let scenic_score = grid.get_max_scenic_score(); println!("Part 1: Number of visible trees: {}", visible_num); println!("Part 2: Max scenic score: {}", scenic_score); } #[cfg(test)] mod tests { use super::*; fn get_data() -> &'static str { include_str!("test-input.txt") } #[test] fn test_row_first_index() { let grid = Grid::from_file_str(get_data()); assert_eq!(grid.row_first_idx(1), 5); assert_eq!(grid.row_first_idx(0), 0); assert_eq!(grid.row_first_idx(2), 10); } #[test] fn test_row_last_index() { let grid = Grid::from_file_str(get_data()); assert_eq!(grid.row_last_idx(0), 4); assert_eq!(grid.row_last_idx(1), 9); } #[test] fn test_get_column_indexes() { let grid = Grid::from_file_str(get_data()); assert_eq!(grid.width, 5); assert_eq!(grid.get_column_indexes(0), vec![0, 5, 10, 15, 20]); assert_eq!(grid.get_column_indexes(1), vec![1, 6, 11, 16, 21]); assert_eq!(grid.get_column_indexes(4), vec![4, 9, 14, 19, 24]); } #[test] fn test_outer_visible_trees() { let mut grid = Grid::from_file_str(get_data()); grid.mark_outer_trees_visible(); assert_eq!(grid.get_visible_trees(), 16usize); } #[test] fn test_visible_trees() { let mut grid = Grid::from_file_str(get_data()); grid.mark_visible_trees(); let visible = [(1usize, 1usize), (2, 1), (1, 2), (4, 3), (2, 3)]; for (x, y) in visible { let idx = grid.xy_idx(x, y); assert!( grid.vec[idx].is_visible(), "Tree {}({},{}) should be visible: {:#?}", idx, x, y, grid.vec[idx] ); } assert_eq!(grid.get_visible_trees(), 21usize); } #[test] fn test_get_surrounding_trees() { let grid = Grid::from_file_str(get_data()); let (t, r, b, l) = grid.get_surrounding_trees(7); assert_eq!(t, vec![2]); assert_eq!(r, vec![8, 9]); assert_eq!(b, vec![12, 17, 22]); assert_eq!(l, vec![6, 5]); } #[test] fn test_get_viewing_distances() { let grid = Grid::from_file_str(get_data()); assert_eq!(grid.get_viewing_distances(7), [1, 2, 2, 1]); assert_eq!(grid.get_viewing_distances(17), [2, 2, 1, 2]); } #[test] fn test_get_scenic_score() { let grid = Grid::from_file_str(get_data()); assert_eq!(grid.get_scenic_score(7), 4); assert_eq!(grid.get_scenic_score(17), 8); assert_eq!(grid.get_max_scenic_score(), 8); } }