Skip to content

Commit 5b9f7dc

Browse files
committed
- Improve robustness of cycle detection
process_vertex() now will panic (except for calls from is_cyclic()) if the number of vertices seen don't match the graph's vertex count when processing is complete. Annoyingly, I can't figure out how to make the expected test for the panic take a const value as an argument, so I've copy/pasted the string into the test macro text.
1 parent e8ac66a commit 5b9f7dc

1 file changed

Lines changed: 46 additions & 4 deletions

File tree

src/iterators/topo.rs

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ extern crate alloc;
1010
#[cfg(feature = "no_std")]
1111
use alloc::vec::Vec;
1212

13+
const PANIC_MSG: &str = "graph contains cycle(s)";
14+
1315
#[derive(Debug)]
1416
/// Topological Iterator
1517
pub struct Topo<'a, T> {
@@ -43,7 +45,7 @@ impl<'a, T> Topo<'a, T> {
4345
/// Will return None if:
4446
///
4547
/// * No vertices are left.
46-
fn process_vertex(&mut self) -> Option<&'a VertexId> {
48+
fn process_vertex(&mut self, check_cyclic: bool) -> Option<&'a VertexId> {
4749
match self.roots.pop() {
4850
Some(node) => {
4951
self.vertices.push(node);
@@ -64,7 +66,12 @@ impl<'a, T> Topo<'a, T> {
6466
}
6567
Some(node)
6668
},
67-
None => None,
69+
None => {
70+
if check_cyclic && self.vertices.len() != self.iterable.vertex_count() {
71+
panic!(PANIC_MSG);
72+
}
73+
None
74+
},
6875
}
6976
}
7077

@@ -76,7 +83,7 @@ impl<'a, T> Topo<'a, T> {
7683
/// It is a logic error to use this iterator after calling this function.
7784
pub fn is_cyclic(&mut self) -> bool {
7885
//Search until an answer is found.
79-
while self.process_vertex().is_some() {}
86+
while self.process_vertex(false).is_some() {}
8087

8188
self.vertices.len() != self.iterable.vertex_count()
8289
}
@@ -92,7 +99,7 @@ impl<'a, T> Iterator for Topo<'a, T> {
9299
}
93100
fn next(&mut self) -> Option<Self::Item> {
94101
(0..self.size_hint().1.unwrap())
95-
.filter_map(move |_| self.process_vertex())
102+
.filter_map(move |_| self.process_vertex(true))
96103
.next()
97104
}
98105
}
@@ -153,4 +160,39 @@ mod tests {
153160

154161
assert!(topo.is_cyclic());
155162
}
163+
164+
#[test]
165+
#[should_panic(expected = "graph contains cycle(s)")]
166+
fn is_cyclic_and_panic() {
167+
let mut graph: Graph<usize> = Graph::new();
168+
169+
let v1 = graph.add_vertex(1);
170+
let v2 = graph.add_vertex(2);
171+
let v3 = graph.add_vertex(3);
172+
let v4 = graph.add_vertex(4);
173+
let v5 = graph.add_vertex(5);
174+
let v6 = graph.add_vertex(6);
175+
let v7 = graph.add_vertex(7);
176+
177+
graph.add_edge(&v1, &v4).unwrap();
178+
graph.add_edge(&v2, &v4).unwrap();
179+
graph.add_edge(&v2, &v5).unwrap();
180+
graph.add_edge(&v3, &v5).unwrap();
181+
graph.add_edge(&v4, &v6).unwrap();
182+
graph.add_edge(&v4, &v7).unwrap();
183+
graph.add_edge(&v5, &v6).unwrap();
184+
graph.add_edge(&v6, &v7).unwrap();
185+
graph.add_edge(&v7, &v2).unwrap();
186+
187+
let mut topo = graph.topo();
188+
189+
topo.next();
190+
topo.next();
191+
topo.next();
192+
topo.next();
193+
topo.next();
194+
topo.next();
195+
topo.next();
196+
}
197+
156198
}

0 commit comments

Comments
 (0)