Add support for pruning nodes from a DFS.
diff --git a/src/visit/dfsvisit.rs b/src/visit/dfsvisit.rs
index 4e8a57b..cfb40c9 100644
--- a/src/visit/dfsvisit.rs
+++ b/src/visit/dfsvisit.rs
@@ -20,26 +20,35 @@
/// For an edge *(u, v)*, if the discover time of *v* is greater than *u*,
/// then it is a forward edge, else a cross edge.
CrossForwardEdge(N, N),
+ /// All edges from a node have been reported.
Finish(N, Time),
}
-/// Return if the expression is a break value.
+/// Return if the expression is a break value, execute the provided statement
+/// if it is a prune value.
macro_rules! try_control {
- ($e:expr) => {
+ ($e:expr, $p:stmt) => {
match $e {
x => if x.should_break() {
return x;
+ } else if x.should_prune() {
+ $p;
}
}
}
}
-/// Control flow for callbacks.
-///
-/// `Break` can carry a value.
+/// Control flow for `depth_first_search` callbacks.
#[derive(Copy, Clone, Debug)]
pub enum Control<B> {
+ /// Continue the DFS traversal as normal.
Continue,
+ /// Prune the current node from the DFS traversal. No more edges from this
+ /// node will be reported to the callback. A `DfsEvent::Finish` for this
+ /// node will still be reported. This can be returned in response to any
+ /// `DfsEvent`, except `Finish`, which will panic.
+ Prune,
+ /// Stop the DFS traversal and return the provided value.
Break(B),
}
@@ -48,7 +57,7 @@
/// Get the value in `Control::Break(_)`, if present.
pub fn break_value(self) -> Option<B> {
match self {
- Control::Continue => None,
+ Control::Continue | Control::Prune => None,
Control::Break(b) => Some(b),
}
}
@@ -60,12 +69,15 @@
pub trait ControlFlow {
fn continuing() -> Self;
fn should_break(&self) -> bool;
+ fn should_prune(&self) -> bool;
}
impl ControlFlow for () {
fn continuing() { }
#[inline]
fn should_break(&self) -> bool { false }
+ #[inline]
+ fn should_prune(&self) -> bool { false }
}
impl<B> ControlFlow for Control<B> {
@@ -73,6 +85,12 @@
fn should_break(&self) -> bool {
if let Control::Break(_) = *self { true } else { false }
}
+ fn should_prune(&self) -> bool {
+ match *self {
+ Control::Prune => true,
+ Control::Continue | Control::Break(_) => false,
+ }
+ }
}
impl<E> ControlFlow for Result<(), E> {
@@ -80,6 +98,9 @@
fn should_break(&self) -> bool {
self.is_err()
}
+ fn should_prune(&self) -> bool {
+ false
+ }
}
/// The default is `Continue`.
@@ -98,8 +119,11 @@
///
/// If the return value of the visitor is simply `()`, the visit runs until it
/// is finished. If the return value is a `Control<B>`, it can be used to
-/// break the visit early, and the last control value is returned by the
-/// function.
+/// change the control flow of the search. `Control::Break` will stop the
+/// the visit early, returning the contained value from the function.
+/// `Control::Prune` will stop traversing any additional edges from the current
+/// node and proceed immediately to the `Finish` event. Attempting to prune a
+/// node from its `Finish` event will cause a panic.
///
/// [de]: enum.DfsEvent.html
///
@@ -156,7 +180,8 @@
let finished = &mut graph.visit_map();
for start in starts {
- try_control!(dfs_visitor(graph, start, &mut visitor, discovered, finished, time));
+ try_control!(dfs_visitor(graph, start, &mut visitor, discovered, finished, time),
+ unreachable!());
}
C::continuing()
}
@@ -171,20 +196,27 @@
if !discovered.visit(u) {
return C::continuing();
}
- try_control!(visitor(DfsEvent::Discover(u, time_post_inc(time))));
- for v in graph.neighbors(u) {
- if !discovered.is_visited(&v) {
- try_control!(visitor(DfsEvent::TreeEdge(u, v)));
- try_control!(dfs_visitor(graph, v, visitor, discovered, finished, time));
- } else if !finished.is_visited(&v) {
- try_control!(visitor(DfsEvent::BackEdge(u, v)));
- } else {
- try_control!(visitor(DfsEvent::CrossForwardEdge(u, v)));
+
+ 'prune: loop {
+ try_control!(visitor(DfsEvent::Discover(u, time_post_inc(time))), break 'prune);
+ for v in graph.neighbors(u) {
+ if !discovered.is_visited(&v) {
+ try_control!(visitor(DfsEvent::TreeEdge(u, v)), continue);
+ try_control!(dfs_visitor(graph, v, visitor, discovered, finished, time),
+ unreachable!());
+ } else if !finished.is_visited(&v) {
+ try_control!(visitor(DfsEvent::BackEdge(u, v)), continue);
+ } else {
+ try_control!(visitor(DfsEvent::CrossForwardEdge(u, v)), continue);
+ }
}
+
+ break;
}
let first_finish = finished.visit(u);
debug_assert!(first_finish);
- try_control!(visitor(DfsEvent::Finish(u, time_post_inc(time))));
+ try_control!(visitor(DfsEvent::Finish(u, time_post_inc(time))),
+ panic!("Pruning on the `DfsEvent::Finish` is not supported!"));
C::continuing()
}
diff --git a/tests/graph.rs b/tests/graph.rs
index 773a993..fa76940 100644
--- a/tests/graph.rs
+++ b/tests/graph.rs
@@ -1539,6 +1539,24 @@
}
path.reverse();
assert_eq!(&path, &[n(0), n(2), n(4)]);
+
+ // check that if we prune 2, we never see 4.
+ let start = n(0);
+ let prune = n(2);
+ let nongoal = n(4);
+ let ret = depth_first_search(&gr, Some(start), |event| {
+ if let Discover(n, _) = event {
+ if n == prune {
+ return Control::Prune;
+ }
+ } else if let TreeEdge(u, v) = event {
+ if v == nongoal {
+ return Control::Break(u);
+ }
+ }
+ Control::Continue
+ });
+ assert!(ret.break_value().is_none());
}