/** * * Copied and substantially modified from petgraph's scored.rs and algo/astar.rs. * * Copyright (c) 2015 **/ use std::collections::hash_map::Entry::{Occupied, Vacant}; use std::collections::{BinaryHeap, HashMap}; use std::hash::Hash; use petgraph::algo::Measure; use petgraph::visit::{EdgeRef, GraphBase, IntoEdges, Visitable}; use std::cmp::Ordering; #[derive(Copy, Clone, Debug)] pub struct MinScored(pub K, pub T); impl PartialEq for MinScored { #[inline] fn eq(&self, other: &MinScored) -> bool { self.cmp(other) == Ordering::Equal } } impl Eq for MinScored {} impl PartialOrd for MinScored { #[inline] fn partial_cmp(&self, other: &MinScored) -> Option { Some(self.cmp(other)) } } impl Ord for MinScored { #[inline] fn cmp(&self, other: &MinScored) -> Ordering { let a = &self.0; let b = &other.0; if a == b { Ordering::Equal } else if a < b { Ordering::Greater } else if a > b { Ordering::Less } else if a.ne(a) && b.ne(b) { // these are the NaN cases Ordering::Equal } else if a.ne(a) { // Order NaN less, so that it is last in the MinScore order Ordering::Less } else { Ordering::Greater } } } pub struct PathTracker where G: GraphBase, G::NodeId: Eq + Hash, { came_from: HashMap, } impl PathTracker where G: GraphBase, G::NodeId: Eq + Hash, { fn new() -> PathTracker { PathTracker { came_from: HashMap::new(), } } fn set_predecessor(&mut self, node: G::NodeId, previous: G::NodeId) { self.came_from.insert(node, previous); } pub fn reconstruct_path_to(&self, last: G::NodeId) -> Vec { let mut path = vec![last]; let mut current = last; while let Some(&previous) = self.came_from.get(¤t) { path.push(previous); current = previous; } path.reverse(); path } } pub trait AstarStrategy where G: IntoEdges + Visitable, K: Measure + Copy, G::NodeId: Eq + Hash, { fn is_goal(&mut self, node: G::NodeId, tracker: &PathTracker) -> bool; fn edge_cost(&mut self, edge: G::EdgeRef) -> Option; fn estimate_cost(&mut self, node: G::NodeId) -> K; } pub fn astar( graph: G, start: G::NodeId, strategy: &mut impl AstarStrategy, ) -> Option<(K, Vec)> where G: IntoEdges + Visitable, G::NodeId: Eq + Hash, K: Measure + Copy, { let mut visit_next = BinaryHeap::new(); let mut scores = HashMap::new(); // g-values, cost to reach the node let mut estimate_scores = HashMap::new(); // f-values, cost to reach + estimate cost to goal let mut path_tracker = PathTracker::::new(); let zero_score = K::default(); scores.insert(start, zero_score); visit_next.push(MinScored(strategy.estimate_cost(start), start)); while let Some(MinScored(estimate_score, node)) = visit_next.pop() { if strategy.is_goal(node, &path_tracker) { let path = path_tracker.reconstruct_path_to(node); let cost = scores[&node]; return Some((cost, path)); } // This lookup can be unwrapped without fear of panic since the node was // necessarily scored before adding it to `visit_next`. let node_score = scores[&node]; match estimate_scores.entry(node) { Occupied(mut entry) => { // If the node has already been visited with an equal or lower score than // now, then we do not need to re-visit it. if *entry.get() <= estimate_score { continue; } entry.insert(estimate_score); } Vacant(entry) => { entry.insert(estimate_score); } } for edge in graph.edges(node) { if let Some(edge_cost) = strategy.edge_cost(edge) { let next = edge.target(); let next_score = node_score + edge_cost; match scores.entry(next) { Occupied(mut entry) => { // No need to add neighbors that we have already reached through a // shorter path than now. if *entry.get() <= next_score { continue; } entry.insert(next_score); } Vacant(entry) => { entry.insert(next_score); } } path_tracker.set_predecessor(next, node); let next_estimate_score = next_score + strategy.estimate_cost(next); visit_next.push(MinScored(next_estimate_score, next)); } } } None }