day 16
This commit is contained in:
		
							parent
							
								
									51777c2af8
								
							
						
					
					
						commit
						534ebef096
					
				
							
								
								
									
										6
									
								
								day16/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								day16/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,6 @@ | ||||
| [package] | ||||
| name = "day16" | ||||
| version = "0.1.0" | ||||
| edition = "2021" | ||||
| 
 | ||||
| [dependencies] | ||||
							
								
								
									
										249
									
								
								day16/src/main.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										249
									
								
								day16/src/main.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,249 @@ | ||||
| use std::cmp::Ordering; | ||||
| use std::collections::HashMap; | ||||
| use std::collections::HashSet; | ||||
| use std::collections::BinaryHeap; | ||||
| 
 | ||||
| use std::env; | ||||
| use std::fs; | ||||
| 
 | ||||
| use crate::Dir::{North, South, East, West }; | ||||
| 
 | ||||
| type Coord = (i32, i32); | ||||
| #[derive(Clone, Copy, Eq, PartialEq, Hash, Debug, PartialOrd, Ord)] | ||||
| enum Dir { North, South, East, West } | ||||
| 
 | ||||
| #[derive(Copy, Clone, Eq, PartialEq)] | ||||
| struct State { | ||||
|     cost: i32, | ||||
|     position: Coord, | ||||
|     facing: Dir, | ||||
| } | ||||
| 
 | ||||
| impl Ord for State { | ||||
|     fn cmp(&self, other: &Self) -> Ordering { | ||||
|         // flipped ordering on cost, so that it's a min-heap and not a max-heap
 | ||||
|         other.cost.cmp(&self.cost) | ||||
|             .then_with(|| self.position.cmp(&other.position)) | ||||
|             .then_with(|| self.facing.cmp(&other.facing)) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // `PartialOrd` needs to be implemented as well.
 | ||||
| impl PartialOrd for State { | ||||
|     fn partial_cmp(&self, other: &Self) -> Option<Ordering> { | ||||
|         Some(self.cmp(other)) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| fn step(start: &Coord, d: Dir, steps: i32) -> Coord { | ||||
|     match d { | ||||
|         North => (start.0, start.1 - steps), | ||||
|         South => (start.0, start.1 + steps), | ||||
|         East => (start.0 + steps, start.1), | ||||
|         West => (start.0 - steps, start.1), | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| fn main() { | ||||
|     println!("Hello, AoC day 13!"); | ||||
| 
 | ||||
|     let args: Vec<String> = env::args().collect(); | ||||
|     if args.len() != 2 { | ||||
|         println!("wrong number of arguments!"); | ||||
|         std::process::exit(1); | ||||
|     } | ||||
| 
 | ||||
|     let file_path = &args[1]; | ||||
| 
 | ||||
|     let file = fs::read_to_string(file_path) | ||||
|         .expect("should be able to read the file"); | ||||
| 
 | ||||
|     let mut map = HashMap::new(); | ||||
| 
 | ||||
|     let mut start = (0,0); | ||||
|     let mut exit = (0,0); | ||||
|     let mut y = 0; | ||||
|     for line in file.lines() { | ||||
|         let mut x = 0; | ||||
|         for c in line.chars() { | ||||
|             match c { | ||||
|                 'S' => { start = (x,y); map.insert((x,y), '.'); }, | ||||
|                 'E' => { exit = (x,y); map.insert((x,y), '.'); }, | ||||
|                 _ => { map.insert((x,y), c); } | ||||
|             }; | ||||
|             x += 1; | ||||
|         } | ||||
|         y += 1; | ||||
|     } | ||||
| 
 | ||||
|     let score = solve_maze(&map, start, exit); | ||||
| 
 | ||||
|     println!("the score was {score:?}"); | ||||
| 
 | ||||
|     let distances = assign_distances(&map, start); | ||||
|     println!("found {} distances", distances.len()); | ||||
| 
 | ||||
|     let paths = find_paths(&map, &distances, start, exit); | ||||
| 
 | ||||
|     let mut touched_squares: HashSet<Coord> = HashSet::new(); | ||||
| 
 | ||||
|     for path in paths { | ||||
|         for (location, _facing) in path { | ||||
|             touched_squares.insert(location); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     println!("there were {} squares on the best paths", touched_squares.len()); | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| fn solve_maze(map: &HashMap<Coord, char>, start: Coord, exit: Coord) -> Option<i32> { | ||||
|     let mut heap = BinaryHeap::new(); 
 | ||||
|     let mut distances: HashMap<(Coord, Dir), i32> = HashMap::new(); | ||||
| 
 | ||||
|     heap.push(State { position: start, facing: East, cost: 0}); | ||||
| 
 | ||||
|     while let Some(State { position, facing, cost}) = heap.pop() { | ||||
|         if position == exit { return Some(cost); } | ||||
| 
 | ||||
|         if let Some(previous_cost) = distances.get(&(position, facing)) { | ||||
|             if *previous_cost < cost { continue; } | ||||
|         } | ||||
| 
 | ||||
|         distances.insert((position, facing), cost); | ||||
| 
 | ||||
|         for new_dir in [North, South, East, West] { | ||||
|             let new_spot = step(&position, new_dir, 1); | ||||
|             let mut new_cost = cost + 1; | ||||
|             if facing != new_dir { new_cost = new_cost + 1000} | ||||
| 
 | ||||
|             if let Some(d) = distances.get(&(new_spot, new_dir)) { | ||||
|                 if *d < new_cost { continue; } | ||||
|             } | ||||
| 
 | ||||
|             if map.get(&new_spot) == Some(&'.') { | ||||
|                 heap.push(State { 
 | ||||
|                     position: new_spot, 
 | ||||
|                     facing: new_dir, | ||||
|                     cost: new_cost | ||||
|                 }); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return None; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| fn assign_distances(map: &HashMap<Coord, char>, start: Coord) -> HashMap<(Coord, Dir), i32> { | ||||
|     let mut heap = BinaryHeap::new(); 
 | ||||
|     let mut distances = HashMap::new(); | ||||
| 
 | ||||
|     heap.push(State { position: start, facing: East, cost: 0}); | ||||
| 
 | ||||
|     while let Some(State { position, facing, cost}) = heap.pop() { | ||||
| 
 | ||||
|         if let Some(previous_cost) = distances.get(&(position, facing)) { | ||||
|             if *previous_cost < cost { continue; } | ||||
|         } | ||||
| 
 | ||||
|         distances.insert((position, facing), cost); | ||||
| 
 | ||||
|         // option to rotate in place! very expensive
 | ||||
|         for new_dir in [North, South, East, West] { | ||||
|             let new_cost = cost + 1000; | ||||
|             if let Some(d) = distances.get(&(position, new_dir)) { | ||||
|                 if *d < new_cost { continue; } | ||||
|             } | ||||
|             heap.push(State { 
 | ||||
|                 position: position, 
 | ||||
|                 facing: new_dir, | ||||
|                 cost: new_cost | ||||
|             }); | ||||
|         } | ||||
| 
 | ||||
|         let forward_position = step(&position, facing, 1); | ||||
|         if Some(&'.') == map.get(&forward_position) { | ||||
|             let new_cost = cost + 1; | ||||
|             if let Some(d) = distances.get(&(forward_position, facing)) { | ||||
|                 if *d < new_cost { continue; } | ||||
|             } | ||||
|             heap.push(State { | ||||
|                 position: forward_position, | ||||
|                 facing: facing, | ||||
|                 cost: new_cost, | ||||
|             }) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return distances; | ||||
| } | ||||
| 
 | ||||
| fn find_paths( | ||||
|     map: &HashMap<Coord, char>, 
 | ||||
|     distances: &HashMap<(Coord, Dir), i32>, 
 | ||||
|     start: Coord, 
 | ||||
|     end: Coord | ||||
| ) -> Vec<Vec<((i32, i32), Dir)>> { | ||||
|     let mut paths: Vec<Vec<(Coord, Dir)>> = Vec::new(); | ||||
|     let mut output: Vec<Vec<(Coord, Dir)>> = Vec::new(); | ||||
| 
 | ||||
|     let mut shortest_end_distance = distances.get(&(end, North)).expect("the end has to be reachable"); | ||||
|     for dir in [North, South, East, West] { | ||||
|         if let Some(d) = distances.get(&(end, dir)) { | ||||
|             if d < shortest_end_distance { | ||||
|                 shortest_end_distance = d; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     for dir in [North, South, East, West] { | ||||
|         if let Some(d) = distances.get(&(end, dir)) { | ||||
|             if d == shortest_end_distance { | ||||
|                 paths.push(vec![(end, dir)]); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     while let Some(mut path) = paths.pop() { | ||||
|         // iterate through paths that lead to the end
 | ||||
|         // find all neighbors that can step to the current path head
 | ||||
|         // if the neighbor can step to the current path lead, 
 | ||||
|         if let Some(head) = path.pop() { | ||||
|             if head == (start, East) { | ||||
|                 path.push(head); | ||||
|                 output.push(path); | ||||
|                 continue; | ||||
|             } | ||||
|             let potential_predecessors = state_predecessors(map, head); | ||||
|             let our_distance = distances.get(&head).unwrap(); | ||||
| 
 | ||||
|             for state in potential_predecessors { | ||||
|                 let candidate_distance = distances.get(&state).unwrap(); | ||||
| 
 | ||||
|                 if candidate_distance < our_distance { | ||||
|                     let mut this_path = path.clone(); | ||||
|                     this_path.push(head); | ||||
|                     this_path.push(state); | ||||
|                     paths.push(this_path); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return output; | ||||
| } | ||||
| 
 | ||||
| fn state_predecessors(map: &HashMap<Coord, char>, end: (Coord, Dir)) -> Vec<(Coord, Dir)> { | ||||
|     let mut output = Vec::new(); | ||||
|     let walk = step(&end.0, end.1, -1); // include the possibility that we walked here.
 | ||||
|     if map.get(&walk) == Some(&'.') { output.push((walk, end.1)); } | ||||
| 
 | ||||
|     for old_dir in [North, South, East, West] { | ||||
|         if old_dir == end.1 { continue; } | ||||
| 
 | ||||
|         output.push((end.0, old_dir)); | ||||
|     } | ||||
| 
 | ||||
|     return output; | ||||
| } | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user