use std::collections::HashSet;
use crate::graph::capability::{Directed, StableNode};
use crate::graph::Graph;
pub struct AllSimplePaths<'r, G: ?Sized, N> {
graph: &'r G,
target: N,
min_length: usize,
max_length: usize,
stack: Vec<N>,
visited: HashSet<N>,
successors: Vec<Vec<N>>,
indices: Vec<usize>,
}
pub fn all_simple_paths<'r, G>(
graph: &'r G,
from: G::NodeIx,
to: G::NodeIx,
min_intermediate_nodes: usize,
max_intermediate_nodes: Option<usize>,
) -> AllSimplePaths<'r, G, G::NodeIx>
where
G: Graph + Directed<'r> + StableNode + ?Sized,
{
assert!(Graph::contains_node_index(graph, from));
assert!(Graph::contains_node_index(graph, to));
unsafe {
all_simple_paths_unchecked(
graph,
from,
to,
min_intermediate_nodes,
max_intermediate_nodes,
)
}
}
pub unsafe fn all_simple_paths_unchecked<'r, G>(
graph: &'r G,
from: G::NodeIx,
to: G::NodeIx,
min_intermediate_nodes: usize,
max_intermediate_nodes: Option<usize>,
) -> AllSimplePaths<'r, G, G::NodeIx>
where
G: Graph + Directed<'r> + StableNode + ?Sized,
{
let max_length = max_intermediate_nodes
.map(|m| m + 2) .unwrap_or(usize::MAX);
let min_length = min_intermediate_nodes + 2;
let mut visited = HashSet::new();
visited.insert(from);
let succs: Vec<G::NodeIx> = graph.neighbor_indices_from_unchecked(from).collect();
AllSimplePaths {
graph,
target: to,
min_length,
max_length,
stack: vec![from],
visited,
successors: vec![succs],
indices: vec![0],
}
}
impl<'r, G> Iterator for AllSimplePaths<'r, G, G::NodeIx>
where
G: Graph + Directed<'r> + StableNode + ?Sized,
{
type Item = Vec<G::NodeIx>;
fn next(&mut self) -> Option<Vec<G::NodeIx>> {
loop {
if self.stack.is_empty() {
return None;
}
let depth = self.stack.len() - 1;
if self.indices[depth] >= self.successors[depth].len() {
let node = self.stack.pop().unwrap();
self.visited.remove(&node);
self.successors.pop();
self.indices.pop();
continue;
}
let succ = self.successors[depth][self.indices[depth]];
self.indices[depth] += 1;
if succ == self.target {
let path_len = self.stack.len() + 1;
if path_len >= self.min_length {
let mut path = self.stack.clone();
path.push(self.target);
return Some(path);
}
continue;
}
if self.stack.len() + 1 >= self.max_length {
continue;
}
if !self.visited.insert(succ) {
continue; }
let succ_succs: Vec<G::NodeIx> =
unsafe { self.graph.neighbor_indices_from_unchecked(succ) }.collect();
self.stack.push(succ);
self.successors.push(succ_succs);
self.indices.push(0);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::BTreeGraph;
fn diamond_btree() -> BTreeGraph<u32, &'static str> {
let mut g = BTreeGraph::<_, _>::default();
g.insert_node(0).unwrap();
g.insert_node(1).unwrap();
g.insert_node(2).unwrap();
g.insert_node(3).unwrap();
g.insert_edge("0->1", [0, 1]).unwrap();
g.insert_edge("0->2", [0, 2]).unwrap();
g.insert_edge("1->3", [1, 3]).unwrap();
g.insert_edge("2->3", [2, 3]).unwrap();
g
}
#[test]
fn all_paths_diamond() {
let g = diamond_btree();
let paths: Vec<Vec<u32>> = all_simple_paths(&g, 0, 3, 0, None).collect();
assert_eq!(paths.len(), 2);
let path_sets: HashSet<Vec<u32>> = paths.into_iter().collect();
assert!(path_sets.contains(&vec![0, 1, 3]));
assert!(path_sets.contains(&vec![0, 2, 3]));
}
#[test]
fn all_paths_with_min() {
let g = diamond_btree();
let paths: Vec<Vec<u32>> = all_simple_paths(&g, 0, 3, 1, None).collect();
assert_eq!(paths.len(), 2);
}
#[test]
fn all_paths_with_max() {
let g = diamond_btree();
let paths: Vec<Vec<u32>> = all_simple_paths(&g, 0, 3, 0, Some(0)).collect();
assert_eq!(paths.len(), 0);
}
#[test]
fn all_paths_no_path() {
let g = diamond_btree();
let paths: Vec<Vec<u32>> = all_simple_paths(&g, 3, 0, 0, None).collect();
assert!(paths.is_empty());
}
#[test]
fn all_paths_parallel_edges() {
let mut g = BTreeGraph::<_, _>::default();
g.insert_node(0).unwrap();
g.insert_node(1).unwrap();
g.insert_node(2).unwrap();
g.insert_edge("0->1", [0, 1]).unwrap();
g.insert_edge("0->2", [0, 2]).unwrap();
g.insert_edge("1->2", [1, 2]).unwrap();
let paths: Vec<Vec<u32>> = all_simple_paths(&g, 0, 2, 0, None).collect();
assert_eq!(paths.len(), 2);
let path_sets: HashSet<Vec<u32>> = paths.into_iter().collect();
assert!(path_sets.contains(&vec![0, 2]));
assert!(path_sets.contains(&vec![0, 1, 2]));
}
}