use crate::utils::graph::{NodeId, Successors};
pub fn has_cycle<G: Successors>(graph: &G, start: NodeId) -> bool {
let node_count = graph.node_count();
if start.index() >= node_count {
return false;
}
let mut visited = vec![false; node_count];
let mut in_stack = vec![false; node_count];
has_cycle_dfs(graph, start, &mut visited, &mut in_stack)
}
fn has_cycle_dfs<G: Successors>(
graph: &G,
node: NodeId,
visited: &mut [bool],
in_stack: &mut [bool],
) -> bool {
let idx = node.index();
if in_stack[idx] {
return true;
}
if visited[idx] {
return false;
}
visited[idx] = true;
in_stack[idx] = true;
for successor in graph.successors(node) {
if has_cycle_dfs(graph, successor, visited, in_stack) {
return true;
}
}
in_stack[idx] = false;
false
}
pub fn find_cycle<G: Successors>(graph: &G, start: NodeId) -> Option<Vec<NodeId>> {
let node_count = graph.node_count();
if start.index() >= node_count {
return None;
}
let mut visited = vec![false; node_count];
let mut in_stack = vec![false; node_count];
let mut path = Vec::new();
find_cycle_dfs(graph, start, &mut visited, &mut in_stack, &mut path)
}
fn find_cycle_dfs<G: Successors>(
graph: &G,
node: NodeId,
visited: &mut [bool],
in_stack: &mut [bool],
path: &mut Vec<NodeId>,
) -> Option<Vec<NodeId>> {
let idx = node.index();
if in_stack[idx] {
let cycle_start_pos = path.iter().position(|&n| n == node)?;
let mut cycle: Vec<NodeId> = path[cycle_start_pos..].to_vec();
cycle.push(node); return Some(cycle);
}
if visited[idx] {
return None;
}
visited[idx] = true;
in_stack[idx] = true;
path.push(node);
for successor in graph.successors(node) {
if let Some(cycle) = find_cycle_dfs(graph, successor, visited, in_stack, path) {
return Some(cycle);
}
}
path.pop();
in_stack[idx] = false;
None
}
#[cfg(test)]
mod tests {
use crate::utils::graph::{
algorithms::cycles::{find_cycle, has_cycle},
DirectedGraph, NodeId,
};
fn create_linear_graph() -> DirectedGraph<'static, &'static str, ()> {
let mut graph = DirectedGraph::new();
let a = graph.add_node("A");
let b = graph.add_node("B");
let c = graph.add_node("C");
graph.add_edge(a, b, ()).unwrap();
graph.add_edge(b, c, ()).unwrap();
graph
}
fn create_diamond_graph() -> DirectedGraph<'static, &'static str, ()> {
let mut graph = DirectedGraph::new();
let a = graph.add_node("A");
let b = graph.add_node("B");
let c = graph.add_node("C");
let d = graph.add_node("D");
graph.add_edge(a, b, ()).unwrap();
graph.add_edge(a, c, ()).unwrap();
graph.add_edge(b, d, ()).unwrap();
graph.add_edge(c, d, ()).unwrap();
graph
}
fn create_simple_cycle() -> DirectedGraph<'static, &'static str, ()> {
let mut graph = DirectedGraph::new();
let a = graph.add_node("A");
let b = graph.add_node("B");
let c = graph.add_node("C");
graph.add_edge(a, b, ()).unwrap();
graph.add_edge(b, c, ()).unwrap();
graph.add_edge(c, a, ()).unwrap();
graph
}
fn create_self_loop() -> DirectedGraph<'static, &'static str, ()> {
let mut graph = DirectedGraph::new();
let a = graph.add_node("A");
graph.add_edge(a, a, ()).unwrap();
graph
}
fn create_complex_with_cycle() -> DirectedGraph<'static, &'static str, ()> {
let mut graph = DirectedGraph::new();
let a = graph.add_node("A");
let b = graph.add_node("B");
let c = graph.add_node("C");
let d = graph.add_node("D");
graph.add_edge(a, b, ()).unwrap();
graph.add_edge(b, c, ()).unwrap();
graph.add_edge(c, d, ()).unwrap();
graph.add_edge(d, b, ()).unwrap();
graph
}
#[test]
fn test_has_cycle_linear() {
let graph = create_linear_graph();
assert!(!has_cycle(&graph, NodeId::new(0)));
}
#[test]
fn test_has_cycle_diamond() {
let graph = create_diamond_graph();
assert!(!has_cycle(&graph, NodeId::new(0)));
}
#[test]
fn test_has_cycle_simple_cycle() {
let graph = create_simple_cycle();
assert!(has_cycle(&graph, NodeId::new(0)));
}
#[test]
fn test_has_cycle_self_loop() {
let graph = create_self_loop();
assert!(has_cycle(&graph, NodeId::new(0)));
}
#[test]
fn test_has_cycle_complex() {
let graph = create_complex_with_cycle();
assert!(has_cycle(&graph, NodeId::new(0)));
}
#[test]
fn test_has_cycle_single_node_no_loop() {
let mut graph: DirectedGraph<(), ()> = DirectedGraph::new();
let a = graph.add_node(());
assert!(!has_cycle(&graph, a));
}
#[test]
fn test_has_cycle_two_separate_cycles() {
let mut graph: DirectedGraph<&str, ()> = DirectedGraph::new();
let a = graph.add_node("A");
let b = graph.add_node("B");
let c = graph.add_node("C");
let d = graph.add_node("D");
graph.add_edge(a, b, ()).unwrap();
graph.add_edge(b, a, ()).unwrap();
graph.add_edge(c, d, ()).unwrap();
graph.add_edge(d, c, ()).unwrap();
assert!(has_cycle(&graph, a));
assert!(has_cycle(&graph, c));
}
#[test]
fn test_find_cycle_linear() {
let graph = create_linear_graph();
assert!(find_cycle(&graph, NodeId::new(0)).is_none());
}
#[test]
fn test_find_cycle_diamond() {
let graph = create_diamond_graph();
assert!(find_cycle(&graph, NodeId::new(0)).is_none());
}
#[test]
fn test_find_cycle_simple_cycle() {
let graph = create_simple_cycle();
let cycle = find_cycle(&graph, NodeId::new(0));
assert!(cycle.is_some());
let cycle = cycle.unwrap();
assert_eq!(cycle.first(), cycle.last());
assert!(cycle.len() >= 3);
}
#[test]
fn test_find_cycle_self_loop() {
let graph = create_self_loop();
let cycle = find_cycle(&graph, NodeId::new(0));
assert!(cycle.is_some());
let cycle = cycle.unwrap();
assert_eq!(cycle.len(), 2);
assert_eq!(cycle[0], cycle[1]);
}
#[test]
fn test_find_cycle_complex() {
let graph = create_complex_with_cycle();
let cycle = find_cycle(&graph, NodeId::new(0));
assert!(cycle.is_some());
let cycle = cycle.unwrap();
assert_eq!(cycle.first(), cycle.last());
}
#[test]
fn test_find_cycle_returns_valid_path() {
let graph = create_simple_cycle();
let cycle = find_cycle(&graph, NodeId::new(0)).unwrap();
for i in 0..cycle.len() - 1 {
let current = cycle[i];
let next = cycle[i + 1];
let successors: Vec<NodeId> = graph.successors(current).collect();
assert!(
successors.contains(&next),
"Invalid cycle path: no edge from {:?} to {:?}",
current,
next
);
}
}
#[test]
fn test_find_cycle_disconnected_cycle() {
let mut graph: DirectedGraph<&str, ()> = DirectedGraph::new();
let entry = graph.add_node("Entry");
let a = graph.add_node("A");
let b = graph.add_node("B");
let c = graph.add_node("C");
graph.add_edge(entry, a, ()).unwrap();
graph.add_edge(a, b, ()).unwrap();
graph.add_edge(b, c, ()).unwrap();
graph.add_edge(c, a, ()).unwrap();
let cycle = find_cycle(&graph, entry);
assert!(cycle.is_some());
}
}