reddb_server/storage/engine/algorithms/
cycles.rs1use std::collections::HashSet;
9
10use super::super::graph_store::GraphStore;
11
12pub struct CycleDetector {
23 pub max_length: usize,
25 pub max_cycles: usize,
27}
28
29impl Default for CycleDetector {
30 fn default() -> Self {
31 Self {
32 max_length: 10,
33 max_cycles: 100,
34 }
35 }
36}
37
38#[derive(Debug, Clone)]
40pub struct Cycle {
41 pub nodes: Vec<String>,
43 pub length: usize,
45}
46
47#[derive(Debug, Clone)]
49pub struct CyclesResult {
50 pub cycles: Vec<Cycle>,
52 pub limit_reached: bool,
54}
55
56impl CycleDetector {
57 pub fn new() -> Self {
59 Self::default()
60 }
61
62 pub fn max_length(mut self, max: usize) -> Self {
64 self.max_length = max;
65 self
66 }
67
68 pub fn max_cycles(mut self, max: usize) -> Self {
70 self.max_cycles = max;
71 self
72 }
73
74 pub fn find(&self, graph: &GraphStore) -> CyclesResult {
76 let nodes: Vec<String> = graph.iter_nodes().map(|n| n.id.clone()).collect();
77 let mut cycles: Vec<Cycle> = Vec::new();
78 let mut visited_global: HashSet<String> = HashSet::new();
79
80 for start in &nodes {
81 if cycles.len() >= self.max_cycles {
82 return CyclesResult {
83 cycles,
84 limit_reached: true,
85 };
86 }
87
88 let mut stack: Vec<(String, Vec<String>)> = vec![(start.clone(), vec![start.clone()])];
90 let mut visited_in_path: HashSet<String> = HashSet::new();
91 visited_in_path.insert(start.clone());
92
93 while let Some((current, path)) = stack.pop() {
94 if path.len() > self.max_length {
96 continue;
97 }
98
99 for (_, neighbor, _) in graph.outgoing_edges(¤t) {
100 if neighbor == *start && path.len() > 1 {
102 let mut cycle_nodes = path.clone();
103 cycle_nodes.push(start.clone());
104
105 if !Self::is_duplicate_cycle(&cycles, &cycle_nodes) {
107 cycles.push(Cycle {
108 length: cycle_nodes.len() - 1,
109 nodes: cycle_nodes,
110 });
111
112 if cycles.len() >= self.max_cycles {
113 return CyclesResult {
114 cycles,
115 limit_reached: true,
116 };
117 }
118 }
119 } else if !visited_in_path.contains(&neighbor)
120 && !visited_global.contains(&neighbor)
121 {
122 let mut new_path = path.clone();
123 new_path.push(neighbor.clone());
124 visited_in_path.insert(neighbor.clone());
125 stack.push((neighbor, new_path));
126 }
127 }
128 }
129
130 visited_global.insert(start.clone());
131 }
132
133 CyclesResult {
134 cycles,
135 limit_reached: false,
136 }
137 }
138
139 fn is_duplicate_cycle(existing: &[Cycle], new_cycle: &[String]) -> bool {
141 if new_cycle.len() < 2 {
142 return true;
143 }
144
145 let cycle_core: Vec<&str> = new_cycle[..new_cycle.len() - 1]
147 .iter()
148 .map(|s| s.as_str())
149 .collect();
150
151 for existing_cycle in existing {
152 if existing_cycle.length != cycle_core.len() {
153 continue;
154 }
155
156 let existing_core: Vec<&str> = existing_cycle.nodes[..existing_cycle.nodes.len() - 1]
157 .iter()
158 .map(|s| s.as_str())
159 .collect();
160
161 if Self::is_rotation(&existing_core, &cycle_core) {
163 return true;
164 }
165 }
166
167 false
168 }
169
170 fn is_rotation(a: &[&str], b: &[&str]) -> bool {
172 if a.len() != b.len() {
173 return false;
174 }
175
176 let n = a.len();
178 for i in 0..n {
179 let mut matches = true;
180 for j in 0..n {
181 if a[j] != b[(i + j) % n] {
182 matches = false;
183 break;
184 }
185 }
186 if matches {
187 return true;
188 }
189 }
190
191 false
192 }
193}