Skip to content

Commit c15ed14

Browse files
committed
Merge pull request #8 from mitchmindtree/add_edge_heuristics
Add edge heuristic to avoid full cycle detection in common cases.
2 parents 7dc1d21 + ed894ff commit c15ed14

File tree

2 files changed

+47
-23
lines changed

2 files changed

+47
-23
lines changed

Cargo.toml

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "daggy"
3-
version = "0.3.0"
3+
version = "0.4.0"
44
authors = ["mitchmindtree <[email protected]>"]
55
description = "A directed acyclic graph data structure library. It is Implemented on top of petgraph's Graph data structure and attempts to follow similar conventions where suitable."
66
readme = "README.md"
@@ -9,6 +9,5 @@ license = "MIT/Apache-2.0"
99
repository = "https://github.com/mitchmindtree/daggy.git"
1010
homepage = "https://github.com/mitchmindtree/daggy"
1111

12-
1312
[dependencies]
14-
petgraph = "0.1.15"
13+
petgraph = "0.2.2"

src/lib.rs

+45-20
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,15 @@
1414
#![forbid(unsafe_code)]
1515
#![warn(missing_docs)]
1616

17-
1817
extern crate petgraph as pg;
1918

20-
21-
pub use pg as petgraph;
22-
pub use pg::graph::{EdgeIndex, NodeIndex, EdgeWeightsMut, NodeWeightsMut};
23-
pub use walker::Walker;
2419
use pg::graph::{DefIndex, GraphIndex, IndexType};
2520
use std::marker::PhantomData;
2621
use std::ops::{Index, IndexMut};
2722

23+
pub use pg as petgraph;
24+
pub use pg::graph::{EdgeIndex, NodeIndex, EdgeWeightsMut, NodeWeightsMut};
25+
pub use walker::Walker;
2826

2927
pub mod walker;
3028

@@ -37,6 +35,7 @@ pub type RawNodes<'a, N, Ix> = &'a [pg::graph::Node<N, Ix>];
3735
/// Read only access into a **Dag**'s internal edge array.
3836
pub type RawEdges<'a, E, Ix> = &'a [pg::graph::Edge<E, Ix>];
3937

38+
4039
/// A Directed acyclic graph (DAG) data structure.
4140
///
4241
/// Dag is a thin wrapper around petgraph's `Graph` data structure, providing a refined API for
@@ -166,10 +165,9 @@ impl<N, E, Ix = DefIndex> Dag<N, E, Ix> where Ix: IndexType {
166165
/// If adding the edge **would** cause the graph to cycle, the edge will not be added and
167166
/// instead a `WouldCycle<E>` error with the given weight will be returned.
168167
///
169-
/// Computes in **O(t)** time where "t" is the time taken to check if adding the edge would
170-
/// cause a cycle in the graph. See petgraph's [`is_cyclic_directed`]
168+
/// In the worst case, petgraph's [`is_cyclic_directed`]
171169
/// (http://bluss.github.io/petulant-avenger-graphlibrary/doc/petgraph/algo/fn.is_cyclic_directed.html)
172-
/// function for more details.
170+
/// function is used to check whether or not adding the edge would create a cycle.
173171
///
174172
/// **Note:** Dag allows adding parallel ("duplicate") edges. If you want to avoid this, use
175173
/// [`update_edge`](./struct.Dag.html#method.update_edge) instead.
@@ -178,18 +176,24 @@ impl<N, E, Ix = DefIndex> Dag<N, E, Ix> where Ix: IndexType {
178176
/// some other node, consider using the [add_child](./struct.Dag.html#method.add_child) or
179177
/// [add_parent](./struct.Dag.html#method.add_parent) methods instead for better performance.
180178
///
181-
/// **Panics** if the Graph is at the maximum number of nodes for its index type.
179+
/// **Panics** if either `a` or `b` do not exist within the **Dag**.
180+
///
181+
/// **Panics** if the Graph is at the maximum number of edges for its index type.
182182
pub fn add_edge(&mut self, a: NodeIndex<Ix>, b: NodeIndex<Ix>, weight: E)
183183
-> Result<EdgeIndex<Ix>, WouldCycle<E>>
184184
{
185+
let should_check_for_cycle = must_check_for_cycle(self, a, b);
186+
185187
let idx = self.graph.add_edge(a, b, weight);
186188

187189
// Check if adding the edge has created a cycle.
188-
// TODO: Once petgraph adds support for re-using visit stack/maps, use that so that we
189-
// don't have to re-allocate every time `add_edge` is called.
190-
if pg::algo::is_cyclic_directed(&self.graph) {
190+
//
191+
// TODO: Re-use a `pg::visit::Topo` for this. Perhaps store this in the `Dag` itself?
192+
if should_check_for_cycle && pg::algo::is_cyclic_directed(&self.graph) {
191193
let weight = self.graph.remove_edge(idx).expect("No edge for index");
192194
Err(WouldCycle(weight))
195+
196+
// If no cycles were found, we're done.
193197
} else {
194198
Ok(idx)
195199
}
@@ -223,26 +227,34 @@ impl<N, E, Ix = DefIndex> Dag<N, E, Ix> where Ix: IndexType {
223227
///
224228
/// **Note:** If you're adding a series of new nodes and edges to a single node, consider using
225229
/// the [add_child](./struct.Dag.html#method.add_child) or [add_parent]
226-
/// (./struct.Dag.html#method.add_parent) methods instead for better performance. These
227-
/// perform better as there is no need to check for cycles.
230+
/// (./struct.Dag.html#method.add_parent) methods instead for greater convenience.
228231
///
229232
/// **Panics** if the Graph is at the maximum number of nodes for its index type.
230233
pub fn add_edges<I>(&mut self, edges: I) -> Result<EdgeIndices<Ix>, WouldCycle<Vec<E>>> where
231-
I: ::std::iter::IntoIterator<Item=(NodeIndex<Ix>, NodeIndex<Ix>, E)>,
234+
I: IntoIterator<Item=(NodeIndex<Ix>, NodeIndex<Ix>, E)>,
232235
{
233236
let mut num_edges = 0;
237+
let mut should_check_for_cycle = false;
238+
234239
for (a, b, weight) in edges {
240+
// Check whether or not we'll need to check for cycles.
241+
if !should_check_for_cycle {
242+
if must_check_for_cycle(self, a, b) {
243+
should_check_for_cycle = true;
244+
}
245+
}
246+
235247
self.graph.add_edge(a, b, weight);
236248
num_edges += 1;
237249
}
238250

239251
let total_edges = self.edge_count();
240-
let new_edges_range = total_edges-num_edges .. total_edges;
252+
let new_edges_range = total_edges-num_edges..total_edges;
241253

242254
// Check if adding the edges has created a cycle.
243-
// TODO: Once petgraph adds support for re-using visit stack/maps, use that so that we
244-
// don't have to re-allocate every time `add_edges` is called.
245-
if pg::algo::is_cyclic_directed(&self.graph) {
255+
//
256+
// TODO: Re-use a `pg::visit::Topo` for this. Perhaps store this in the `Dag` itself?
257+
if should_check_for_cycle && pg::algo::is_cyclic_directed(&self.graph) {
246258
let removed_edges = new_edges_range.rev().filter_map(|i| {
247259
let idx = EdgeIndex::new(i);
248260
self.graph.remove_edge(idx)
@@ -269,7 +281,7 @@ impl<N, E, Ix = DefIndex> Dag<N, E, Ix> where Ix: IndexType {
269281
///
270282
/// **Note:** If you're adding a new node and immediately adding a single edge to that node from
271283
/// some parent node, consider using the [`add_child`](./struct.Dag.html#method.add_child)
272-
/// method instead for better performance.
284+
/// method instead for greater convenience.
273285
///
274286
/// **Panics** if the Graph is at the maximum number of nodes for its index type.
275287
pub fn update_edge(&mut self, a: NodeIndex<Ix>, b: NodeIndex<Ix>, weight: E)
@@ -469,6 +481,19 @@ impl<N, E, Ix = DefIndex> Dag<N, E, Ix> where Ix: IndexType {
469481
}
470482

471483

484+
/// After adding a new edge to the graph, we use this function immediately after to check whether
485+
/// or not we need to check for a cycle.
486+
///
487+
/// If our parent *a* has no parents or our child *b* has no children, or if there was already an
488+
/// edge connecting *a* to *b*, we know that adding this edge has not caused the graph to cycle.
489+
fn must_check_for_cycle<N, E, Ix>(dag: &Dag<N, E, Ix>, a: NodeIndex<Ix>, b: NodeIndex<Ix>) -> bool
490+
where Ix: IndexType,
491+
{
492+
dag.parents(a).next(dag).is_some() && dag.children(b).next(dag).is_some()
493+
&& dag.find_edge(a, b).is_none()
494+
}
495+
496+
472497
impl<N, E, Ix> Index<NodeIndex<Ix>> for Dag<N, E, Ix> where Ix: IndexType {
473498
type Output = N;
474499
fn index(&self, index: NodeIndex<Ix>) -> &N {

0 commit comments

Comments
 (0)