Fix IncidenceCursor for AppendGraph and Graph, and add tests

This commit is contained in:
2026-07-17 22:06:28 +02:00
parent 5fa415841e
commit 3fa4b66981
3 changed files with 212 additions and 20 deletions
+3 -1
View File
@@ -34,7 +34,9 @@ pub struct AppendGraphIncidenceCursor {
impl IncidenceCursor<AppendGraph> for AppendGraphIncidenceCursor {
fn next(&mut self, graph: &AppendGraph) -> Option<(Vertex, Edge)> {
graph.step_incidence(&mut self.incidence)
graph
.step_incidence(&mut self.incidence)
.map(|(v, e)| (v, e.normalize()))
}
}
+12 -4
View File
@@ -43,9 +43,12 @@ pub struct GraphIncidenceCursor {
impl IncidenceCursor<Graph> for GraphIncidenceCursor {
fn next(&mut self, graph: &Graph) -> Option<(Vertex, Edge)> {
graph
.step_incidence(&mut self.incidence)
.map(|(vs, e)| (graph.vertices.get_idx(vs.0).unwrap(), e))
graph.step_incidence(&mut self.incidence).map(|(vs, e)| {
(
graph.vertices.get_idx(vs.0).unwrap(),
graph.normalize_edge(e),
)
})
}
}
@@ -140,7 +143,12 @@ impl Graph {
}
fn normalize_edge(&self, e: Edge) -> Edge {
self.incidences.get_idx(e.arr_idx() & !1).unwrap()
let i = e.arr_idx();
if i & 1 == 0 {
e
} else {
self.incidences.get_idx(i ^ 1).unwrap()
}
}
}
+197 -15
View File
@@ -298,7 +298,7 @@ macro_rules! graph_topology_tests {
assert_eq!(
vertices.iter().filter(|&x| *x == v).count(),
1,
"unexpected vertex {v:?} from the iterator"
"unexpected vertex {v:?} from iterator"
);
}
}
@@ -339,7 +339,7 @@ macro_rules! graph_topology_tests {
.iter()
.position(|w| *w == v)
.expect(&format!(
"unexpected adjacent vertex {v:?} of {:?} from the iterator",
"unexpected adjacent vertex {v:?} of {:?} from iterator",
vertices[4]
));
expected_adjacency.swap_remove(i);
@@ -347,7 +347,7 @@ macro_rules! graph_topology_tests {
assert_eq!(
expected_adjacency.len(),
0,
"expected adjacent vertices {:?} of {:?} were not matched by the iterator",
"expected adjacent vertices {:?} of {:?} were not matched by iterator",
expected_adjacency,
vertices[4]
);
@@ -484,7 +484,7 @@ macro_rules! graph_topology_tests {
assert_eq!(
edges.iter().filter(|&&(f, _, _)| f == e).count(),
1,
"unexpected edge {e:?} from the iterator"
"unexpected edge {e:?} from iterator"
);
}
}
@@ -518,14 +518,14 @@ macro_rules! graph_topology_tests {
.iter()
.position(|f| *f == e)
.expect(&format!(
"unexpected incident edge {e:?} of vertex {:?} from the iterator",
"unexpected incident edge {e:?} of vertex {:?} from iterator",
vertices[i]
));
expected.swap_remove(pos);
}
assert!(
expected.is_empty(),
"expected incident edges {:?} of vertex {:?} were not matched by the iterator",
"expected incident edges {:?} of vertex {:?} were not matched by iterator",
expected,
vertices[i]
);
@@ -572,7 +572,7 @@ macro_rules! graph_topology_tests {
}
assert!(
expected.is_empty(),
"expected incident edges {:?} of vertex {:?} were not matched by the iterator",
"expected incident edges {:?} of vertex {:?} were not matched by iterator",
expected,
vertices[i]
);
@@ -608,14 +608,14 @@ macro_rules! graph_topology_tests {
.iter()
.position(|(v, e)| *v == incidence.0 && *e == incidence.1)
.expect(&format!(
"unexpected incidence {incidence:?} of vertex {:?} from the iterator",
"unexpected incidence {incidence:?} of vertex {:?} from iterator",
vertices[i]
));
expected.swap_remove(pos);
}
assert!(
expected.is_empty(),
"expected incidences {:?} of vertex {:?} were not matched by the iterator",
"expected incidences {:?} of vertex {:?} were not matched by iterator",
expected,
vertices[i]
);
@@ -710,6 +710,131 @@ macro_rules! graph_topology_tests {
}
}
#[test]
fn incidence_cursor_empty() {
use $crate::traits::{GraphTopology, IncidenceCursor};
let mut graph = <$T>::new();
let v = graph.add_vertex();
let mut cursor = graph.incidence_cursor(v);
assert_eq!(
cursor.next(&graph),
None,
"incidence cursor of vertex with degree 0 should immediately be exhausted"
);
}
#[test]
fn incidence_cursor() {
use $crate::traits::{GraphTopology, IncidenceCursor};
let (graph, vertices, _, incidences) = make_test_graph();
for i in 0..10 {
let mut expected = incidences[i].clone();
let mut cursor = graph.incidence_cursor(vertices[i]);
while let Some(incidence) = cursor.next(&graph) {
let pos = expected
.iter()
.position(|(v, e)| *v == incidence.0 && *e == incidence.1)
.expect(&format!(
"unexpected incidence {incidence:?} of vertex {:?} from cursor",
vertices[i]
));
expected.swap_remove(pos);
}
assert!(
expected.is_empty(),
"expected incidences {:?} of vertex {:?} were not matched by cursor",
expected,
vertices[i]
);
}
}
#[test]
fn incidence_cursor_loop_edge() {
use $crate::traits::{GraphTopology, IncidenceCursor};
let mut graph = <$T>::new();
let v = graph.add_vertex();
let e = graph.add_edge(v, v);
let mut cursor = graph.incidence_cursor(v);
assert_eq!(
cursor.next(&graph),
Some((v, e)),
"vertex should be adjacent to itself"
);
assert_eq!(
cursor.next(&graph),
Some((v, e)),
"vertex should be adjacent to itself twice"
);
assert_eq!(
cursor.next(&graph),
None,
"too many incidences from cursor"
);
}
#[test]
fn incidence_cursor_multiple_edges() {
use $crate::traits::{GraphTopology, IncidenceCursor};
let k = 3;
let mut graph = <$T>::new();
let vertices = [graph.add_vertex(), graph.add_vertex()];
let mut edges = Vec::new();
for _ in 0..k {
edges.push(graph.add_edge(vertices[0], vertices[1]));
}
for i in 0..2 {
let mut cursor = graph.incidence_cursor(vertices[i]);
for j in 0..k {
let current = cursor.next(&graph).expect(&format!(
"incidence {j} missing, expected {k} incidences for vertex {:?}",
vertices[i]
));
assert_eq!(
current.0,
vertices[1 - i],
"unexpected adjacent vertex of vertex {:?} in incidence {j}",
vertices[i]
);
assert_eq!(
edges.iter().filter(|e| **e == current.1).count(),
1,
"unexpected incident edge {:?} of vertex {:?}",
current.1,
vertices[i],
);
}
assert_eq!(
cursor.next(&graph),
None,
"too many incidences of {:?} from cursor",
vertices[i]
);
}
}
#[test]
fn incidence_cursor_copy() {
use $crate::traits::{GraphTopology, IncidenceCursor};
// Constructs a graph with two vertices connected to `v`.
let mut graph = <$T>::new();
let v = graph.add_vertex();
for _ in 0..2 {
let u = graph.add_vertex();
graph.add_edge(u, v);
}
let mut c1 = graph.incidence_cursor(v);
assert!(c1.next(&graph).is_some(), "expected first incidence");
// Copies cursor mid-traversal.
let mut c2 = c1;
// Continues iteration with original cursor.
assert!(c1.next(&graph).is_some(), "expected second incidence from original cursor");
assert!(c1.next(&graph).is_none(), "expected original cursor to be exhausted");
// Replays from the copy point with the copied cursor.
assert!(c2.next(&graph).is_some(), "expected second incidence from copied cursor");
assert!(c2.next(&graph).is_none(), "expected copied cursor to be exhausted");
}
#[test]
fn incident_vertices_incidences_consistency() {
use $crate::traits::GraphTopology;
@@ -1103,8 +1228,7 @@ macro_rules! graph_topology_deletion_tests {
for i in 0..10 {
let mut expected: Vec<_> = incidences[i]
.iter()
.filter(|(_, e)| *e != edges[2].0)
.map(|(_, e)| *e)
.filter_map(|(_, e)| (*e != edges[2].0).then_some(*e))
.collect();
assert_eq!(
graph.incident_edges(vertices[i]).count(),
@@ -1137,8 +1261,7 @@ macro_rules! graph_topology_deletion_tests {
for i in [0, 1, 3, 4, 5, 6, 7, 8, 9] {
let mut expected: Vec<_> = incidences[i]
.iter()
.filter(|(v, _)| *v != vertices[2])
.map(|(_, e)| *e)
.filter_map(|(v, e)| (*v != vertices[2]).then_some(*e))
.collect();
assert_eq!(
graph.incident_edges(vertices[i]).count(),
@@ -1213,14 +1336,73 @@ macro_rules! graph_topology_deletion_tests {
.iter()
.position(|(u, e)| *u == incidence.0 && *e == incidence.1)
.expect(&format!(
"unexpected incidence {incidence:?} of vertex {:?} after delete",
"unexpected incidence {incidence:?} of vertex {:?} from iterator after delete",
v
));
expected.swap_remove(pos);
}
assert!(
expected.is_empty(),
"expected incidences {:?} of vertex {:?} not matched after delete",
"expected incidences {:?} of vertex {:?} not matched by iterator after delete",
expected,
v
);
}
#[test]
fn incidence_cursor_after_delete_vertex() {
use $crate::traits::GraphTopologyDeletion;
let (mut graph, vertices, _, incidences) = make_test_graph();
graph.delete_vertex(vertices[2]);
for i in [0, 1, 3, 4, 5, 6, 7, 8, 9] {
let remaining = incidences[i]
.iter()
.filter(|(v, _)| *v != vertices[2])
.cloned()
.collect();
assert_vertex_incidence_cursor(&graph, vertices[i], remaining);
}
}
#[test]
fn incidence_cursor_after_delete_edge() {
use $crate::traits::GraphTopologyDeletion;
let (mut graph, vertices, edges, incidences) = make_test_graph();
// Deletes the edge from vertices[1] to vertices[2].
graph.delete_edge(edges[2].0);
for i in 0..10 {
let remaining = incidences[i]
.iter()
.filter(|(_, e)| *e != edges[2].0)
.cloned()
.collect();
assert_vertex_incidence_cursor(&graph, vertices[i], remaining);
}
}
fn assert_vertex_incidence_cursor(
graph: &$T,
v: <$T as $crate::traits::GraphTopology>::Vertex,
mut expected: Vec<(
<$T as $crate::traits::GraphTopology>::Vertex,
<$T as $crate::traits::GraphTopology>::Edge,
)>,
) {
use $crate::traits::IncidenceCursor;
let mut cursor = graph.incidence_cursor(v);
while let Some(incidence) = cursor.next(graph) {
let pos = expected
.iter()
.position(|(u, e)| *u == incidence.0 && *e == incidence.1)
.expect(&format!(
"unexpected incidence {incidence:?} of vertex {:?} from cursor after delete",
v
));
expected.swap_remove(pos);
}
assert!(
expected.is_empty(),
"expected incidences {:?} of vertex {:?} not matched by cursor after delete",
expected,
v
);