Get cycle if toposort fails

This commit is contained in:
d0cd 2022-11-14 17:03:37 -08:00
parent 9fbcdfc9f3
commit 8b8c7b5c0a

View File

@ -27,9 +27,9 @@ impl Node for Symbol {}
/// Errors in graph operations.
#[derive(Debug)]
pub enum GraphError {
/// A cycle was detected in the graph.
CycleDetected,
pub enum GraphError<N: Node> {
/// An error that is emitted when a cycle is detected in the graph. Contains the path of cycle.
CycleDetected(Vec<N>),
}
/// A directed graph.
@ -68,21 +68,40 @@ impl<N: Node> DiGraph<N> {
}
/// Detects if there is a cycle in the graph.
pub fn topological_sort(&self) -> Result<IndexSet<N>, GraphError> {
pub fn topological_sort(&self) -> Result<IndexSet<N>, GraphError<N>> {
// The set of nodes that do not need to be visited again.
let mut finished: IndexSet<N> = IndexSet::with_capacity(self.nodes.len());
// The set of nodes that are on the path to the current node in the search.
let mut discovered: IndexSet<N> = IndexSet::new();
// Perform a depth-first search of the graph, starting from `node`, for each node in the graph.
for node in self.nodes.iter() {
// If the node has not been explored, explore it.
if !discovered.contains(node) //TODO: Can we remove this check? Any nodes added to discovered in `contains_cycle_from` are removed.
&& !finished.contains(node)
&& self.contains_cycle_from(*node, &mut discovered, &mut finished)
{
// A cycle was found.
return Err(GraphError::CycleDetected);
if !finished.contains(node) {
// The set of nodes that are on the path to the current node in the search.
let mut discovered: IndexSet<N> = IndexSet::new();
// Check if there is a cycle in the graph starting from `node`.
if self.contains_cycle_from(*node, &mut discovered, &mut finished) {
let path = match discovered.pop() {
// TODO: Should this error more silently?
None => unreachable!("If `contains_cycle_from` returns `true`, `discovered` is not empty."),
Some(node) => {
let mut path = vec![node];
// Backtrack through the discovered nodes to find the cycle.
while let Some(next) = discovered.pop() {
// Add the node to the path.
path.push(next);
// If the node is the same as the first node in the path, we have found the cycle.
if next == node {
break;
}
}
// Reverse the path to get the cycle in the correct order.
path.reverse();
path
}
};
// A cycle was detected. Return the path of the cycle.
return Err(GraphError::CycleDetected(path));
}
}
}
// No cycle was found. Return the set of nodes in topological order.
@ -100,6 +119,9 @@ impl<N: Node> DiGraph<N> {
for child in children.iter() {
// If the node already been discovered, there is a cycle.
if discovered.contains(child) {
// Insert the child node into the set of discovered nodes; this is used to reconstruct the cycle.
// Note that this case is always hit when there is a cycle.
discovered.insert(*child);
return true;
}
// If the node has not been explored, explore it.
@ -110,7 +132,7 @@ impl<N: Node> DiGraph<N> {
}
// Remove the node from the set of discovered nodes.
discovered.remove(&node);
discovered.pop();
// Add the node to the set of finished nodes.
finished.insert(node);