use std::collections::BinaryHeap;
use std::hash::Hash;
use hashbrown::hash_map::Entry::{Occupied, Vacant};
use hashbrown::HashMap;
use petgraph::algo::Measure;
use petgraph::visit::{ControlFlow, EdgeRef, IntoEdges, VisitMap, Visitable};
use crate::min_scored::MinScored;
use super::try_control;
macro_rules! try_control_with_result {
($e:expr, $p:stmt) => {
try_control_with_result!($e, $p, ());
};
($e:expr, $p:stmt, $q:stmt) => {
match $e {
x => {
if x.should_break() {
return Ok(x);
} else if x.should_prune() {
$p
} else {
$q
}
}
}
};
}
#[derive(Copy, Clone, Debug)]
pub enum DijkstraEvent<N, E, K> {
Discover(N, K),
ExamineEdge(N, N, E),
EdgeRelaxed(N, N, E),
EdgeNotRelaxed(N, N, E),
Finish(N),
}
pub fn dijkstra_search<G, I, F, K, E, H, C>(
graph: G,
starts: I,
mut edge_cost: F,
mut visitor: H,
) -> Result<C, E>
where
G: IntoEdges + Visitable,
G::NodeId: Eq + Hash,
I: IntoIterator<Item = G::NodeId>,
F: FnMut(G::EdgeRef) -> Result<K, E>,
K: Measure + Copy,
H: FnMut(DijkstraEvent<G::NodeId, &G::EdgeWeight, K>) -> C,
C: ControlFlow,
{
let visited = &mut graph.visit_map();
for start in starts {
try_control!(
dijkstra_visitor(graph, start, &mut edge_cost, &mut visitor, visited),
unreachable!()
);
}
Ok(C::continuing())
}
pub fn dijkstra_visitor<G, F, K, E, V, C>(
graph: G,
start: G::NodeId,
mut edge_cost: F,
mut visitor: V,
visited: &mut G::Map,
) -> Result<C, E>
where
G: IntoEdges + Visitable,
G::NodeId: Eq + Hash,
F: FnMut(G::EdgeRef) -> Result<K, E>,
K: Measure + Copy,
V: FnMut(DijkstraEvent<G::NodeId, &G::EdgeWeight, K>) -> C,
C: ControlFlow,
{
if visited.is_visited(&start) {
return Ok(C::continuing());
}
let mut scores = HashMap::new();
let mut visit_next = BinaryHeap::new();
let zero_score = K::default();
scores.insert(start, zero_score);
visit_next.push(MinScored(zero_score, start));
while let Some(MinScored(node_score, node)) = visit_next.pop() {
if !visited.visit(node) {
continue;
}
try_control_with_result!(visitor(DijkstraEvent::Discover(node, node_score)), continue);
for edge in graph.edges(node) {
let next = edge.target();
try_control_with_result!(
visitor(DijkstraEvent::ExamineEdge(node, next, edge.weight())),
continue
);
if visited.is_visited(&next) {
continue;
}
let cost = edge_cost(edge)?;
let next_score = node_score + cost;
match scores.entry(next) {
Occupied(ent) => {
if next_score < *ent.get() {
try_control_with_result!(
visitor(DijkstraEvent::EdgeRelaxed(node, next, edge.weight())),
continue
);
*ent.into_mut() = next_score;
visit_next.push(MinScored(next_score, next));
} else {
try_control_with_result!(
visitor(DijkstraEvent::EdgeNotRelaxed(node, next, edge.weight())),
continue
);
}
}
Vacant(ent) => {
try_control_with_result!(
visitor(DijkstraEvent::EdgeRelaxed(node, next, edge.weight())),
continue
);
ent.insert(next_score);
visit_next.push(MinScored(next_score, next));
}
}
}
try_control_with_result!(
visitor(DijkstraEvent::Finish(node)),
panic!("Pruning on the `DijkstraEvent::Finish` is not supported!")
);
}
Ok(C::continuing())
}