use std::collections::{BTreeSet, HashMap};
use super::GraphError;
use crate::pipeline::PipelineNode;
use crate::pipeline_edge::PipelineEdge;
fn links(nodes: &[&PipelineNode], edges: &[PipelineEdge]) -> Vec<(usize, usize)> {
let mut index = HashMap::new();
for (i, node) in nodes.iter().enumerate() {
index.entry(node.id.as_str()).or_insert(i);
}
edges
.iter()
.filter_map(|e| {
Some((
*index.get(e.source.as_str())?,
*index.get(e.target.as_str())?,
))
})
.collect()
}
pub(super) fn topological<'a>(
nodes: &[&'a PipelineNode],
edges: &[PipelineEdge],
) -> Result<Vec<&'a PipelineNode>, GraphError> {
let links = links(nodes, edges);
let mut indegree = vec![0usize; nodes.len()];
for &(_, target) in &links {
indegree[target] += 1;
}
let order = kahn(&links, &mut indegree);
if order.len() < nodes.len() {
let path = cycle_path(&links, &indegree);
let path = path.into_iter().map(|i| nodes[i].id.clone()).collect();
return Err(GraphError::Cycle { path });
}
Ok(order.into_iter().map(|i| nodes[i]).collect())
}
fn kahn(links: &[(usize, usize)], indegree: &mut [usize]) -> Vec<usize> {
let mut ready: BTreeSet<usize> = (0..indegree.len()).filter(|&i| indegree[i] == 0).collect();
let mut order = Vec::with_capacity(indegree.len());
while let Some(next) = ready.pop_first() {
order.push(next);
for &(_, target) in links.iter().filter(|&&(source, _)| source == next) {
indegree[target] -= 1;
if indegree[target] == 0 {
ready.insert(target);
}
}
}
order
}
fn cycle_path(links: &[(usize, usize)], indegree: &[usize]) -> Vec<usize> {
let start = indegree.iter().position(|&d| d > 0);
let mut walk = vec![start.expect("a cycle leaves nodes unplaced")];
loop {
let here = *walk.last().expect("walk starts non-empty");
let prev = unplaced_predecessor(links, indegree, here);
if let Some(at) = walk.iter().position(|&i| i == prev) {
let mut cycle = walk.split_off(at);
cycle.push(prev);
cycle.reverse();
return cycle;
}
walk.push(prev);
}
}
fn unplaced_predecessor(links: &[(usize, usize)], indegree: &[usize], here: usize) -> usize {
links
.iter()
.find(|&&(source, target)| target == here && indegree[source] > 0)
.map(|&(source, _)| source)
.expect("an unplaced node has an unplaced predecessor")
}