449 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			449 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
| use std::{collections::HashMap, error::Error};
 | |
| 
 | |
| use itertools::Itertools;
 | |
| 
 | |
| pub fn process_part1(input: &str) -> usize {
 | |
|     let mut start = (0, 0);
 | |
|     let mut end = (0, 0);
 | |
|     let grid = input
 | |
|         .lines()
 | |
|         .enumerate()
 | |
|         .map(|(yidx, row)| {
 | |
|             row.chars()
 | |
|                 .enumerate()
 | |
|                 .map(|(xidx, chara)| {
 | |
|                     let tile = GridTile::try_from(chara).unwrap();
 | |
|                     match tile {
 | |
|                         GridTile::Wall | GridTile::Path => tile,
 | |
|                         GridTile::Start => {
 | |
|                             start = (xidx, yidx);
 | |
|                             tile
 | |
|                         }
 | |
|                         GridTile::End => {
 | |
|                             end = (xidx, yidx);
 | |
|                             tile
 | |
|                         }
 | |
|                     }
 | |
|                 })
 | |
|                 .collect_vec()
 | |
|         })
 | |
|         .collect_vec();
 | |
|     let mut next_paths = vec![Reindeer {
 | |
|         coords: start,
 | |
|         ..Default::default()
 | |
|     }];
 | |
|     let mut arrived: Vec<Reindeer> = Vec::new();
 | |
|     let mut visited = HashMap::new();
 | |
|     let mut iter = 0;
 | |
|     while !next_paths.is_empty() {
 | |
|         next_paths = next_paths
 | |
|             .iter()
 | |
|             .map(|reindeer| reindeer.get_all_next_paths(&grid))
 | |
|             .collect_vec()
 | |
|             .concat();
 | |
|         for (idx, reindeer) in next_paths.clone().iter().enumerate().rev() {
 | |
|             if let Some(score) = visited.get_mut(&reindeer.coords) {
 | |
|                 if *score <= reindeer.score {
 | |
|                     next_paths.remove(idx);
 | |
|                     continue;
 | |
|                 } else {
 | |
|                     *score = reindeer.score;
 | |
|                 }
 | |
|             } else {
 | |
|                 visited.insert(reindeer.coords, reindeer.score);
 | |
|             }
 | |
|             if reindeer.state == ReindeerState::Arrived {
 | |
|                 let arrived_reindeer = next_paths.remove(idx);
 | |
|                 arrived.push(arrived_reindeer);
 | |
|             }
 | |
|         }
 | |
|         iter += 1;
 | |
|     }
 | |
|     println!("Iterations to goal {iter}");
 | |
|     arrived.iter().map(|reindeer| reindeer.score).min().unwrap()
 | |
| }
 | |
| 
 | |
| pub fn process_part2(input: &str) -> usize {
 | |
|     let mut start = (0, 0);
 | |
|     let mut end = (0, 0);
 | |
|     let grid = input
 | |
|         .lines()
 | |
|         .enumerate()
 | |
|         .map(|(yidx, row)| {
 | |
|             row.chars()
 | |
|                 .enumerate()
 | |
|                 .map(|(xidx, chara)| {
 | |
|                     let tile = GridTile::try_from(chara).unwrap();
 | |
|                     match tile {
 | |
|                         GridTile::Wall | GridTile::Path => tile,
 | |
|                         GridTile::Start => {
 | |
|                             start = (xidx, yidx);
 | |
|                             tile
 | |
|                         }
 | |
|                         GridTile::End => {
 | |
|                             end = (xidx, yidx);
 | |
|                             tile
 | |
|                         }
 | |
|                     }
 | |
|                 })
 | |
|                 .collect_vec()
 | |
|         })
 | |
|         .collect_vec();
 | |
|     let mut smallest_score = usize::MAX;
 | |
|     let mut visited = HashMap::new();
 | |
|     visited.insert((start, Orientation::East), 0);
 | |
|     let mut next_paths = vec![Reindeer {
 | |
|         coords: start,
 | |
|         visited: visited.clone(),
 | |
|         ..Default::default()
 | |
|     }];
 | |
|     let mut arrived: Vec<Reindeer> = Vec::new();
 | |
|     while !next_paths.is_empty() {
 | |
|         next_paths = next_paths
 | |
|             .iter()
 | |
|             .map(|reindeer| reindeer.get_all_next_paths(&grid))
 | |
|             .collect_vec()
 | |
|             .concat();
 | |
|         for (idx, reindeer) in next_paths.clone().iter().enumerate().rev() {
 | |
|             if reindeer.score > smallest_score
 | |
|                 || reindeer
 | |
|                     .visited
 | |
|                     .contains_key(&(reindeer.coords, reindeer.orientation))
 | |
|             {
 | |
|                 next_paths.remove(idx);
 | |
|                 continue;
 | |
|             }
 | |
|             next_paths[idx]
 | |
|                 .visited
 | |
|                 .insert((reindeer.coords, reindeer.orientation), reindeer.score);
 | |
|             if let Some(score) = visited.get_mut(&(reindeer.coords, reindeer.orientation)) {
 | |
|                 if *score < reindeer.score {
 | |
|                     next_paths.remove(idx);
 | |
|                     continue;
 | |
|                 } else {
 | |
|                     *score = reindeer.score;
 | |
|                 }
 | |
|             } else {
 | |
|                 visited.insert((reindeer.coords, reindeer.orientation), reindeer.score);
 | |
|             }
 | |
|             if reindeer.state == ReindeerState::Arrived && reindeer.score <= smallest_score {
 | |
|                 smallest_score = reindeer.score;
 | |
|                 let arrived_reindeer = next_paths.remove(idx);
 | |
|                 arrived.push(arrived_reindeer);
 | |
|             }
 | |
|         }
 | |
|         arrived.retain(|reindeer| reindeer.score == smallest_score);
 | |
|     }
 | |
|     let visited = arrived
 | |
|         .iter()
 | |
|         .map(|reindeer| {
 | |
|             reindeer
 | |
|                 .visited
 | |
|                 .keys()
 | |
|                 .map(|(coords, _)| coords)
 | |
|                 .collect_vec()
 | |
|         })
 | |
|         .collect_vec()
 | |
|         .concat();
 | |
|     log_maze(&grid, &arrived);
 | |
|     visited.iter().unique().count()
 | |
| }
 | |
| 
 | |
| #[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq)]
 | |
| enum Orientation {
 | |
|     #[default]
 | |
|     East,
 | |
|     West,
 | |
|     North,
 | |
|     South,
 | |
| }
 | |
| 
 | |
| impl Orientation {
 | |
|     fn turn_clockwise(&self) -> Self {
 | |
|         match self {
 | |
|             Orientation::East => Self::South,
 | |
|             Orientation::West => Self::North,
 | |
|             Orientation::North => Self::East,
 | |
|             Orientation::South => Self::West,
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     fn turn_counterclockwise(&self) -> Self {
 | |
|         match self {
 | |
|             Orientation::East => Self::North,
 | |
|             Orientation::West => Self::South,
 | |
|             Orientation::North => Self::West,
 | |
|             Orientation::South => Self::East,
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| #[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq)]
 | |
| enum ReindeerState {
 | |
|     #[default]
 | |
|     Going,
 | |
|     Arrived,
 | |
|     Stuck,
 | |
| }
 | |
| 
 | |
| #[derive(Debug, Default, Clone, PartialEq, Eq)]
 | |
| struct Reindeer {
 | |
|     orientation: Orientation,
 | |
|     coords: (usize, usize),
 | |
|     score: usize,
 | |
|     visited: HashMap<((usize, usize), Orientation), usize>,
 | |
|     state: ReindeerState,
 | |
| }
 | |
| 
 | |
| impl Reindeer {
 | |
|     fn get_next(&self, grid: &[Vec<GridTile>]) -> Reindeer {
 | |
|         let tile = match self.orientation {
 | |
|             Orientation::East => grid[self.coords.1][self.coords.0 + 1],
 | |
|             Orientation::West => grid[self.coords.1][self.coords.0 - 1],
 | |
|             Orientation::North => grid[self.coords.1 - 1][self.coords.0],
 | |
|             Orientation::South => grid[self.coords.1 + 1][self.coords.0],
 | |
|         };
 | |
|         let coords = match self.orientation {
 | |
|             Orientation::East => (self.coords.0 + 1, self.coords.1),
 | |
|             Orientation::West => (self.coords.0 - 1, self.coords.1),
 | |
|             Orientation::South => (self.coords.0, self.coords.1 + 1),
 | |
|             Orientation::North => (self.coords.0, self.coords.1 - 1),
 | |
|         };
 | |
|         if tile == GridTile::Wall {
 | |
|             Reindeer {
 | |
|                 state: ReindeerState::Stuck,
 | |
|                 ..self.clone()
 | |
|             }
 | |
|         } else if tile == GridTile::End {
 | |
|             Reindeer {
 | |
|                 score: self.score + 1,
 | |
|                 state: ReindeerState::Arrived,
 | |
|                 coords,
 | |
|                 ..self.clone()
 | |
|             }
 | |
|         } else {
 | |
|             Reindeer {
 | |
|                 score: self.score + 1,
 | |
|                 coords,
 | |
|                 ..self.clone()
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     fn get_right(&self, grid: &[Vec<GridTile>]) -> Reindeer {
 | |
|         let tile = match self.orientation {
 | |
|             Orientation::East => grid[self.coords.1 + 1][self.coords.0],
 | |
|             Orientation::West => grid[self.coords.1 - 1][self.coords.0],
 | |
|             Orientation::North => grid[self.coords.1][self.coords.0 + 1],
 | |
|             Orientation::South => grid[self.coords.1][self.coords.0 - 1],
 | |
|         };
 | |
|         let coords = match self.orientation {
 | |
|             Orientation::East => (self.coords.0, self.coords.1 + 1),
 | |
|             Orientation::West => (self.coords.0, self.coords.1 - 1),
 | |
|             Orientation::South => (self.coords.0 - 1, self.coords.1),
 | |
|             Orientation::North => (self.coords.0 + 1, self.coords.1),
 | |
|         };
 | |
|         if tile == GridTile::Wall {
 | |
|             Reindeer {
 | |
|                 state: ReindeerState::Stuck,
 | |
|                 ..self.clone()
 | |
|             }
 | |
|         } else if tile == GridTile::End {
 | |
|             Reindeer {
 | |
|                 score: self.score + 1001,
 | |
|                 state: ReindeerState::Arrived,
 | |
|                 coords,
 | |
|                 orientation: self.orientation.turn_clockwise(),
 | |
|                 ..self.clone()
 | |
|             }
 | |
|         } else {
 | |
|             Reindeer {
 | |
|                 score: self.score + 1001,
 | |
|                 orientation: self.orientation.turn_clockwise(),
 | |
|                 coords,
 | |
|                 ..self.clone()
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     fn get_left(&self, grid: &[Vec<GridTile>]) -> Reindeer {
 | |
|         let tile = match self.orientation {
 | |
|             Orientation::East => grid[self.coords.1 - 1][self.coords.0],
 | |
|             Orientation::West => grid[self.coords.1 + 1][self.coords.0],
 | |
|             Orientation::North => grid[self.coords.1][self.coords.0 - 1],
 | |
|             Orientation::South => grid[self.coords.1][self.coords.0 + 1],
 | |
|         };
 | |
|         let coords = match self.orientation {
 | |
|             Orientation::East => (self.coords.0, self.coords.1 - 1),
 | |
|             Orientation::West => (self.coords.0, self.coords.1 + 1),
 | |
|             Orientation::South => (self.coords.0 + 1, self.coords.1),
 | |
|             Orientation::North => (self.coords.0 - 1, self.coords.1),
 | |
|         };
 | |
|         if tile == GridTile::Wall {
 | |
|             Reindeer {
 | |
|                 state: ReindeerState::Stuck,
 | |
|                 ..self.clone()
 | |
|             }
 | |
|         } else if tile == GridTile::End {
 | |
|             Reindeer {
 | |
|                 score: self.score + 1001,
 | |
|                 state: ReindeerState::Arrived,
 | |
|                 coords,
 | |
|                 orientation: self.orientation.turn_counterclockwise(),
 | |
|                 ..self.clone()
 | |
|             }
 | |
|         } else {
 | |
|             Reindeer {
 | |
|                 score: self.score + 1001,
 | |
|                 orientation: self.orientation.turn_counterclockwise(),
 | |
|                 coords,
 | |
|                 ..self.clone()
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     fn get_all_next_paths(&self, grid: &[Vec<GridTile>]) -> Vec<Reindeer> {
 | |
|         let next = self.get_next(grid);
 | |
|         let right = self.get_right(grid);
 | |
|         let left = self.get_left(grid);
 | |
|         let mut paths = Vec::new();
 | |
|         if next.state != ReindeerState::Stuck {
 | |
|             paths.push(next);
 | |
|         }
 | |
|         if right.state != ReindeerState::Stuck {
 | |
|             paths.push(right);
 | |
|         }
 | |
|         if left.state != ReindeerState::Stuck {
 | |
|             paths.push(left);
 | |
|         }
 | |
|         paths
 | |
|     }
 | |
| }
 | |
| 
 | |
| #[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq)]
 | |
| enum GridTile {
 | |
|     Wall,
 | |
|     #[default]
 | |
|     Path,
 | |
|     Start,
 | |
|     End,
 | |
| }
 | |
| 
 | |
| impl TryFrom<char> for GridTile {
 | |
|     type Error = Box<dyn Error>;
 | |
| 
 | |
|     fn try_from(value: char) -> std::result::Result<GridTile, Box<dyn Error>> {
 | |
|         match value {
 | |
|             '#' => Ok(Self::Wall),
 | |
|             '.' => Ok(Self::Path),
 | |
|             'S' => Ok(Self::Start),
 | |
|             'E' => Ok(Self::End),
 | |
|             _ => Err(Box::from(format!("{value} is not a valid tile"))),
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| fn log_maze(grid: &[Vec<GridTile>], visited: &Vec<Reindeer>) {
 | |
|     for (yidx, row) in grid.iter().enumerate() {
 | |
|         for (xidx, tile) in row.iter().enumerate() {
 | |
|             let contains = {
 | |
|                 let mut contains = false;
 | |
|                 for reindeer in visited {
 | |
|                     if reindeer
 | |
|                         .visited
 | |
|                         .contains_key(&((xidx, yidx), Orientation::East))
 | |
|                         || reindeer
 | |
|                             .visited
 | |
|                             .contains_key(&((xidx, yidx), Orientation::West))
 | |
|                         || reindeer
 | |
|                             .visited
 | |
|                             .contains_key(&((xidx, yidx), Orientation::North))
 | |
|                         || reindeer
 | |
|                             .visited
 | |
|                             .contains_key(&((xidx, yidx), Orientation::South))
 | |
|                     {
 | |
|                         contains = true;
 | |
|                         break;
 | |
|                     }
 | |
|                 }
 | |
|                 contains
 | |
|             };
 | |
|             if contains && !(*tile == GridTile::Start || *tile == GridTile::End) {
 | |
|                 print!("O");
 | |
|             } else if *tile == GridTile::Wall {
 | |
|                 print!("#");
 | |
|             } else if *tile == GridTile::Path {
 | |
|                 print!(".");
 | |
|             } else if *tile == GridTile::Start {
 | |
|                 print!("S");
 | |
|             } else if *tile == GridTile::End {
 | |
|                 print!("E");
 | |
|             }
 | |
|         }
 | |
|         println!();
 | |
|     }
 | |
| }
 | |
| 
 | |
| #[cfg(test)]
 | |
| mod tests {
 | |
|     use super::*;
 | |
| 
 | |
|     const INPUT_1: &str = "###############
 | |
| #.......#....E#
 | |
| #.#.###.#.###.#
 | |
| #.....#.#...#.#
 | |
| #.###.#####.#.#
 | |
| #.#.#.......#.#
 | |
| #.#.#####.###.#
 | |
| #...........#.#
 | |
| ###.#.#####.#.#
 | |
| #...#.....#.#.#
 | |
| #.#.#.###.#.#.#
 | |
| #.....#...#.#.#
 | |
| #.###.#.#.#.#.#
 | |
| #S..#.....#...#
 | |
| ###############";
 | |
| 
 | |
|     const INPUT_2: &str = "#################
 | |
| #...#...#...#..E#
 | |
| #.#.#.#.#.#.#.#.#
 | |
| #.#.#.#...#...#.#
 | |
| #.#.#.#.###.#.#.#
 | |
| #...#.#.#.....#.#
 | |
| #.#.#.#.#.#####.#
 | |
| #.#...#.#.#.....#
 | |
| #.#.#####.#.###.#
 | |
| #.#.#.......#...#
 | |
| #.#.###.#####.###
 | |
| #.#.#...#.....#.#
 | |
| #.#.#.#####.###.#
 | |
| #.#.#.........#.#
 | |
| #.#.#.#########.#
 | |
| #S#.............#
 | |
| #################";
 | |
| 
 | |
|     #[test]
 | |
|     fn part1_1() {
 | |
|         let result = process_part1(INPUT_1);
 | |
|         assert_eq!(result, 7036);
 | |
|     }
 | |
| 
 | |
|     #[test]
 | |
|     fn part1_2() {
 | |
|         let result = process_part1(INPUT_2);
 | |
|         assert_eq!(result, 11048);
 | |
|     }
 | |
| 
 | |
|     #[test]
 | |
|     fn part2_1() {
 | |
|         let result = process_part2(INPUT_1);
 | |
|         assert_eq!(result, 45);
 | |
|     }
 | |
| 
 | |
|     #[test]
 | |
|     fn part2_2() {
 | |
|         let result = process_part2(INPUT_2);
 | |
|         assert_eq!(result, 64);
 | |
|     }
 | |
| }
 |