@@ -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