diff --git a/README.md b/README.md index d0b549c..5fe21d6 100644 --- a/README.md +++ b/README.md @@ -33,10 +33,11 @@ a total budget of 100 milliseconds. | [21](https://adventofcode.com/2022/day/21) | [21.rs](src/bin/21.rs) | 325.30µs | 235.40µs | - | | [22](https://adventofcode.com/2022/day/22) | [22.rs](src/bin/22.rs) | 139.31µs | 127.73µs | - | | [23](https://adventofcode.com/2022/day/23) | [23.rs](src/bin/23.rs) | 15.51ms | 831.17ms | 😔 | -| [24](https://adventofcode.com/2022/day/24) | [24.rs](src/bin/24.rs) | 9.62s | 92.24s | 😔 | +| [24](https://adventofcode.com/2022/day/24) | [24.rs](src/bin/24.rs) | 4.21s | 39.31s | 😔 | | [25](https://adventofcode.com/2022/day/25) | [25.rs](src/bin/25.rs) | 18.00ns | - | - | -(Totally unscientific numbers from a single run, will improve these in the future.) +In the end, days 15, 19, 23 and 24 blew the 100 ms budget by themselves, but ignoring those the total time for the rest +of the 21 days is 114 ms, which is pretty decent. ## Previous years diff --git a/src/bin/24.rs b/src/bin/24.rs index 6413f20..3f4675c 100644 --- a/src/bin/24.rs +++ b/src/bin/24.rs @@ -1,16 +1,13 @@ -use std::cmp::Reverse; use std::iter::once; - -use priority_queue::PriorityQueue; - use aoc::point::CardinalDirection; +use aoc::shortest_path::{Graph, shortest_path_len}; pub fn part_one(input: &str) -> Option { - Basin::parse(input).shortest_path_len(false) + Basin::parse(input, false).shortest_path() } pub fn part_two(input: &str) -> Option { - Basin::parse(input).shortest_path_len(true) + Basin::parse(input, true).shortest_path() } type Minutes = u32; @@ -67,10 +64,11 @@ struct Basin { width: Coordinate, height: Coordinate, blizzards: Vec, + go_back_to_start: bool, } impl Basin { - fn parse(s: &str) -> Self { + fn parse(s: &str, go_back_to_start: bool) -> Self { let lines: Vec<_> = s.lines().filter(|l| l.starts_with('#')).collect(); let mut blizzards = Vec::::new(); @@ -96,6 +94,7 @@ impl Basin { width, height, blizzards, + go_back_to_start, } } @@ -109,33 +108,27 @@ impl Basin { } } - fn shortest_path_len(&self, go_back_to_start: bool) -> Option { + fn shortest_path(&self) -> Option { let start = SearchState { pos: self.start, minutes: 0, state: TripState::Initial, }; + shortest_path_len(self, start).map(|x|x.1) + } +} - let mut queue = PriorityQueue::>::new(); - queue.push(start, Reverse(start.minutes)); - - while let Some((current, _)) = queue.pop() { - if current.pos == self.end && (!go_back_to_start || current.state == TripState::VisitedStartAfterEnd) { - return Some(current.minutes); - } - - for neighbor in self.neighbors(¤t) { - queue.push(neighbor, Reverse(neighbor.minutes)); - } - } +impl Graph for Basin { + type Node = SearchState; - None + fn is_solution(&self, node: &Self::Node) -> bool { + node.pos == self.end && (!self.go_back_to_start || node.state == TripState::VisitedStartAfterEnd) } - fn neighbors(&self, current: &SearchState) -> impl Iterator + '_ { - let current_pos = current.pos; - let current_minutes = current.minutes; - let current_state = current.state; + fn collect_neighbors(&self, node: &Self::Node, neighbors: &mut Vec<(Self::Node, u32)>) { + let current_pos = node.pos; + let current_minutes = node.minutes; + let current_state = node.state; let wait_state = SearchState { pos: current_pos, @@ -155,12 +148,28 @@ impl Basin { Initial => if pos == self.end { VisitedEnd } else { Initial } VisitedEnd => if pos == self.start { VisitedStartAfterEnd } else { VisitedEnd } VisitedStartAfterEnd => VisitedStartAfterEnd - } + }, } }); - move_states.chain(once(wait_state)) + let cs = move_states.chain(once(wait_state)) .filter(|s| self.is_empty(s.pos, s.minutes)) + .map(|n| (n, 1)); + + neighbors.extend(cs); + } + + fn heuristic_distance(&self, node: &Self::Node) -> u32 { + if self.go_back_to_start { + let start_to_end = self.start.manhattan_distance(&self.end); + (match node.state { + TripState::Initial => 2 * start_to_end + node.pos.manhattan_distance(&self.end), + TripState::VisitedEnd => start_to_end + node.pos.manhattan_distance(&self.start), + TripState::VisitedStartAfterEnd => node.pos.manhattan_distance(&self.end), + }) as u32 + } else { + node.pos.manhattan_distance(&self.end) as u32 + } } } diff --git a/src/lib.rs b/src/lib.rs index 7aa787c..1648703 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,6 +8,7 @@ use std::fs; pub mod helpers; pub mod point; +pub mod shortest_path; pub const ANSI_ITALIC: &str = "\x1b[3m"; pub const ANSI_BOLD: &str = "\x1b[1m"; diff --git a/src/shortest_path.rs b/src/shortest_path.rs new file mode 100644 index 0000000..ec62cec --- /dev/null +++ b/src/shortest_path.rs @@ -0,0 +1,47 @@ +use std::cmp::Reverse; +use std::hash::Hash; + +use hashbrown::HashMap; +use priority_queue::PriorityQueue; + +pub trait Graph { + type Node: Eq + Hash + Clone; + + fn is_solution(&self, node: &Self::Node) -> bool; + fn collect_neighbors(&self, node: &Self::Node, neighbors: &mut Vec<(Self::Node, u32)>); + fn heuristic_distance(&self, node: &Self::Node) -> u32; +} + +pub fn shortest_path_len(g: &G, start: G::Node) -> Option<(G::Node, u32)> + where + G: Graph, +{ + let mut g_score = HashMap::::new(); + let mut open_set = PriorityQueue::>::new(); + let mut neighbors = Vec::new(); + + g_score.insert(start.clone(), 0); + let start_distance = g.heuristic_distance(&start); + open_set.push(start, Reverse(start_distance)); + + while let Some((current, _)) = open_set.pop() { + let current_gscore = *g_score.get(¤t).unwrap(); + + if g.is_solution(¤t) { + return Some((current, current_gscore)); + } + + g.collect_neighbors(¤t, &mut neighbors); + for (neighbor, cost) in neighbors.drain(..) { + let tentative_gscore = current_gscore + cost; + if tentative_gscore < g_score.get(&neighbor).copied().unwrap_or(u32::max_value()) { + g_score.insert(neighbor.clone(), tentative_gscore); + + let neighbor_score = tentative_gscore + g.heuristic_distance(&neighbor); + open_set.push(neighbor, Reverse(neighbor_score)); + } + } + } + + None +}