Skip to content

Commit 069610b

Browse files
authored
Merge pull request #46 from purpleprotocol/feature/42/insert-edge-cycle-check
Added Graph::add_edge_check_cycle() method
2 parents e3375cb + 7ca0967 commit 069610b

3 files changed

Lines changed: 142 additions & 8 deletions

File tree

.travis.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ git:
1515
depth: 10
1616

1717
before_script:
18-
- chmod +x benches-compare.sh
18+
- if [ "$TRAVIS_RUST_VERSION" == "nightly" ]; then chmod +x benches-compare.sh; fi
1919

2020
script:
2121
- cargo build --verbose --all --features "dot"
@@ -28,4 +28,4 @@ script:
2828
fi
2929

3030
after_script:
31-
- ./benches-compare.sh
31+
- if [ "$TRAVIS_RUST_VERSION" == "nightly" ]; ./benches-compare.sh; fi

benches/benchmark.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1073,6 +1073,44 @@ fn bench_others(c: &mut Criterion) {
10731073
}
10741074
})
10751075
});
1076+
c.bench_function("add_edge_cycle_check_10", |b| {
1077+
let mut graph: Graph<usize> = Graph::new();
1078+
b.iter(|| {
1079+
let mut v1 = graph.add_vertex(0);
1080+
1081+
for i in 1..=10 {
1082+
let v2 = graph.add_vertex(i);
1083+
graph.add_edge_check_cycle(&v1, &v2);
1084+
v1 = v2.clone();
1085+
}
1086+
})
1087+
});
1088+
1089+
c.bench_function("add_edge_cycle_check_100", |b| {
1090+
let mut graph: Graph<usize> = Graph::new();
1091+
b.iter(|| {
1092+
let mut v1 = graph.add_vertex(0);
1093+
1094+
for i in 1..=100 {
1095+
let v2 = graph.add_vertex(i);
1096+
graph.add_edge_check_cycle(&v1, &v2);
1097+
v1 = v2.clone();
1098+
}
1099+
})
1100+
});
1101+
#[cfg(feature = "sbench")]
1102+
c.bench_function("add_edge_cycle_check_m", |b| {
1103+
let mut graph: Graph<usize> = Graph::new();
1104+
b.iter(|| {
1105+
let mut v1 = graph.add_vertex(0);
1106+
1107+
for i in 1..=10_000_000 {
1108+
let v2 = graph.add_vertex(i);
1109+
graph.add_edge_check_cycle(&v1, &v2);
1110+
v1 = v2.clone();
1111+
}
1112+
})
1113+
});
10761114
c.bench_function("add_vertex_10", |b| {
10771115
let mut graph: Graph<usize> = Graph::new();
10781116
b.iter(|| {

src/graph.rs

Lines changed: 102 additions & 6 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
@@ -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

Comments
 (0)