@@ -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
@@ -1284,7 +1322,7 @@ impl<T> Graph<T> {
12841322
12851323 #[ cfg( feature = "dot" ) ]
12861324 /// Creates a file with the dot representation of the graph.
1287- /// This method requires the `dot` feature.
1325+ /// This method requires the `dot` crate feature.
12881326 ///
12891327 /// ## Example
12901328 /// ```rust
@@ -1332,6 +1370,8 @@ impl<T> Graph<T> {
13321370
13331371 #[ cfg( feature = "dot" ) ]
13341372 /// Labels the vertex with the given id. Returns the old label if successful.
1373+ ///
1374+ /// This method requires the `dot` crate feature.
13351375 ///
13361376 /// ## Example
13371377 /// ```rust
@@ -1365,6 +1405,8 @@ impl<T> Graph<T> {
13651405
13661406 #[ cfg( feature = "dot" ) ]
13671407 /// Retrieves the label of the vertex with the given id.
1408+ ///
1409+ /// This method requires the `dot` crate feature.
13681410 ///
13691411 /// This function will return a default label if no label is set. Returns
13701412 /// `None` if there is no vertex associated with the given id in the graph.
@@ -1399,6 +1441,8 @@ impl<T> Graph<T> {
13991441
14001442 #[ cfg( feature = "dot" ) ]
14011443 /// Maps each label that is placed on a vertex to a new label.
1444+ ///
1445+ /// This method requires the `dot` crate feature.
14021446 ///
14031447 /// ```rust
14041448 /// use std::collections::HashMap;
@@ -1454,7 +1498,7 @@ impl<T> Graph<T> {
14541498 }
14551499 }
14561500
1457- fn do_add_edge ( & mut self , a : & VertexId , b : & VertexId , weight : f32 ) -> Result < ( ) , GraphErr > {
1501+ fn do_add_edge ( & mut self , a : & VertexId , b : & VertexId , weight : f32 , check_cycle : bool ) -> Result < ( ) , GraphErr > {
14581502 let id_ptr1 = if self . vertices . get ( a) . is_some ( ) {
14591503 * a
14601504 } else {
@@ -1497,10 +1541,33 @@ impl<T> Graph<T> {
14971541 }
14981542
14991543 // Remove outbound vertex from roots
1500- self . roots . remove ( & b) ;
1544+ let was_root = self . roots . remove ( & b) ;
15011545
15021546 // Remove inbound vertex from tips
1503- self . tips . remove ( & a) ;
1547+ let was_tip = self . tips . remove ( & a) ;
1548+
1549+ let mut is_cyclic = false ;
1550+
1551+ if check_cycle {
1552+ let mut dfs = Dfs :: new ( & self ) ;
1553+ is_cyclic = dfs. is_cyclic ( ) ;
1554+ }
1555+
1556+ // Roll-back changes if cycle check succeeds
1557+ if is_cyclic {
1558+ // Remove from edge table
1559+ self . remove_edge ( a, b) ;
1560+
1561+ if was_root {
1562+ self . roots . insert ( b. clone ( ) ) ;
1563+ }
1564+
1565+ if was_tip {
1566+ self . tips . insert ( a. clone ( ) ) ;
1567+ }
1568+
1569+ return Err ( GraphErr :: CycleError ) ;
1570+ }
15041571
15051572 Ok ( ( ) )
15061573 }
@@ -1698,4 +1765,33 @@ mod tests {
16981765 }
16991766 }
17001767 }
1768+
1769+ #[ test]
1770+ fn test_add_edge_cycle_check ( ) {
1771+ let mut graph: Graph < usize > = Graph :: new ( ) ;
1772+
1773+ // Id of vertex that is not place in the graph
1774+ let id = VertexId :: random ( ) ;
1775+
1776+ let v1 = graph. add_vertex ( 1 ) ;
1777+ let v2 = graph. add_vertex ( 2 ) ;
1778+
1779+ // Adding an edge is idempotent
1780+ graph. add_edge_check_cycle ( & v1, & v2) . unwrap ( ) ;
1781+ graph. add_edge_check_cycle ( & v1, & v2) . unwrap ( ) ;
1782+ graph. add_edge_check_cycle ( & v1, & v2) . unwrap ( ) ;
1783+
1784+ let mut graph2 = graph. clone ( ) ;
1785+
1786+ // Fails on adding an edge which creates
1787+ // a cycle in the graph.
1788+ assert_eq ! ( graph2. add_edge_check_cycle( & v2, & v1) , Err ( GraphErr :: CycleError ) ) ;
1789+
1790+ // Check that the graph state has rolled back
1791+ assert_eq ! ( graph. edges, graph2. edges) ;
1792+ assert_eq ! ( graph. roots, graph2. roots) ;
1793+ assert_eq ! ( graph. tips, graph2. tips) ;
1794+ assert_eq ! ( graph. inbound_table, graph2. inbound_table) ;
1795+ assert_eq ! ( graph. outbound_table, graph2. outbound_table) ;
1796+ }
17011797}
0 commit comments