From 755fbbc53d8d5c7c20b88b6d0e078e23f496ebce Mon Sep 17 00:00:00 2001 From: Keenan Tims Date: Mon, 16 Dec 2024 00:57:19 -0800 Subject: [PATCH] day16: refactor, optimize split path recording and best cost functions for big gainz use i32 instead of i64 positions to shrink data structures for some gainz --- src/day16.rs | 164 +++++++++++++++++++++++++++++++++++---------------- 1 file changed, 114 insertions(+), 50 deletions(-) diff --git a/src/day16.rs b/src/day16.rs index d7441cd..72843c3 100644 --- a/src/day16.rs +++ b/src/day16.rs @@ -1,9 +1,7 @@ use aoc_runner_derive::aoc; use grid::{AsCoord2d, Coord2d, Grid}; -use itertools::Itertools; use std::{ collections::{BinaryHeap, HashMap}, - i64, str::FromStr, usize, }; @@ -17,7 +15,7 @@ enum FacingDirection { } impl FacingDirection { - fn ofs(&self) -> (i64, i64) { + fn ofs(&self) -> (i32, i32) { match self { FacingDirection::East => (1, 0), FacingDirection::South => (0, 1), @@ -37,9 +35,8 @@ impl FacingDirection { #[derive(Clone, Eq, PartialEq, Debug)] struct State { cost: usize, - position: Coord2d, + position: (i32, i32), facing: FacingDirection, - path: Vec, } impl Ord for State { @@ -58,62 +55,57 @@ impl PartialOrd for State { } } +#[derive(Clone, Eq, PartialEq, Debug)] +struct PathState { + state: State, + path: Vec<(i32, i32)>, +} + +impl Ord for PathState { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.state.cmp(&other.state) + } +} +impl PartialOrd for PathState { + fn partial_cmp(&self, other: &Self) -> Option { + self.state.partial_cmp(&other.state) + } +} + struct Maze { map: Grid, - paths: HashMap>>, } impl FromStr for Maze { type Err = Box; fn from_str(s: &str) -> Result { let map: Grid = s.parse()?; - let paths = HashMap::new(); - Ok(Self { map, paths }) + Ok(Self { map }) } } impl Maze { - fn dijkstra(&mut self) -> usize { - let start = self.map.find(&b'S').expect("can't find start").to_coord(); - let finish = self.map.find(&b'E').expect("can't find finish").to_coord(); + fn dijkstra(&self) -> usize { + let (start_x, start_y) = self.map.find(&b'S').expect("can't find start"); + let start = (start_x as i32, start_y as i32); + + let (finish_x, finish_y) = self.map.find(&b'E').expect("can't find finish"); + let finish = (finish_x as i32, finish_y as i32); let mut distances = HashMap::new(); - let mut queue = BinaryHeap::with_capacity(self.map.data.len()); - let mut best_cost = usize::MAX; + let mut queue = BinaryHeap::new(); distances.insert((start, FacingDirection::East), 0); queue.push(State { cost: 0, position: start, facing: FacingDirection::East, - path: Vec::new(), }); - while let Some(State { - cost, - position, - facing, - path, - }) = queue.pop() - { - let mut new_path = path.clone(); - new_path.push(position); - + while let Some(State { cost, position, facing }) = queue.pop() { if position == finish { - if RECORD_PATHS { - if cost < best_cost { - best_cost = cost - } - if !self.paths.contains_key(&cost) { - self.paths.insert(cost, vec![new_path.clone()]); - } else { - self.paths.get_mut(&cost).unwrap().push(new_path.clone()); - } - continue; - } else { - return cost; - } + return cost; } if distances.get(&(position, facing)).is_some_and(|v| cost > *v) { @@ -123,29 +115,103 @@ impl Maze { for (new_dir, new_position, new_cost) in facing .reachable() .iter() - .map(|dir| (dir, &position + dir.ofs())) + .map(|dir| (dir, (position.0 + dir.ofs().0, position.1 + dir.ofs().1))) .filter(|(_, pos)| self.map.get(pos).is_some_and(|c| *c != b'#')) .map(|(dir, pos)| (dir, pos, if *dir == facing { cost + 1 } else { cost + 1001 })) { if distances .get(&(new_position, *new_dir)) - .is_none_or(|best_cost| new_cost <= *best_cost) + .is_none_or(|best_cost| new_cost < *best_cost) { queue.push(State { cost: new_cost, position: new_position, facing: *new_dir, + }); + distances.insert((new_position, *new_dir), new_cost); + } + } + } + usize::MAX + } + fn path_dijkstra(&mut self) -> (usize, Vec>) { + let (start_x, start_y) = self.map.find(&b'S').expect("can't find start"); + let start = (start_x.try_into().unwrap(), start_y.try_into().unwrap()); + let (finish_x, finish_y) = self.map.find(&b'E').expect("can't find finish"); + let finish = (finish_x.try_into().unwrap(), finish_y.try_into().unwrap()); + + let mut distances = HashMap::new(); + let mut queue = BinaryHeap::with_capacity(self.map.data.len()); + let mut best_paths = Vec::new(); + let mut best_cost = usize::MAX; + + distances.insert((start, FacingDirection::East), 0); + queue.push(PathState { + state: State { + cost: 0, + position: start, + facing: FacingDirection::East, + }, + path: Vec::with_capacity(100), + }); + + while let Some(PathState { state, path }) = queue.pop() { + let mut new_path = path.clone(); + new_path.push(state.position); + + if state.position == finish { + if state.cost < best_cost { + best_paths.clear(); + best_paths.push(new_path); + best_cost = state.cost + } else if state.cost == best_cost { + best_paths.push(new_path); + } + continue; + } + + if distances + .get(&(state.position, state.facing)) + .is_some_and(|v| state.cost > *v) + { + continue; + } + + for (new_dir, new_position, new_cost) in state + .facing + .reachable() + .iter() + .map(|dir| (dir, (state.position.0 + dir.ofs().0, state.position.1 + dir.ofs().1))) + .filter(|(_, pos)| self.map.get(pos).is_some_and(|c| *c != b'#')) + .map(|(dir, pos)| { + ( + dir, + pos, + if *dir == state.facing { + state.cost + 1 + } else { + state.cost + 1001 + }, + ) + }) + { + if distances + .get(&(new_position, *new_dir)) + .is_none_or(|best_cost| new_cost <= *best_cost) + { + queue.push(PathState { + state: State { + cost: new_cost, + position: new_position, + facing: *new_dir, + }, path: new_path.clone(), }); distances.insert((new_position, *new_dir), new_cost); } } } - if best_cost == usize::MAX || !RECORD_PATHS { - panic!("no path found"); - } else { - return best_cost; - } + return (best_cost, best_paths); } } @@ -155,19 +221,17 @@ fn parse(input: &str) -> Maze { #[aoc(day16, part1)] pub fn part1(input: &str) -> usize { - let mut maze = parse(input); - maze.dijkstra::() + let maze = parse(input); + maze.dijkstra() } #[aoc(day16, part2)] pub fn part2(input: &str) -> usize { let mut maze = parse(input); - let best_cost = maze.dijkstra::(); - let best_paths = maze.paths.get(&best_cost).unwrap(); - let best_path_tiles = best_paths.into_iter().flatten().collect_vec(); + let best_paths = maze.path_dijkstra(); let mut path_map = maze.map.clone(); - for tile in best_path_tiles { + for tile in best_paths.1.into_iter().flatten() { path_map.set(&tile, b'O'); } path_map.count(&b'O')