Skip to content

Commit

Permalink
'Dfs' and 'TarjanSCC' now take 'Ensure + GraphDeref' as input graphs.
Browse files Browse the repository at this point in the history
'TarjanSCC::new' now only requires 'HasVertex'.
  • Loading branch information
Emoun committed Jan 20, 2025
1 parent 310abff commit 946c81d
Show file tree
Hide file tree
Showing 5 changed files with 139 additions and 78 deletions.
95 changes: 61 additions & 34 deletions src/algo/dfs.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::core::{property::VertexIn, Graph};
use crate::core::{property::VertexIn, Ensure, Graph, GraphDeref};
use std::borrow::Borrow;

/// Performs [depth-first traversal](https://mathworld.wolfram.com/Depth-FirstTraversal.html)
Expand Down Expand Up @@ -90,59 +90,71 @@ use std::borrow::Borrow;
///
/// [`next`]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#tymethod.next
/// [`get_vertex`]: ../core/property/trait.HasVertex.html#method.get_vertex
pub struct Dfs<'a, G, F>
pub struct Dfs<G, F>
where
G: 'a + Graph,
G: Ensure + GraphDeref,
{
/// A reference to the graph being traversed.
///
/// This is use by `Dfs` when doing the traversal. Mutating
/// this reference between calls to
/// [`next`](https://doc.rust-lang.org/std/iter/trait.Iterator.html#tymethod.next)
/// is undefined behaviour.
pub graph: &'a G,
pub graph: G,

/// A custom payload, available to the function called upon a vertex exit.
/// See [`new`](#method.new).
pub payload: F,
visited: Vec<G::Vertex>,
visited: Vec<<G::Graph as Graph>::Vertex>,

/// The vertex on the stack, and whether on_exit should be called upon
/// popping.
stack: Vec<(G::Vertex, bool)>,
stack: Vec<(<G::Graph as Graph>::Vertex, bool)>,

/// Function to call when visiting a vertex
on_visit: fn(&mut Self, G::Vertex),
on_visit: fn(&G::Graph, <G::Graph as Graph>::Vertex, &mut F),

/// Function to call when exiting a vertex.
///
/// Provides a reference to the graph, the vertex that is exiting,
/// and a mutable reference to the payload given to the Dfs.
on_exit: fn(&G, G::Vertex, &mut F),
on_exit: fn(&G::Graph, <G::Graph as Graph>::Vertex, &mut F),

/// Function to call when exploring an edge.
///
/// When a vertex is being visited, this function is called for
/// every outgoing edge, regardless of whether the sinked vertex
/// (second vertex argument) has already been visited.
on_explore: fn(&mut Self, G::Vertex, G::Vertex, &G::EdgeWeight),
on_explore: fn(
&G::Graph,
<G::Graph as Graph>::Vertex,
<G::Graph as Graph>::Vertex,
&<G::Graph as Graph>::EdgeWeight,
&mut F,
),
}

impl<'a, G, F> Dfs<'a, G, F>
impl<G, F> Dfs<G, F>
where
G: 'a + Graph,
G: Ensure + GraphDeref,
{
pub fn new(
g: &'a G,
on_visit: fn(&mut Self, G::Vertex),
on_exit: fn(&G, G::Vertex, &mut F),
on_explore: fn(&mut Self, G::Vertex, G::Vertex, &G::EdgeWeight),
g: G,
on_visit: fn(&G::Graph, <G::Graph as Graph>::Vertex, &mut F),
on_exit: fn(&G::Graph, <G::Graph as Graph>::Vertex, &mut F),
on_explore: fn(
&G::Graph,
<G::Graph as Graph>::Vertex,
<G::Graph as Graph>::Vertex,
&<G::Graph as Graph>::EdgeWeight,
&mut F,
),
payload: F,
) -> Self
where
G: VertexIn<1>,
G::Graph: VertexIn<1>,
{
let v = g.vertex_at::<0>();
let v = g.graph().vertex_at::<0>();
let mut result = Self {
graph: g,
visited: Vec::new(),
Expand All @@ -157,16 +169,22 @@ where
result
}

fn visit(&mut self, to_return: G::Vertex)
fn visit(&mut self, to_return: <G::Graph as Graph>::Vertex)
{
(self.on_visit)(self, to_return);
(self.on_visit)(self.graph.graph(), to_return, &mut self.payload);
// Mark visited
self.visited.push(to_return);

// Explore children
for (child, weight) in self.graph.edges_sourced_in(to_return.clone())
for (child, weight) in self.graph.graph().edges_sourced_in(to_return.clone())
{
(self.on_explore)(self, to_return, child, weight.borrow());
(self.on_explore)(
self.graph.graph(),
to_return,
child,
weight.borrow(),
&mut self.payload,
);
if !self.visited(child.clone())
{
// Push to stack without exit mark
Expand All @@ -175,7 +193,7 @@ where
}
}

pub fn visited(&self, v: G::Vertex) -> bool
pub fn visited(&self, v: <G::Graph as Graph>::Vertex) -> bool
{
self.visited.contains(&v)
}
Expand All @@ -185,7 +203,7 @@ where
///
/// If there was nothing to pop and call `on_exit` on, return false,
/// otherwise returns true.
pub fn advance_next_exit(&mut self) -> Option<G::Vertex>
pub fn advance_next_exit(&mut self) -> Option<<G::Graph as Graph>::Vertex>
{
while let Some(last) = self.stack.last()
{
Expand All @@ -196,7 +214,7 @@ where
// If its exit marked, call the closure on it.
if last.1
{
(self.on_exit)(self.graph, last.0, &mut self.payload);
(self.on_exit)(self.graph.graph(), last.0, &mut self.payload);
return Some(last.0);
}
}
Expand All @@ -208,7 +226,7 @@ where
None
}

pub fn continue_from(&mut self, v: G::Vertex) -> bool
pub fn continue_from(&mut self, v: <G::Graph as Graph>::Vertex) -> bool
{
if !self.visited(v.clone())
{
Expand All @@ -221,16 +239,25 @@ where
}
}

pub fn do_nothing_on_visit(_: &mut Self, _: G::Vertex) {}
pub fn do_nothing_on_visit(_: &G::Graph, _: <G::Graph as Graph>::Vertex, _: &mut F) {}

pub fn do_nothing_on_exit(_: &G, _: G::Vertex, _: &mut F) {}
pub fn do_nothing_on_exit(_: &G::Graph, _: <G::Graph as Graph>::Vertex, _: &mut F) {}

pub fn do_nothing_on_explore(_: &mut Self, _: G::Vertex, _: G::Vertex, _: &G::EdgeWeight) {}
pub fn do_nothing_on_explore(
_: &G::Graph,
_: <G::Graph as Graph>::Vertex,
_: <G::Graph as Graph>::Vertex,
_: &<G::Graph as Graph>::EdgeWeight,
_: &mut F,
)
{
}
}

impl<'a, G> Dfs<'a, G, ()>
impl<G> Dfs<G, ()>
where
G: 'a + VertexIn<1>,
G: Ensure + GraphDeref,
G::Graph: VertexIn<1>,
{
/// Constructs a new `Dfs` to traverse the specified graph.
///
Expand All @@ -247,7 +274,7 @@ where
///
/// [`next`]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#tymethod.next
/// [`get_vertex`]: ../core/property/trait.HasVertex.html#method.get_vertex
pub fn new_simple(g: &'a G) -> Self
pub fn new_simple(g: G) -> Self
{
Self::new(
g,
Expand All @@ -259,11 +286,11 @@ where
}
}

impl<'a, G, F> Iterator for Dfs<'a, G, F>
impl<'a, G, F> Iterator for Dfs<G, F>
where
G: 'a + Graph,
G: 'a + Ensure + GraphDeref,
{
type Item = G::Vertex;
type Item = <G::Graph as Graph>::Vertex;

fn next(&mut self) -> Option<Self::Item>
{
Expand Down
94 changes: 64 additions & 30 deletions src/algo/tarjan_scc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
//! reachable from this vertex. The lowlink value should be seen as the lowest
//! index reachable.
//!
//! When a vertex is pushed in the stack, it is assigned an index. It is also
//! When a vertex is pushed on the stack, it is assigned an index. It is also
//! given a lowlink value equal to its index (since we know it can at least
//! reach itself). When you are finished visiting the children of a vertex,
//! check all vertices that are reachable from the current one. If they are on
Expand Down Expand Up @@ -51,9 +51,9 @@
use crate::{
algo::Dfs,
core::{
property::{ConnectedGraph, VertexIn},
property::{ConnectedGraph, HasVertex, VertexInGraph},
proxy::SubgraphProxy,
Directed, Graph, Guard,
Directed, Ensure, Graph, GraphDeref, Guard,
},
};
use std::cmp::min;
Expand Down Expand Up @@ -105,7 +105,7 @@ use std::cmp::min;
/// // Connect first SCC to second
/// graph.add_edge(&v0,&v2).unwrap();
///
/// let graph = VertexInGraph::ensure(graph, [v0]).unwrap();
/// let graph = VertexInGraph::<_>::ensure(graph, [v0]).unwrap();
///
/// // Initialize algorithm
/// let mut tarj = TarjanScc::new(&graph);
Expand Down Expand Up @@ -146,20 +146,22 @@ use std::cmp::min;
/// [`subgraphs`]: ../core/property/trait.Subgraph.html
/// [`Subgraph`]: ../core/property/trait.Subgraph.html
/// [`Subgraph::reaches`]: ../core/property/trait.Subgraph.html#method.reaches
pub struct TarjanScc<'a, G>
pub struct TarjanScc<G>
where
G: 'a + Graph<Directedness = Directed>,
G: Ensure + GraphDeref,
G::Graph: Graph<Directedness = Directed>,
{
dfs: Dfs<'a, G, Vec<(G::Vertex, usize)>>,
dfs: Dfs<VertexInGraph<G>, Vec<(<G::Graph as Graph>::Vertex, usize)>>,

/// We use this to keep track of which vertices we have check for
/// We use this to keep track of which vertices we have to check for
/// whether they have been visited.
unchecked: Box<dyn 'a + Iterator<Item = G::Vertex>>,
unchecked: std::vec::IntoIter<<G::Graph as Graph>::Vertex>,
}

impl<'a, G> TarjanScc<'a, G>
impl<G> TarjanScc<G>
where
G: 'a + Graph<Directedness = Directed> + VertexIn<1>,
G: Ensure + GraphDeref,
G::Graph: Graph<Directedness = Directed> + HasVertex,
{
/// Constructs a new `TarjanScc` to find the [strongly connected components](https://mathworld.wolfram.com/StronglyConnectedComponent.html)
/// of the specified graph.
Expand All @@ -177,7 +179,7 @@ where
/// [`next`]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#tymethod.next
/// [`get_vertex`]:
/// ../core/property/trait.HasVertex.html#tymethod.get_vertex
pub fn new(graph: &'a G) -> Self
pub fn new(graph: G) -> Self
{
/// Implements part of Tarjan's algorithm, namely what happens when we
/// are finished visiting a vertex.
Expand Down Expand Up @@ -207,36 +209,37 @@ where
}
}

let v = graph.graph().any_vertex();
let graph = VertexInGraph::ensure_unchecked(graph, [v]);
let vs = graph.all_vertices().collect::<Vec<_>>();

// Push the start vertex on the stack with low-link = 0
let dfs = Dfs::new(
graph,
Dfs::do_nothing_on_visit,
Dfs::<VertexInGraph<_>, _>::do_nothing_on_visit,
on_exit,
Dfs::do_nothing_on_explore,
vec![(graph.vertex_at::<0>(), 0)],
Dfs::<VertexInGraph<_>, _>::do_nothing_on_explore,
vec![(v, 0)],
);
Self {
dfs,
unchecked: Box::new(graph.all_vertices()),
unchecked: vs.into_iter(),
}
}
}

impl<'a, G> Iterator for TarjanScc<'a, G>
where
G: 'a + Graph<Directedness = Directed>,
{
type Item = ConnectedGraph<SubgraphProxy<&'a G>>;

fn next(&mut self) -> Option<Self::Item>
macro_rules! next_scc_impl {
{
$self_tt:ident,
$($return_code:tt)*
} => {
// Repeat until either an SCC is found or all vertices have been visited.
loop
{
// For each vertex we are finished visiting, check if its the root of a SCC.
while let Some(v) = self.dfs.advance_next_exit()
while let Some(v) = $self_tt.dfs.advance_next_exit()
{
let stack = &mut self.dfs.payload;
let stack = &mut $self_tt.dfs.payload;

// Find the index of the vertex
let index = stack.iter().position(|(v2, _)| *v2 == v).unwrap();
Expand All @@ -245,7 +248,7 @@ where
{
// Vertex is root of SCC, pop stack for all before it

let mut scc = SubgraphProxy::new(self.dfs.graph);
let mut scc = SubgraphProxy::new($self_tt.dfs.graph.0 $($return_code)*);
while stack.len() > index
{
scc.expand(stack.pop().unwrap().0).unwrap();
Expand All @@ -261,20 +264,51 @@ where
}

// No SCCs found, let the Dfs run once
if let Some(v) = self.dfs.next()
if let Some(v) = $self_tt.dfs.next()
{
// First push vertex onto stack, with lowlink value equal to its index
let stack = &mut self.dfs.payload;
let stack = &mut $self_tt.dfs.payload;
stack.push((v.clone(), stack.len()));
}
else
{
let dfs = &mut self.dfs;
if !self.unchecked.any(|v| dfs.continue_from(v))
let dfs = &mut $self_tt.dfs;
if !$self_tt.unchecked.any(|v| dfs.continue_from(v))
{
return None;
}
}
}
}
}

impl<G> TarjanScc<G>
where
G: Ensure + GraphDeref,
G::Graph: Graph<Directedness = Directed>,
{
/// Returns the next strongly connected component `TarjanScc` has found, if
/// any.
///
/// This is similar to `next`, however can be used when `TarjanScc` receives
/// a non-copy ensure.
///
///
pub fn next_scc(&mut self) -> Option<ConnectedGraph<SubgraphProxy<&G::Graph>>>
{
next_scc_impl!(self, .graph())
}
}

impl<G> Iterator for TarjanScc<G>
where
G: Ensure + GraphDeref + Copy,
G::Graph: Graph<Directedness = Directed>,
{
type Item = ConnectedGraph<SubgraphProxy<G>>;

fn next(&mut self) -> Option<Self::Item>
{
next_scc_impl!(self,)
}
}
Loading

0 comments on commit 946c81d

Please sign in to comment.