use alloc::{vec, vec::Vec};
use oxgraph_topology::{ElementIndex, ElementSuccessors, TopologyBase};
const UNVISITED: usize = usize::MAX;
struct Induced<'view, G> {
graph: &'view G,
member: &'view [bool],
bound: usize,
}
struct Frame<Id> {
node: Id,
successors: Vec<Id>,
position: usize,
}
struct TarjanScratch<Id> {
order_index: Vec<usize>,
lowlink: Vec<usize>,
on_stack: Vec<bool>,
stack: Vec<Id>,
components: Vec<Vec<Id>>,
counter: usize,
}
fn member_successors<G>(
ctx: &Induced<'_, G>,
node: <G as TopologyBase>::ElementId,
) -> Vec<<G as TopologyBase>::ElementId>
where
G: ElementIndex + ElementSuccessors,
{
ctx.graph
.element_successors(node)
.filter(|&successor| {
let target = ctx.graph.element_index(successor);
target < ctx.bound && ctx.member[target]
})
.collect()
}
fn discover<G>(
ctx: &Induced<'_, G>,
node: <G as TopologyBase>::ElementId,
scratch: &mut TarjanScratch<<G as TopologyBase>::ElementId>,
work: &mut Vec<Frame<<G as TopologyBase>::ElementId>>,
) where
G: ElementIndex + ElementSuccessors,
{
let index = ctx.graph.element_index(node);
scratch.order_index[index] = scratch.counter;
scratch.lowlink[index] = scratch.counter;
scratch.counter += 1;
scratch.on_stack[index] = true;
scratch.stack.push(node);
work.push(Frame {
node,
successors: member_successors(ctx, node),
position: 0,
});
}
fn advance<G>(
ctx: &Induced<'_, G>,
node: <G as TopologyBase>::ElementId,
successor: <G as TopologyBase>::ElementId,
scratch: &mut TarjanScratch<<G as TopologyBase>::ElementId>,
work: &mut Vec<Frame<<G as TopologyBase>::ElementId>>,
) where
G: ElementIndex + ElementSuccessors,
{
let target = ctx.graph.element_index(successor);
if scratch.order_index[target] == UNVISITED {
discover(ctx, successor, scratch, work);
} else if scratch.on_stack[target] {
let node_index = ctx.graph.element_index(node);
if scratch.order_index[target] < scratch.lowlink[node_index] {
scratch.lowlink[node_index] = scratch.order_index[target];
}
}
}
fn pop_component<G>(
ctx: &Induced<'_, G>,
root_index: usize,
scratch: &mut TarjanScratch<<G as TopologyBase>::ElementId>,
) where
G: ElementIndex + ElementSuccessors,
{
let mut component = Vec::new();
while let Some(popped) = scratch.stack.pop() {
let popped_index = ctx.graph.element_index(popped);
scratch.on_stack[popped_index] = false;
component.push(popped);
if popped_index == root_index {
break;
}
}
scratch.components.push(component);
}
fn finish<G>(
ctx: &Induced<'_, G>,
node: <G as TopologyBase>::ElementId,
scratch: &mut TarjanScratch<<G as TopologyBase>::ElementId>,
work: &mut Vec<Frame<<G as TopologyBase>::ElementId>>,
) where
G: ElementIndex + ElementSuccessors,
{
let node_index = ctx.graph.element_index(node);
if scratch.lowlink[node_index] == scratch.order_index[node_index] {
pop_component(ctx, node_index, scratch);
}
work.pop();
if let Some(parent) = work.last() {
let parent_index = ctx.graph.element_index(parent.node);
if scratch.lowlink[node_index] < scratch.lowlink[parent_index] {
scratch.lowlink[parent_index] = scratch.lowlink[node_index];
}
}
}
fn visit<G>(
ctx: &Induced<'_, G>,
start: <G as TopologyBase>::ElementId,
scratch: &mut TarjanScratch<<G as TopologyBase>::ElementId>,
) where
G: ElementIndex + ElementSuccessors,
{
let mut work: Vec<Frame<G::ElementId>> = Vec::new();
discover(ctx, start, scratch, &mut work);
while let Some(top) = work.last_mut() {
let node = top.node;
if top.position < top.successors.len() {
let successor = top.successors[top.position];
top.position += 1;
advance(ctx, node, successor, scratch, &mut work);
} else {
finish(ctx, node, scratch, &mut work);
}
}
}
pub fn strongly_connected_components<G>(
graph: &G,
elements: &[<G as TopologyBase>::ElementId],
) -> Vec<Vec<<G as TopologyBase>::ElementId>>
where
G: ElementIndex + ElementSuccessors,
{
let bound = graph.element_bound();
let mut member = vec![false; bound];
let mut nodes: Vec<G::ElementId> = Vec::new();
for &element in elements {
let index = graph.element_index(element);
if index < bound && !member[index] {
member[index] = true;
nodes.push(element);
}
}
let ctx = Induced {
graph,
member: &member,
bound,
};
let mut scratch = TarjanScratch {
order_index: vec![UNVISITED; bound],
lowlink: vec![0usize; bound],
on_stack: vec![false; bound],
stack: Vec::new(),
components: Vec::new(),
counter: 0,
};
for &start in &nodes {
if scratch.order_index[graph.element_index(start)] == UNVISITED {
visit(&ctx, start, &mut scratch);
}
}
scratch.components
}