use std::{
borrow::Borrow,
collections::{HashMap, HashSet},
hash::Hash,
};
use crate::dep_graph::DepNode;
pub type SccId = usize;
pub enum SccEvent<Node = DepNode> {
Next {
this: SccId,
out_edges: HashSet<SccId>,
marked_for_main: bool,
},
Member {
node: Node,
},
}
#[derive(Default)]
pub struct TarjanSccResult<Node = DepNode> {
next_sccid: SccId,
fully_explored: HashMap<Node, SccId>,
topological_sort: Vec<SccEvent<Node>>,
}
impl<Node> TarjanSccResult<Node> {
pub fn new() -> Self {
Self {
next_sccid: 1, fully_explored: HashMap::new(),
topological_sort: vec![],
}
}
pub fn scc_of(&self, vertex: &Node) -> Option<SccId>
where
Node: Eq + Hash,
{
self.fully_explored.get(vertex).cloned()
}
pub fn explore<Roots>(
&mut self,
roots: Roots,
graph: &Graph<Node>,
wbg_in_main: &HashSet<Node>,
) -> HashSet<SccId>
where
Roots: IntoIterator,
<Roots as IntoIterator>::Item: Borrow<Node>,
Node: Eq + Hash + Copy,
{
let mut state = TarjanState::new(self, graph, wbg_in_main);
for root in roots {
state.connect(*root.borrow());
}
state.explore_root.out_edges
}
pub fn into_topsort(self) -> Vec<SccEvent<Node>> {
self.topological_sort
}
}
enum WorkItem<Node> {
Explore {
vertex: Node,
parent: Option<Node>,
},
Pop {
vertex: Node,
parent: Option<Node>,
link: SccId,
},
}
struct VertexState {
lowlink: SccId,
out_edges: HashSet<SccId>,
marked_for_main: bool,
}
impl VertexState {
fn new(link: SccId) -> Self {
Self {
lowlink: link,
out_edges: HashSet::new(),
marked_for_main: false,
}
}
}
type Graph<Node> = HashMap<Node, HashSet<Node>>;
struct TarjanState<'r, Node> {
result: &'r mut TarjanSccResult<Node>,
graph: &'r Graph<Node>,
wbg_in_main: &'r HashSet<Node>,
explore_root: VertexState,
stack: Vec<Node>,
vertex_state: HashMap<Node, VertexState>,
work: Vec<WorkItem<Node>>,
}
impl<'r, Node: Copy + Eq + Hash> TarjanState<'r, Node> {
fn new(
result: &'r mut TarjanSccResult<Node>,
graph: &'r Graph<Node>,
wbg_in_main: &'r HashSet<Node>,
) -> Self {
Self {
result,
graph,
explore_root: VertexState::new(0),
wbg_in_main,
stack: vec![],
vertex_state: HashMap::new(),
work: vec![],
}
}
fn should_explore(&self, vertex: &Node) -> bool {
self.result.scc_of(vertex).is_none() && !self.vertex_state.contains_key(vertex)
}
fn connect(&mut self, vertex: Node) {
if let Some(scc) = self.result.scc_of(&vertex) {
self.explore_root.out_edges.insert(scc);
return;
}
debug_assert!(self.work.is_empty() && self.vertex_state.is_empty());
self.work.push(WorkItem::Explore {
vertex,
parent: None,
});
self.drain_work();
}
fn drain_work(&mut self) {
while let Some(work) = self.work.pop() {
match work {
WorkItem::Explore { vertex, parent } => {
if !self.should_explore(&vertex) {
continue;
}
self.explore(vertex, parent);
}
WorkItem::Pop {
vertex,
parent,
link,
} => {
self.pop(vertex, parent, link);
}
}
}
}
fn explore(&mut self, vertex: Node, parent: Option<Node>) {
let scc = self.result.next_sccid;
self.result.next_sccid += 1;
self.work.push(WorkItem::Pop {
vertex,
parent,
link: scc,
});
let mut v_state = VertexState {
lowlink: scc,
out_edges: HashSet::new(),
marked_for_main: false,
};
let v_lowlink = &mut v_state.lowlink;
self.stack.push(vertex);
let neighbors = self.graph.get(&vertex);
if let Some(deps) = neighbors {
if !self.wbg_in_main.is_disjoint(deps) {
v_state.marked_for_main = true;
}
}
for &neighbor in neighbors.into_iter().flatten() {
if let Some(&VertexState { lowlink, .. }) = self.vertex_state.get(&neighbor) {
*v_lowlink = lowlink.min(*v_lowlink);
} else if let Some(to_scc) = self.result.scc_of(&neighbor) {
v_state.out_edges.insert(to_scc);
} else {
self.work.push(WorkItem::Explore {
vertex: neighbor,
parent: Some(vertex),
});
}
}
let _old = self.vertex_state.insert(vertex, v_state);
debug_assert!(_old.is_none());
}
fn pop(&mut self, vertex: Node, parent: Option<Node>, link: SccId) {
let lowlink = self.vertex_state.get_mut(&vertex).unwrap().lowlink;
let defines_new_scc = link == lowlink;
if defines_new_scc {
let mut out_edges = HashSet::new();
let mut marked_for_main = false;
while let Some(w) = self.stack.pop() {
let stack_state = self.vertex_state.remove(&w).unwrap();
out_edges.extend(stack_state.out_edges);
marked_for_main |= stack_state.marked_for_main;
self.result
.topological_sort
.push(SccEvent::Member { node: w });
self.result.fully_explored.insert(w, lowlink);
if w == vertex {
break;
}
}
self.result.topological_sort.push(SccEvent::Next {
this: lowlink,
out_edges,
marked_for_main,
});
}
let par_state = if let Some(parent) = parent {
self.vertex_state.get_mut(&parent).unwrap()
} else {
&mut self.explore_root
};
if defines_new_scc {
par_state.out_edges.insert(lowlink);
debug_assert!(par_state.lowlink < lowlink);
} else {
par_state.lowlink = lowlink.min(par_state.lowlink);
}
}
}
#[cfg(test)]
mod tests {
use std::collections::{HashMap, HashSet};
use crate::graph_utils::tarjan_scc::SccEvent;
use super::TarjanSccResult;
#[test]
fn test_small_graph() {
let mut searcher = TarjanSccResult::new();
let graph = HashMap::from([
(0, HashSet::from([1, 2])),
(1, HashSet::from([2])),
(2, HashSet::from([1, 5])),
(3, HashSet::from([5])),
(5, HashSet::from([6])),
(6, HashSet::from([7])),
(7, HashSet::from([5])),
(99, HashSet::from([100])),
(100, HashSet::from([99])),
]);
let roots = searcher.explore([0, 3], &graph, &HashSet::new());
assert_eq!(
HashSet::from([0, 1, 2, 3, 5, 6, 7]),
searcher.fully_explored.keys().cloned().collect(),
);
let mut scc_graph = HashMap::new();
let it = searcher.into_topsort();
let mut it = it.iter();
'graph: loop {
let mut component = vec![];
loop {
match it.next() {
None => break 'graph,
Some(&SccEvent::Member { node: _node }) => {
component.push(_node);
}
Some(&SccEvent::Next {
this,
ref out_edges,
..
}) => {
component.sort();
scc_graph.insert(component, (this, out_edges.clone()));
break;
}
}
}
}
let root_scc = scc_graph
.get(&[0][..])
.expect("to find a component for [0]");
let inner_12 = scc_graph
.get(&[1, 2][..])
.expect("to find a component for [1, 2]");
let inner_567 = scc_graph
.get(&[5, 6, 7][..])
.expect("to find a component for [5, 6, 7]");
let inner_3 = scc_graph
.get(&[3][..])
.expect("to find a component for [3]");
assert!(root_scc.1.contains(&inner_12.0));
assert!(!root_scc.1.contains(&inner_567.0));
assert!(inner_12.1.contains(&inner_567.0));
assert!(inner_3.1.contains(&inner_567.0));
assert_eq!(HashSet::from([root_scc.0, inner_3.0]), roots);
}
}