1#![allow(dead_code)]
2use std::collections::{HashMap, HashSet, VecDeque};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
12pub struct CycleNodeId(
13 pub usize,
15);
16
17impl std::fmt::Display for CycleNodeId {
18 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
19 write!(f, "CycleNode({})", self.0)
20 }
21}
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum NodeColor {
26 White,
28 Gray,
30 Black,
32}
33
34#[derive(Debug, Clone, PartialEq, Eq)]
36pub struct Cycle {
37 pub nodes: Vec<CycleNodeId>,
39}
40
41impl Cycle {
42 pub fn new(nodes: Vec<CycleNodeId>) -> Self {
44 Self { nodes }
45 }
46
47 pub fn len(&self) -> usize {
49 self.nodes.len()
50 }
51
52 pub fn is_empty(&self) -> bool {
54 self.nodes.is_empty()
55 }
56
57 pub fn contains(&self, id: CycleNodeId) -> bool {
59 self.nodes.contains(&id)
60 }
61
62 pub fn canonical(&self) -> Self {
64 if self.nodes.is_empty() {
65 return self.clone();
66 }
67 let min_pos = self
68 .nodes
69 .iter()
70 .enumerate()
71 .min_by_key(|(_, n)| n.0)
72 .map(|(i, _)| i)
73 .unwrap_or(0);
74 let mut rotated = Vec::with_capacity(self.nodes.len());
75 rotated.extend_from_slice(&self.nodes[min_pos..]);
76 rotated.extend_from_slice(&self.nodes[..min_pos]);
77 Self { nodes: rotated }
78 }
79}
80
81impl std::fmt::Display for Cycle {
82 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
83 let ids: Vec<String> = self.nodes.iter().map(|n| n.0.to_string()).collect();
84 write!(f, "[{}]", ids.join(" -> "))
85 }
86}
87
88#[derive(Debug, Clone)]
90pub struct CycleDetectionResult {
91 pub is_acyclic: bool,
93 pub cycles: Vec<Cycle>,
95 pub nodes_visited: usize,
97}
98
99impl CycleDetectionResult {
100 pub fn acyclic(nodes_visited: usize) -> Self {
102 Self {
103 is_acyclic: true,
104 cycles: Vec::new(),
105 nodes_visited,
106 }
107 }
108
109 pub fn with_cycles(cycles: Vec<Cycle>, nodes_visited: usize) -> Self {
111 Self {
112 is_acyclic: cycles.is_empty(),
113 cycles,
114 nodes_visited,
115 }
116 }
117
118 pub fn cycle_count(&self) -> usize {
120 self.cycles.len()
121 }
122}
123
124pub struct CycleGraph {
126 adjacency: HashMap<CycleNodeId, HashSet<CycleNodeId>>,
128}
129
130impl CycleGraph {
131 pub fn new() -> Self {
133 Self {
134 adjacency: HashMap::new(),
135 }
136 }
137
138 pub fn add_node(&mut self, id: CycleNodeId) {
140 self.adjacency.entry(id).or_default();
141 }
142
143 pub fn add_edge(&mut self, from: CycleNodeId, to: CycleNodeId) {
145 self.add_node(from);
146 self.add_node(to);
147 self.adjacency.entry(from).or_default().insert(to);
148 }
149
150 pub fn remove_edge(&mut self, from: CycleNodeId, to: CycleNodeId) -> bool {
152 self.adjacency.get_mut(&from).is_some_and(|s| s.remove(&to))
153 }
154
155 pub fn node_count(&self) -> usize {
157 self.adjacency.len()
158 }
159
160 pub fn edge_count(&self) -> usize {
162 self.adjacency.values().map(|s| s.len()).sum()
163 }
164
165 pub fn has_cycle(&self) -> bool {
167 let mut colors: HashMap<CycleNodeId, NodeColor> = self
168 .adjacency
169 .keys()
170 .map(|&k| (k, NodeColor::White))
171 .collect();
172
173 let mut nodes: Vec<CycleNodeId> = self.adjacency.keys().copied().collect();
174 nodes.sort();
175
176 for &node in &nodes {
177 if colors[&node] == NodeColor::White && self.dfs_has_cycle(node, &mut colors) {
178 return true;
179 }
180 }
181 false
182 }
183
184 fn dfs_has_cycle(
186 &self,
187 node: CycleNodeId,
188 colors: &mut HashMap<CycleNodeId, NodeColor>,
189 ) -> bool {
190 colors.insert(node, NodeColor::Gray);
191
192 if let Some(successors) = self.adjacency.get(&node) {
193 for &succ in successors {
194 match colors.get(&succ) {
195 Some(NodeColor::Gray) => return true,
196 Some(NodeColor::White) => {
197 if self.dfs_has_cycle(succ, colors) {
198 return true;
199 }
200 }
201 _ => {}
202 }
203 }
204 }
205
206 colors.insert(node, NodeColor::Black);
207 false
208 }
209
210 pub fn detect_all_cycles(&self) -> CycleDetectionResult {
212 let mut visited: HashSet<CycleNodeId> = HashSet::new();
213 let mut rec_stack: HashSet<CycleNodeId> = HashSet::new();
214 let mut path: Vec<CycleNodeId> = Vec::new();
215 let mut cycles: Vec<Cycle> = Vec::new();
216 let mut nodes_visited = 0_usize;
217
218 let mut nodes: Vec<CycleNodeId> = self.adjacency.keys().copied().collect();
219 nodes.sort();
220
221 for &node in &nodes {
222 if !visited.contains(&node) {
223 self.dfs_find_cycles(
224 node,
225 &mut visited,
226 &mut rec_stack,
227 &mut path,
228 &mut cycles,
229 &mut nodes_visited,
230 );
231 }
232 }
233
234 CycleDetectionResult::with_cycles(cycles, nodes_visited)
235 }
236
237 fn dfs_find_cycles(
239 &self,
240 node: CycleNodeId,
241 visited: &mut HashSet<CycleNodeId>,
242 rec_stack: &mut HashSet<CycleNodeId>,
243 path: &mut Vec<CycleNodeId>,
244 cycles: &mut Vec<Cycle>,
245 nodes_visited: &mut usize,
246 ) {
247 visited.insert(node);
248 rec_stack.insert(node);
249 path.push(node);
250 *nodes_visited += 1;
251
252 if let Some(successors) = self.adjacency.get(&node) {
253 let mut sorted_succ: Vec<CycleNodeId> = successors.iter().copied().collect();
254 sorted_succ.sort();
255 for succ in sorted_succ {
256 if rec_stack.contains(&succ) {
257 if let Some(start_idx) = path.iter().position(|&n| n == succ) {
259 let cycle_nodes = path[start_idx..].to_vec();
260 cycles.push(Cycle::new(cycle_nodes));
261 }
262 } else if !visited.contains(&succ) {
263 self.dfs_find_cycles(succ, visited, rec_stack, path, cycles, nodes_visited);
264 }
265 }
266 }
267
268 path.pop();
269 rec_stack.remove(&node);
270 }
271
272 pub fn would_create_cycle(&self, from: CycleNodeId, to: CycleNodeId) -> bool {
275 if from == to {
276 return true;
277 }
278
279 let mut visited: HashSet<CycleNodeId> = HashSet::new();
280 let mut queue: VecDeque<CycleNodeId> = VecDeque::new();
281 queue.push_back(to);
282
283 while let Some(current) = queue.pop_front() {
284 if current == from {
285 return true;
286 }
287 if visited.insert(current) {
288 if let Some(successors) = self.adjacency.get(¤t) {
289 for &succ in successors {
290 queue.push_back(succ);
291 }
292 }
293 }
294 }
295
296 false
297 }
298
299 pub fn try_add_edge(&mut self, from: CycleNodeId, to: CycleNodeId) -> bool {
302 if self.would_create_cycle(from, to) {
303 return false;
304 }
305 self.add_edge(from, to);
306 true
307 }
308
309 pub fn cyclic_nodes(&self) -> HashSet<CycleNodeId> {
311 let result = self.detect_all_cycles();
312 let mut cyclic: HashSet<CycleNodeId> = HashSet::new();
313 for cycle in &result.cycles {
314 for &node in &cycle.nodes {
315 cyclic.insert(node);
316 }
317 }
318 cyclic
319 }
320
321 pub fn is_in_cycle(&self, id: CycleNodeId) -> bool {
323 self.cyclic_nodes().contains(&id)
324 }
325}
326
327impl Default for CycleGraph {
328 fn default() -> Self {
329 Self::new()
330 }
331}
332
333#[cfg(test)]
334mod tests {
335 use super::*;
336
337 fn cn(id: usize) -> CycleNodeId {
338 CycleNodeId(id)
339 }
340
341 #[test]
342 fn test_empty_graph_no_cycles() {
343 let graph = CycleGraph::new();
344 assert!(!graph.has_cycle());
345 }
346
347 #[test]
348 fn test_single_node_no_cycle() {
349 let mut graph = CycleGraph::new();
350 graph.add_node(cn(0));
351 assert!(!graph.has_cycle());
352 }
353
354 #[test]
355 fn test_dag_no_cycle() {
356 let mut graph = CycleGraph::new();
357 graph.add_edge(cn(0), cn(1));
358 graph.add_edge(cn(1), cn(2));
359 graph.add_edge(cn(0), cn(2));
360 assert!(!graph.has_cycle());
361 }
362
363 #[test]
364 fn test_simple_cycle() {
365 let mut graph = CycleGraph::new();
366 graph.add_edge(cn(0), cn(1));
367 graph.add_edge(cn(1), cn(2));
368 graph.add_edge(cn(2), cn(0));
369 assert!(graph.has_cycle());
370 }
371
372 #[test]
373 fn test_self_loop() {
374 let mut graph = CycleGraph::new();
375 graph.add_edge(cn(0), cn(0));
376 assert!(graph.has_cycle());
377 }
378
379 #[test]
380 fn test_detect_all_cycles() {
381 let mut graph = CycleGraph::new();
382 graph.add_edge(cn(0), cn(1));
383 graph.add_edge(cn(1), cn(0));
384 let result = graph.detect_all_cycles();
385 assert!(!result.is_acyclic);
386 assert!(!result.cycles.is_empty());
387 }
388
389 #[test]
390 fn test_detect_acyclic() {
391 let mut graph = CycleGraph::new();
392 graph.add_edge(cn(0), cn(1));
393 graph.add_edge(cn(1), cn(2));
394 let result = graph.detect_all_cycles();
395 assert!(result.is_acyclic);
396 assert_eq!(result.cycle_count(), 0);
397 }
398
399 #[test]
400 fn test_would_create_cycle() {
401 let mut graph = CycleGraph::new();
402 graph.add_edge(cn(0), cn(1));
403 graph.add_edge(cn(1), cn(2));
404 assert!(graph.would_create_cycle(cn(2), cn(0)));
405 assert!(!graph.would_create_cycle(cn(0), cn(2)));
406 }
407
408 #[test]
409 fn test_would_create_self_loop() {
410 let graph = CycleGraph::new();
411 assert!(graph.would_create_cycle(cn(0), cn(0)));
412 }
413
414 #[test]
415 fn test_try_add_edge_success() {
416 let mut graph = CycleGraph::new();
417 graph.add_edge(cn(0), cn(1));
418 assert!(graph.try_add_edge(cn(1), cn(2)));
419 assert_eq!(graph.edge_count(), 2);
420 }
421
422 #[test]
423 fn test_try_add_edge_rejected() {
424 let mut graph = CycleGraph::new();
425 graph.add_edge(cn(0), cn(1));
426 graph.add_edge(cn(1), cn(2));
427 assert!(!graph.try_add_edge(cn(2), cn(0)));
428 assert_eq!(graph.edge_count(), 2);
429 }
430
431 #[test]
432 fn test_cyclic_nodes() {
433 let mut graph = CycleGraph::new();
434 graph.add_edge(cn(0), cn(1));
435 graph.add_edge(cn(1), cn(2));
436 graph.add_edge(cn(2), cn(0));
437 graph.add_edge(cn(3), cn(0)); let cyclic = graph.cyclic_nodes();
439 assert!(cyclic.contains(&cn(0)));
440 assert!(cyclic.contains(&cn(1)));
441 assert!(cyclic.contains(&cn(2)));
442 assert!(!cyclic.contains(&cn(3)));
443 }
444
445 #[test]
446 fn test_is_in_cycle() {
447 let mut graph = CycleGraph::new();
448 graph.add_edge(cn(0), cn(1));
449 graph.add_edge(cn(1), cn(0));
450 graph.add_node(cn(2));
451 assert!(graph.is_in_cycle(cn(0)));
452 assert!(!graph.is_in_cycle(cn(2)));
453 }
454
455 #[test]
456 fn test_remove_edge() {
457 let mut graph = CycleGraph::new();
458 graph.add_edge(cn(0), cn(1));
459 assert!(graph.remove_edge(cn(0), cn(1)));
460 assert!(!graph.remove_edge(cn(0), cn(1)));
461 assert_eq!(graph.edge_count(), 0);
462 }
463
464 #[test]
465 fn test_cycle_canonical() {
466 let cycle = Cycle::new(vec![cn(2), cn(0), cn(1)]);
467 let canonical = cycle.canonical();
468 assert_eq!(canonical.nodes[0], cn(0));
469 }
470
471 #[test]
472 fn test_cycle_display() {
473 let cycle = Cycle::new(vec![cn(0), cn(1), cn(2)]);
474 let display = format!("{cycle}");
475 assert!(display.contains("0"));
476 assert!(display.contains("2"));
477 }
478
479 #[test]
480 fn test_cycle_contains() {
481 let cycle = Cycle::new(vec![cn(0), cn(1), cn(2)]);
482 assert!(cycle.contains(cn(1)));
483 assert!(!cycle.contains(cn(5)));
484 }
485
486 #[test]
487 fn test_node_color_states() {
488 assert_eq!(NodeColor::White, NodeColor::White);
489 assert_ne!(NodeColor::White, NodeColor::Gray);
490 assert_ne!(NodeColor::Gray, NodeColor::Black);
491 }
492
493 #[test]
494 fn test_cycle_detection_result_acyclic() {
495 let result = CycleDetectionResult::acyclic(10);
496 assert!(result.is_acyclic);
497 assert_eq!(result.nodes_visited, 10);
498 assert_eq!(result.cycle_count(), 0);
499 }
500}