Skip to content

Commit d95e9e9

Browse files
committed
Added Graph::add_edge_check_cycle() method
1 parent e3375cb commit d95e9e9

1 file changed

Lines changed: 95 additions & 5 deletions

File tree

src/graph.rs

Lines changed: 95 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ pub enum GraphErr {
4747
/// The given weight is invalid
4848
InvalidWeight,
4949

50+
/// The operation cannot be performed as it will
51+
/// create a cycle in the graph.
52+
CycleError,
53+
5054
#[cfg(feature = "dot")]
5155
/// Could not render .dot file
5256
CouldNotRender,
@@ -292,7 +296,41 @@ impl<T> Graph<T> {
292296
return Ok(());
293297
}
294298

295-
self.do_add_edge(a, b, 0.0)
299+
self.do_add_edge(a, b, 0.0, false)
300+
}
301+
302+
/// Attempts to place a new edge in the graph, checking if the specified
303+
/// edge will create a cycle in the graph. If it does, this operation will fail.
304+
///
305+
/// Note that this operation has a bigger performance hit than `Graph::add_edge()`.
306+
///
307+
/// /// ## Example
308+
/// ```rust
309+
/// use graphlib::{Graph, GraphErr, VertexId};
310+
///
311+
/// let mut graph: Graph<usize> = Graph::new();
312+
///
313+
/// // Id of vertex that is not place in the graph
314+
/// let id = VertexId::random();
315+
///
316+
/// let v1 = graph.add_vertex(1);
317+
/// let v2 = graph.add_vertex(2);
318+
///
319+
/// // Adding an edge is idempotent
320+
/// graph.add_edge_check_cycle(&v1, &v2);
321+
/// graph.add_edge_check_cycle(&v1, &v2);
322+
/// graph.add_edge_check_cycle(&v1, &v2);
323+
///
324+
/// // Fails on adding an edge which creates
325+
/// // a cycle in the graph.
326+
/// assert_eq!(graph.add_edge_check_cycle(&v2, &v1), Err(GraphErr::CycleError));
327+
/// ```
328+
pub fn add_edge_check_cycle(&mut self, a: &VertexId, b: &VertexId) -> Result<(), GraphErr> {
329+
if self.has_edge(a, b) {
330+
return Ok(());
331+
}
332+
333+
self.do_add_edge(a, b, 0.0, true)
296334
}
297335

298336
/// Attempts to place a new edge in the graph.
@@ -330,7 +368,7 @@ impl<T> Graph<T> {
330368
return Err(GraphErr::InvalidWeight);
331369
}
332370

333-
self.do_add_edge(a, b, weight)
371+
self.do_add_edge(a, b, weight, false)
334372
}
335373

336374
/// Returns the weight of the specified edge
@@ -1454,7 +1492,7 @@ impl<T> Graph<T> {
14541492
}
14551493
}
14561494

1457-
fn do_add_edge(&mut self, a: &VertexId, b: &VertexId, weight: f32) -> Result<(), GraphErr> {
1495+
fn do_add_edge(&mut self, a: &VertexId, b: &VertexId, weight: f32, check_cycle: bool) -> Result<(), GraphErr> {
14581496
let id_ptr1 = if self.vertices.get(a).is_some() {
14591497
*a
14601498
} else {
@@ -1497,10 +1535,33 @@ impl<T> Graph<T> {
14971535
}
14981536

14991537
// Remove outbound vertex from roots
1500-
self.roots.remove(&b);
1538+
let was_root = self.roots.remove(&b);
15011539

15021540
// Remove inbound vertex from tips
1503-
self.tips.remove(&a);
1541+
let was_tip = self.tips.remove(&a);
1542+
1543+
let mut is_cyclic = false;
1544+
1545+
if check_cycle {
1546+
let mut dfs = Dfs::new(&self);
1547+
is_cyclic = dfs.is_cyclic();
1548+
}
1549+
1550+
// Roll-back changes if cycle check succeeds
1551+
if is_cyclic {
1552+
// Remove from edge table
1553+
self.remove_edge(a, b);
1554+
1555+
if was_root {
1556+
self.roots.insert(b.clone());
1557+
}
1558+
1559+
if was_tip {
1560+
self.tips.insert(a.clone());
1561+
}
1562+
1563+
return Err(GraphErr::CycleError);
1564+
}
15041565

15051566
Ok(())
15061567
}
@@ -1698,4 +1759,33 @@ mod tests {
16981759
}
16991760
}
17001761
}
1762+
1763+
#[test]
1764+
fn test_add_edge_cycle_check() {
1765+
let mut graph: Graph<usize> = Graph::new();
1766+
1767+
// Id of vertex that is not place in the graph
1768+
let id = VertexId::random();
1769+
1770+
let v1 = graph.add_vertex(1);
1771+
let v2 = graph.add_vertex(2);
1772+
1773+
// Adding an edge is idempotent
1774+
graph.add_edge_check_cycle(&v1, &v2).unwrap();
1775+
graph.add_edge_check_cycle(&v1, &v2).unwrap();
1776+
graph.add_edge_check_cycle(&v1, &v2).unwrap();
1777+
1778+
let mut graph2 = graph.clone();
1779+
1780+
// Fails on adding an edge which creates
1781+
// a cycle in the graph.
1782+
assert_eq!(graph2.add_edge_check_cycle(&v2, &v1), Err(GraphErr::CycleError));
1783+
1784+
// Check that the graph state has rolled back
1785+
assert_eq!(graph.edges, graph2.edges);
1786+
assert_eq!(graph.roots, graph2.roots);
1787+
assert_eq!(graph.tips, graph2.tips);
1788+
assert_eq!(graph.inbound_table, graph2.inbound_table);
1789+
assert_eq!(graph.outbound_table, graph2.outbound_table);
1790+
}
17011791
}

0 commit comments

Comments
 (0)