use std::collections::HashMap;
use crate::graph::capability::{Directed, StableNode};
use crate::graph::Graph;
pub struct Dominators<N> {
pairs: Vec<(N, N)>,
idx: usize,
}
pub fn dominators<'r, G>(graph: &'r G, start: G::NodeIx) -> Dominators<G::NodeIx>
where
G: Graph + Directed<'r> + StableNode + ?Sized,
{
let _ = graph.node(start);
unsafe { dominators_unchecked(graph, start) }
}
pub unsafe fn dominators_unchecked<'r, G>(graph: &'r G, start: G::NodeIx) -> Dominators<G::NodeIx>
where
G: Graph + Directed<'r> + StableNode + ?Sized,
{
let mut visited = std::collections::HashSet::new();
let mut rpo = Vec::new();
let mut stack: Vec<(G::NodeIx, bool)> = vec![(start, false)];
visited.insert(start);
while let Some((node, expanded)) = stack.last_mut() {
if *expanded {
rpo.push(*node);
stack.pop();
} else {
*expanded = true;
let node = *node;
let succs: Vec<G::NodeIx> =
unsafe { graph.neighbor_indices_from_unchecked(node) }.collect();
for succ in succs.into_iter().rev() {
if visited.insert(succ) {
stack.push((succ, false));
}
}
}
}
rpo.reverse();
if rpo.is_empty() {
return Dominators {
pairs: Vec::new(),
idx: 0,
};
}
let rpo_index: HashMap<G::NodeIx, usize> =
rpo.iter().enumerate().map(|(i, &n)| (n, i)).collect();
let n = rpo.len();
let mut idom: Vec<Option<usize>> = vec![None; n];
idom[0] = Some(0);
let mut changed = true;
while changed {
changed = false;
for i in 1..n {
let node = rpo[i];
let preds: Vec<G::NodeIx> =
unsafe { graph.neighbor_indices_to_unchecked(node) }.collect();
let mut new_idom: Option<usize> = None;
for pred in preds {
if let Some(&pred_rpo) = rpo_index.get(&pred) {
if idom[pred_rpo].is_some() {
new_idom = Some(match new_idom {
None => pred_rpo,
Some(current) => intersect(&idom, current, pred_rpo),
});
}
}
}
if new_idom != idom[i] {
idom[i] = new_idom;
changed = true;
}
}
}
let mut pairs = Vec::new();
for (i, &dom) in idom.iter().enumerate() {
if let Some(d) = dom {
pairs.push((rpo[i], rpo[d]));
}
}
Dominators { pairs, idx: 0 }
}
impl<N: Copy> Iterator for Dominators<N> {
type Item = (N, N);
fn next(&mut self) -> Option<(N, N)> {
if self.idx < self.pairs.len() {
let pair = self.pairs[self.idx];
self.idx += 1;
Some(pair)
} else {
None
}
}
}
fn intersect(idom: &[Option<usize>], mut a: usize, mut b: usize) -> usize {
while a != b {
while a > b {
a = idom[a].unwrap();
}
while b > a {
b = idom[b].unwrap();
}
}
a
}
#[cfg(test)]
mod tests {
use super::*;
use crate::BTreeGraph;
use crate::graph::Graph;
#[test]
fn dominators_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 dom: HashMap<_, _> = dominators(&g, 0).collect();
assert_eq!(dom[&0], 0); assert_eq!(dom[&1], 0);
assert_eq!(dom[&2], 1);
}
#[test]
fn dominators_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 dom: HashMap<_, _> = dominators(&g, 0).collect();
assert_eq!(dom[&0], 0);
assert_eq!(dom[&1], 0);
assert_eq!(dom[&2], 0);
assert_eq!(dom[&3], 0); }
#[test]
fn dominators_unreachable() {
let mut g = BTreeGraph::<u32, &str>::default();
g.insert_node(0).unwrap();
g.insert_node(1).unwrap();
let dom: HashMap<_, _> = dominators(&g, 0).collect();
assert_eq!(dom.len(), 1);
assert_eq!(dom[&0], 0);
}
}