use std::collections::HashSet;
use super::super::graph_store::GraphStore;
pub struct CycleDetector {
pub max_length: usize,
pub max_cycles: usize,
}
impl Default for CycleDetector {
fn default() -> Self {
Self {
max_length: 10,
max_cycles: 100,
}
}
}
#[derive(Debug, Clone)]
pub struct Cycle {
pub nodes: Vec<String>,
pub length: usize,
}
#[derive(Debug, Clone)]
pub struct CyclesResult {
pub cycles: Vec<Cycle>,
pub limit_reached: bool,
}
impl CycleDetector {
pub fn new() -> Self {
Self::default()
}
pub fn max_length(mut self, max: usize) -> Self {
self.max_length = max;
self
}
pub fn max_cycles(mut self, max: usize) -> Self {
self.max_cycles = max;
self
}
pub fn find(&self, graph: &GraphStore) -> CyclesResult {
let nodes: Vec<String> = graph.iter_nodes().map(|n| n.id.clone()).collect();
let mut cycles: Vec<Cycle> = Vec::new();
let mut visited_global: HashSet<String> = HashSet::new();
for start in &nodes {
if cycles.len() >= self.max_cycles {
return CyclesResult {
cycles,
limit_reached: true,
};
}
let mut stack: Vec<(String, Vec<String>)> = vec![(start.clone(), vec![start.clone()])];
let mut visited_in_path: HashSet<String> = HashSet::new();
visited_in_path.insert(start.clone());
while let Some((current, path)) = stack.pop() {
if path.len() > self.max_length {
continue;
}
for (_, neighbor, _) in graph.outgoing_edges(¤t) {
if neighbor == *start && path.len() > 1 {
let mut cycle_nodes = path.clone();
cycle_nodes.push(start.clone());
if !Self::is_duplicate_cycle(&cycles, &cycle_nodes) {
cycles.push(Cycle {
length: cycle_nodes.len() - 1,
nodes: cycle_nodes,
});
if cycles.len() >= self.max_cycles {
return CyclesResult {
cycles,
limit_reached: true,
};
}
}
} else if !visited_in_path.contains(&neighbor)
&& !visited_global.contains(&neighbor)
{
let mut new_path = path.clone();
new_path.push(neighbor.clone());
visited_in_path.insert(neighbor.clone());
stack.push((neighbor, new_path));
}
}
}
visited_global.insert(start.clone());
}
CyclesResult {
cycles,
limit_reached: false,
}
}
fn is_duplicate_cycle(existing: &[Cycle], new_cycle: &[String]) -> bool {
if new_cycle.len() < 2 {
return true;
}
let cycle_core: Vec<&str> = new_cycle[..new_cycle.len() - 1]
.iter()
.map(|s| s.as_str())
.collect();
for existing_cycle in existing {
if existing_cycle.length != cycle_core.len() {
continue;
}
let existing_core: Vec<&str> = existing_cycle.nodes[..existing_cycle.nodes.len() - 1]
.iter()
.map(|s| s.as_str())
.collect();
if Self::is_rotation(&existing_core, &cycle_core) {
return true;
}
}
false
}
fn is_rotation(a: &[&str], b: &[&str]) -> bool {
if a.len() != b.len() {
return false;
}
let n = a.len();
for i in 0..n {
let mut matches = true;
for j in 0..n {
if a[j] != b[(i + j) % n] {
matches = false;
break;
}
}
if matches {
return true;
}
}
false
}
}