use itertools::Itertools; pub fn day10() { let map = HikingMap::from_str(&crate::input(10)); let all_positions = (0..map.width()).cartesian_product(0..map.height()); let (scores, ratings) = all_positions // only the 0s (trailheads) .filter(|(x, y)| map.height_at(*x, *y) == 0) // (score, rating) for each trailhead .map(|(x, y)| find_trails(&map, x, y)) // (sum of scores, sum of ratings) .fold((0, 0), |acc, e| (acc.0 + e.0, acc.1 + e.1)); println!("Sum of Trailhead Scores: {}", scores); println!("Sum of Trailhead Ratings: {}", ratings); } fn find_trails(map: &HikingMap, x: usize, y: usize) -> (usize, usize) { fn find_trails( map: &HikingMap, x: usize, y: usize, visited: &mut Vec<(usize, usize)>, ) -> (usize, usize) { if map.height_at(x, y) == 9 { let score = if !visited.contains(&(x, y)) { 1 } else { 0 }; visited.push((x, y)); return (score, 1); } Direction::all_variants() .into_iter() // all (nx, ny) that are accessible from (x, y) .filter_map(|direction| map.travel(x, y, direction)) // each sum of (scores, ratings) for each adjacent position .map(|(nx, ny)| find_trails(map, nx, ny, visited)) // sum of (scores, ratings) for this position's adjacent positions .fold((0, 0), |acc, e| (acc.0 + e.0, acc.1 + e.1)) } let mut visited = vec![]; find_trails(map, x, y, &mut visited) } struct HikingMap { map: Vec>, } impl HikingMap { fn from_str(s: &str) -> Self { Self { map: s .lines() .map(|line| { line.chars() .map(|c| c.to_digit(10).unwrap() as u8) .collect() }) .collect(), } } fn travel(&self, x: usize, y: usize, direction: Direction) -> Option<(usize, usize)> { let (nx, ny) = direction.next_position(x, y)?; // valid positions are in-bounds and moving upwards gradually let is_valid = nx < self.width() && ny < self.height() && self.height_at(nx, ny).checked_sub(self.height_at(x, y)) == Some(1); is_valid.then_some((nx, ny)) } fn height_at(&self, x: usize, y: usize) -> u8 { self.map[y][x] } fn height(&self) -> usize { self.map.len() } fn width(&self) -> usize { self.map[0].len() } } #[derive(Copy, Clone)] enum Direction { North, East, South, West, } impl Direction { fn all_variants() -> Vec { vec![Self::North, Self::East, Self::South, Self::West] } fn next_position(&self, x: usize, y: usize) -> Option<(usize, usize)> { match self { Self::North => Some((x, y.checked_sub(1)?)), Self::East => Some((x.checked_add(1)?, y)), Self::South => Some((x, y.checked_add(1)?)), Self::West => Some((x.checked_sub(1)?, y)), } } }