use std::collections::HashSet;
use crate::graph::capability::{Bigraph, Directed, StableEdge, StableNode};
use crate::graph::Graph;
pub struct DagTransitiveReduction<'r, G: ?Sized, E> {
graph: &'r G,
edges: E,
}
pub fn dag_transitive_reduction<'r, G>(graph: &'r G) -> DagTransitiveReduction<'r, G, <G as crate::graph::GraphOperation<'r>>::EdgeIndices>
where
G: Graph + Directed<'r> + Bigraph + StableEdge + ?Sized,
{
DagTransitiveReduction {
graph,
edges: <_ as crate::graph::GraphOperation<'_>>::edge_indices(graph),
}
}
impl<'r, G> Iterator for DagTransitiveReduction<'r, G, <G as crate::graph::GraphOperation<'r>>::EdgeIndices>
where
G: Graph + Directed<'r> + Bigraph + StableNode + ?Sized,
{
type Item = G::EdgeIx;
fn next(&mut self) -> Option<G::EdgeIx> {
loop {
let eix = self.edges.next()?;
let tail = unsafe { self.graph.edge_tail_index_unchecked(eix) };
let head = unsafe { self.graph.edge_head_index_unchecked(eix) };
let mut reachable_via_other = false;
for other_eix in unsafe { <G as crate::graph::GraphOperation<'_>>::edge_indices_from_unchecked(self.graph, tail) } {
if other_eix == eix {
continue;
}
let other_head = unsafe { self.graph.edge_head_index_unchecked(other_eix) };
if unsafe { can_reach(self.graph, other_head, head) } {
reachable_via_other = true;
break;
}
}
if !reachable_via_other {
return Some(eix);
}
}
}
}
pub struct DagTransitiveClosure<'r, G: ?Sized, N> {
graph: &'r G,
nodes: Vec<N>,
node_idx: usize,
dfs_stack: Vec<N>,
dfs_visited: HashSet<N>,
current_source: Option<N>,
pending: Vec<(N, N)>,
}
pub fn dag_transitive_closure<'r, G>(graph: &'r G) -> DagTransitiveClosure<'r, G, G::NodeIx>
where
G: Graph + Directed<'r> + StableNode + ?Sized,
{
let nodes: Vec<G::NodeIx> = <_ as crate::graph::GraphOperation<'_>>::node_indices(graph).collect();
DagTransitiveClosure {
graph,
nodes,
node_idx: 0,
dfs_stack: Vec::new(),
dfs_visited: HashSet::new(),
current_source: None,
pending: Vec::new(),
}
}
impl<'r, G> Iterator for DagTransitiveClosure<'r, G, G::NodeIx>
where
G: Graph + Directed<'r> + StableNode + ?Sized,
{
type Item = (G::NodeIx, G::NodeIx);
fn next(&mut self) -> Option<(G::NodeIx, G::NodeIx)> {
loop {
if let Some(pair) = self.pending.pop() {
return Some(pair);
}
if let Some(current) = self.dfs_stack.pop() {
let source = self.current_source.unwrap();
let succs: Vec<G::NodeIx> =
unsafe { self.graph.neighbor_indices_from_unchecked(current) }.collect();
for succ in succs {
if self.dfs_visited.insert(succ) {
self.dfs_stack.push(succ);
self.pending.push((source, succ));
}
}
continue;
}
if self.node_idx >= self.nodes.len() {
return None;
}
let source = self.nodes[self.node_idx];
self.node_idx += 1;
self.current_source = Some(source);
self.dfs_visited.clear();
self.dfs_visited.insert(source);
self.dfs_stack.clear();
self.dfs_stack.push(source);
}
}
}
unsafe fn can_reach<'r, G>(graph: &'r G, source: G::NodeIx, target: G::NodeIx) -> bool
where
G: Graph + Directed<'r> + StableNode + ?Sized,
{
if source == target {
return true;
}
let mut visited = HashSet::new();
let mut stack = vec![source];
visited.insert(source);
while let Some(node) = stack.pop() {
let succs: Vec<G::NodeIx> = graph.neighbor_indices_from_unchecked(node).collect();
for succ in succs {
if succ == target {
return true;
}
if visited.insert(succ) {
stack.push(succ);
}
}
}
false
}
#[cfg(test)]
mod tests {
use super::*;
use crate::BTreeGraph;
#[test]
fn transitive_reduction_diamond() {
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.insert_edge("0->3", [0, 3]).unwrap();
let keep: HashSet<_> = dag_transitive_reduction(&g).collect();
assert_eq!(keep.len(), 4);
assert!(keep.contains(&"0->1"));
assert!(keep.contains(&"0->2"));
assert!(keep.contains(&"1->3"));
assert!(keep.contains(&"2->3"));
assert!(!keep.contains(&"0->3"));
}
#[test]
fn transitive_reduction_linear() {
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("1->2", [1, 2]).unwrap();
g.insert_edge("2->3", [2, 3]).unwrap();
g.insert_edge("0->2", [0, 2]).unwrap();
let keep: HashSet<_> = dag_transitive_reduction(&g).collect();
assert_eq!(keep.len(), 3);
assert!(!keep.contains(&"0->2"));
}
#[test]
fn transitive_closure_diamond() {
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();
let closure: HashSet<_> = dag_transitive_closure(&g).collect();
assert!(closure.contains(&(0, 1)));
assert!(closure.contains(&(0, 2)));
assert!(closure.contains(&(1, 3)));
assert!(closure.contains(&(2, 3)));
assert!(closure.contains(&(0, 3)));
assert!(!closure.contains(&(3, 0)));
assert!(!closure.contains(&(1, 0)));
}
#[test]
fn transitive_closure_linear() {
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("1->2", [1, 2]).unwrap();
let closure: HashSet<_> = dag_transitive_closure(&g).collect();
assert!(closure.contains(&(0, 1)));
assert!(closure.contains(&(1, 2)));
assert!(closure.contains(&(0, 2))); assert_eq!(closure.len(), 3);
}
}