From 0bcd270d12bfe6dbb8f413cbd57c9d47ce6357fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Fri, 8 May 2026 13:12:23 +0200 Subject: [PATCH] Add GraphTopology::incidences(), update and add related tests --- src/models/append_graph.rs | 33 ++- src/models/graph.rs | 29 ++ src/testing/graph_topology_testing.rs | 378 +++++++++++++++++++++----- src/traits.rs | 1 + 4 files changed, 378 insertions(+), 63 deletions(-) diff --git a/src/models/append_graph.rs b/src/models/append_graph.rs index 6ac77db..02f6b5f 100644 --- a/src/models/append_graph.rs +++ b/src/models/append_graph.rs @@ -7,6 +7,12 @@ pub struct Vertex(usize); #[derive(Copy, Clone, PartialEq, Eq, Debug)] pub struct Incidence(usize); +impl Incidence { + fn normalize(&self) -> Self { + Self(self.0 & !1) + } +} + struct VertexIncidenceHeader { incidence_count: usize, first_incidence: Option, @@ -26,13 +32,29 @@ impl<'a> Iterator for AdjacentVertexIterator<'a> { type Item = Vertex; fn next(&mut self) -> Option { - let incidence = self.incidence?; - let entry = &self.graph.incidences[incidence.0]; + let current = self.incidence?; + let entry = &self.graph.incidences[current.0]; self.incidence = entry.next; Some(entry.adjacent) } } +struct IncidenceIterator<'a> { + graph: &'a AppendGraph, + incidence: Option, +} + +impl<'a> Iterator for IncidenceIterator<'a> { + type Item = (Vertex, Incidence); + + fn next(&mut self) -> Option { + let current = self.incidence?; + let entry = &self.graph.incidences[current.0]; + self.incidence = entry.next; + Some((entry.adjacent, current.normalize())) + } +} + pub struct AppendGraph { // TODO: Index 'vertices' by a 'Vertex' instead of 'Vertex.0'? vertices: Vec, @@ -117,6 +139,13 @@ impl GraphTopology for AppendGraph { (0..self.incidences.len()).step_by(2).map(Incidence) } + fn incidences(&self, v: Self::Vertex) -> impl Iterator { + IncidenceIterator { + graph: self, + incidence: self.vertices[v.0].first_incidence, + } + } + fn add_vertex(&mut self) -> Self::Vertex { self.vertices.push(VertexIncidenceHeader { incidence_count: 0, diff --git a/src/models/graph.rs b/src/models/graph.rs index 1c65694..eddaa5c 100644 --- a/src/models/graph.rs +++ b/src/models/graph.rs @@ -70,6 +70,24 @@ impl IncidentEdgeCursor { } } +struct IncidenceIterator<'a> { + graph: &'a Graph, + incidence: Option, +} + +impl<'a> Iterator for IncidenceIterator<'a> { + type Item = (Vertex, Edge); + + fn next(&mut self) -> Option { + let current = self.incidence?; + let e = self.graph.incidences.get_idx(current.0).unwrap(); + let entry = &self.graph.incidences[e]; + self.incidence = entry.next; + let v = self.graph.vertices.get_idx(entry.adjacent.0).unwrap(); + Some((v, self.graph.normalize_edge(e))) + } +} + pub struct Graph { // TODO: Arena index and generation types could be externalized to Graph. vertices: Arena, @@ -142,6 +160,10 @@ impl Graph { self.incidences[previous].next = next; } } + + fn normalize_edge(&self, e: Edge) -> Edge { + self.incidences.get_idx(e.arr_idx() & !1).unwrap() + } } impl Default for Graph { @@ -205,6 +227,13 @@ impl GraphTopology for Graph { .map(|(i, _)| i) } + fn incidences(&self, v: Self::Vertex) -> impl Iterator { + IncidenceIterator { + graph: self, + incidence: self.vertices[v].first_incidence, + } + } + fn add_vertex(&mut self) -> Self::Vertex { self.vertices.insert(VertexIncidenceHeader { incidence_count: 0, diff --git a/src/testing/graph_topology_testing.rs b/src/testing/graph_topology_testing.rs index 6316fe6..6ecd67e 100644 --- a/src/testing/graph_topology_testing.rs +++ b/src/testing/graph_topology_testing.rs @@ -6,6 +6,10 @@ macro_rules! graph_topology_test_fixtures { $T, [<$T as $crate::traits::GraphTopology>::Vertex; 10], [<$T as $crate::traits::GraphTopology>::Edge; 18], + [Vec<( + <$T as $crate::traits::GraphTopology>::Vertex, + <$T as $crate::traits::GraphTopology>::Edge, + )>; 10], ) { let mut graph = <$T>::new(); let vertices: [<$T as $crate::traits::GraphTopology>::Vertex; 10] = @@ -30,7 +34,53 @@ macro_rules! graph_topology_test_fixtures { graph.add_edge(vertices[7], vertices[8]), graph.add_edge(vertices[7], vertices[9]), ]; - (graph, vertices, edges) + let incidences: [Vec<_>; 10] = [ + vec![(vertices[1], edges[0]), (vertices[1], edges[1])], + vec![ + (vertices[0], edges[0]), + (vertices[0], edges[1]), + (vertices[2], edges[2]), + (vertices[3], edges[3]), + (vertices[4], edges[4]), + ], + vec![ + (vertices[2], edges[5]), + (vertices[2], edges[5]), + (vertices[1], edges[2]), + (vertices[4], edges[6]), + (vertices[4], edges[7]), + (vertices[5], edges[8]), + (vertices[6], edges[9]), + ], + vec![(vertices[1], edges[3]), (vertices[6], edges[10])], + vec![ + (vertices[1], edges[4]), + (vertices[2], edges[6]), + (vertices[2], edges[7]), + (vertices[4], edges[11]), + (vertices[4], edges[11]), + (vertices[7], edges[12]), + (vertices[8], edges[13]), + ], + vec![(vertices[2], edges[8]), (vertices[9], edges[14])], + vec![ + (vertices[2], edges[9]), + (vertices[3], edges[10]), + (vertices[9], edges[15]), + ], + vec![ + (vertices[4], edges[12]), + (vertices[8], edges[16]), + (vertices[9], edges[17]), + ], + vec![(vertices[4], edges[13]), (vertices[7], edges[16])], + vec![ + (vertices[5], edges[14]), + (vertices[6], edges[15]), + (vertices[7], edges[17]), + ], + ]; + (graph, vertices, edges, incidences) } }; } @@ -54,7 +104,7 @@ macro_rules! graph_topology_tests { #[test] fn vertex_count() { - let (graph, _, _) = make_test_graph(); + let (graph, _, _, _) = make_test_graph(); assert_eq!(graph.vertex_count(), 10, "unexpected vertex count"); } @@ -75,7 +125,7 @@ macro_rules! graph_topology_tests { #[test] fn edge_count() { - let (graph, _, _) = make_test_graph(); + let (graph, _, _, _) = make_test_graph(); assert_eq!(graph.edge_count(), 18, "unexpected edge count"); } @@ -88,7 +138,7 @@ macro_rules! graph_topology_tests { #[test] fn degree() { - let (graph, vertices, _) = make_test_graph(); + let (graph, vertices, _, _) = make_test_graph(); let expected_degrees = [2, 5, 7, 2, 7, 2, 3, 3, 2, 3]; for i in 0..graph.vertex_count() { assert_eq!( @@ -100,6 +150,36 @@ macro_rules! graph_topology_tests { } } + #[test] + fn loop_edge() { + let mut graph = <$T>::new(); + let v = graph.add_vertex(); + graph.add_edge(v, v); + assert_eq!(graph.vertex_count(), 1, "unexpected vertex count"); + assert_eq!(graph.edge_count(), 1, "unexpected edge count"); + assert_eq!(graph.degree(v), 2, "unexpected degree"); + assert!( + graph.are_adjacent(v, v), + "vertex with loop edge should be adjacent to itself" + ); + } + + #[test] + fn multiple_edges() { + let k = 3; + let mut graph = <$T>::new(); + let v1 = graph.add_vertex(); + let v2 = graph.add_vertex(); + for _ in 0..k { + graph.add_edge(v1, v2); + } + assert_eq!(graph.vertex_count(), 2, "unexpected vertex count"); + assert_eq!(graph.edge_count(), k, "unexpected edge count"); + assert_eq!(graph.degree(v1), k, "unexpected degree of vertex {v1:?}"); + assert_eq!(graph.degree(v2), k, "unexpected degree of vertex {v2:?}"); + assert!(graph.are_adjacent(v1, v2), "should be adjacent"); + } + #[test] fn are_adjacent_vertex_self() { let mut graph = <$T>::new(); @@ -124,7 +204,7 @@ macro_rules! graph_topology_tests { #[test] fn are_adjacent() { - let (graph, vertices, _) = make_test_graph(); + let (graph, vertices, _, _) = make_test_graph(); assert!( graph.are_adjacent(vertices[0], vertices[1]), "expected {:?} and {:?} to be adjacent", @@ -178,7 +258,7 @@ macro_rules! graph_topology_tests { #[test] fn vertices() { - let (graph, vertices, _) = make_test_graph(); + let (graph, vertices, _, _) = make_test_graph(); assert_eq!(graph.vertices().count(), 10, "unexpected vertex count"); // Expects each vertex to appear exactly once. @@ -194,9 +274,9 @@ macro_rules! graph_topology_tests { #[test] fn adjacent_vertices_empty() { let mut graph = <$T>::new(); - let vertex = graph.add_vertex(); + let v = graph.add_vertex(); assert_eq!( - graph.adjacent_vertices(vertex).count(), + graph.adjacent_vertices(v).count(), 0, "adjacent vertex iterator of vertex with degree 0 should have no elements" ); @@ -204,7 +284,7 @@ macro_rules! graph_topology_tests { #[test] fn adjacent_vertices() { - let (graph, vertices, _) = make_test_graph(); + let (graph, vertices, _, _) = make_test_graph(); // Checks adjacency of vertex 4. assert_eq!( graph.adjacent_vertices(vertices[4]).count(), @@ -240,42 +320,10 @@ macro_rules! graph_topology_tests { } #[test] - fn edges_empty() { - let graph = <$T>::new(); - assert_eq!( - graph.edges().count(), - 0, - "edge iterator of empty graph should have no elements" - ); - } - - #[test] - fn edges() { - let (graph, _, edges) = make_test_graph(); - assert_eq!(graph.edges().count(), 18, "unexpected edge count"); - - // Expects each edge to appear exactly once. - for e in graph.edges() { - assert_eq!( - edges.iter().filter(|&x| *x == e).count(), - 1, - "unexpected edge {e:?} from the iterator" - ); - } - } - - #[test] - fn loop_edge() { + fn adjacent_vertices_loop_edge() { let mut graph = <$T>::new(); let v = graph.add_vertex(); graph.add_edge(v, v); - assert_eq!(graph.vertex_count(), 1, "unexpected vertex count"); - assert_eq!(graph.edge_count(), 1, "unexpected edge count"); - assert_eq!(graph.degree(v), 2, "unexpected degree"); - assert!( - graph.are_adjacent(v, v), - "vertex with loop edge should be adjacent to itself" - ); let mut iter = graph.adjacent_vertices(v); assert_eq!(iter.next(), Some(v), "vertex should be adjacent to itself"); assert_eq!( @@ -291,35 +339,172 @@ macro_rules! graph_topology_tests { } #[test] - fn multiple_edges() { + fn adjacent_vertices_multiple_edges() { let k = 3; let mut graph = <$T>::new(); let vertices = [graph.add_vertex(), graph.add_vertex()]; for _ in 0..k { graph.add_edge(vertices[0], vertices[1]); } - assert_eq!( - graph.vertex_count(), - vertices.len(), - "unexpected vertex count" - ); - assert_eq!(graph.edge_count(), k, "unexpected edge count"); - for v in vertices { - assert_eq!(graph.degree(v), k, "unexpected degree of {v:?}"); - } - assert!( - graph.are_adjacent(vertices[0], vertices[1]), - "should be adjacent" - ); for i in 0..2 { let mut iter = graph.adjacent_vertices(vertices[i]); for j in 0..k { assert_eq!( iter.next(), Some(vertices[1 - i]), - "adjacent vertex {j} of vertex {:?} should be {:?}", + "unexpected adjacent vertex {j} of vertex {:?}", + vertices[i] + ); + } + assert_eq!( + iter.next(), + None, + "too many adjacent vertices of vertex {:?} from iterator", + vertices[i] + ); + } + } + + #[test] + fn edges_empty() { + let graph = <$T>::new(); + assert_eq!( + graph.edges().count(), + 0, + "edge iterator of empty graph should have no elements" + ); + } + + #[test] + fn edges() { + let (graph, _, edges, _) = make_test_graph(); + assert_eq!(graph.edges().count(), 18, "unexpected edge count"); + + // Expects each edge to appear exactly once. + for e in graph.edges() { + assert_eq!( + edges.iter().filter(|&x| *x == e).count(), + 1, + "unexpected edge {e:?} from the iterator" + ); + } + } + + #[test] + fn incidences_empty() { + let mut graph = <$T>::new(); + let v = graph.add_vertex(); + assert_eq!( + graph.incidences(v).count(), + 0, + "incidence iterator of vertex with degree 0 should have no elements" + ); + } + + #[test] + fn incidences() { + let (graph, vertices, _, incidences) = make_test_graph(); + for i in 0..10 { + assert_eq!( + graph.incidences(vertices[i]).count(), + incidences[i].len(), + "unexpected incidence count for vertex {:?}", + vertices[i] + ); + let mut remaining = incidences[i].clone(); + for incidence in graph.incidences(vertices[i]) { + let pos = remaining + .iter() + .position(|(v, e)| *v == incidence.0 && *e == incidence.1) + .expect(&format!( + "unexpected incidence {incidence:?} of vertex {:?} from the iterator", + vertices[i] + )); + remaining.swap_remove(pos); + } + assert!( + remaining.is_empty(), + "expected incidences {:?} of vertex {:?} were not matched by the iterator", + remaining, + vertices[i] + ); + } + } + + #[test] + fn incidences_edge_consistency() { + // For each incidence (v, e) of u, the same edge e must appear in the incidences of v. + // For loop edges (u == v), the edge must appear exactly twice in the incidences of u. + let (graph, _, _, _) = make_test_graph(); + for u in graph.vertices() { + for (v, e) in graph.incidences(u) { + if u == v { + assert_eq!( + graph.incidences(u).filter(|(_, f)| *f == e).count(), + 2, + "loop edge {e:?} should appear exactly twice in incidences of vertex {u:?}" + ); + } else { + assert!( + graph.incidences(v).any(|(w, f)| w == u && f == e), + "edge {e:?} from incidences of vertex {u:?} missing in incidences of adjacent vertex {v:?}" + ); + } + } + } + } + + #[test] + fn incidences_loop_edge() { + let mut graph = <$T>::new(); + let v = graph.add_vertex(); + let e = graph.add_edge(v, v); + let mut iter = graph.incidences(v); + assert_eq!( + iter.next(), + Some((v, e)), + "vertex should be adjacent to itself" + ); + assert_eq!( + iter.next(), + Some((v, e)), + "vertex should be adjacent to itself twice" + ); + assert_eq!( + iter.next(), + None, + "too many adjacent vertices from iterator" + ); + } + + #[test] + fn incidences_multiple_edges() { + 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 iter = graph.incidences(vertices[i]); + for j in 0..k { + let current = iter.next().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], - vertices[1 - i] ); } assert_eq!( @@ -400,7 +585,7 @@ macro_rules! graph_topology_deletion_tests { #[test] fn delete_vertex_connected() { - let (mut graph, vertices, _) = make_test_graph(); + let (mut graph, vertices, _, _) = make_test_graph(); assert_eq!( graph.vertex_count(), 10, @@ -587,7 +772,7 @@ macro_rules! graph_topology_deletion_tests { #[test] fn vertices_after_delete() { - let (mut graph, vertices, _) = make_test_graph(); + let (mut graph, vertices, _, _) = make_test_graph(); graph.delete_vertex(vertices[2]); assert_eq!( graph.vertex_count(), @@ -617,7 +802,7 @@ macro_rules! graph_topology_deletion_tests { #[test] fn edges_after_delete() { - let (mut graph, vertices, edges) = make_test_graph(); + let (mut graph, vertices, edges, _) = make_test_graph(); graph.delete_vertex(vertices[2]); assert_eq!(graph.edge_count(), 12, "unexpected edge count after delete"); assert_eq!( @@ -640,5 +825,76 @@ macro_rules! graph_topology_deletion_tests { ); } } + + #[test] + fn incidences_after_delete_vertex() { + 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 mut remaining: Vec<_> = incidences[i] + .iter() + .filter(|(v, _)| *v != vertices[2]) + .cloned() + .collect(); + assert_eq!( + graph.incidences(vertices[i]).count(), + remaining.len(), + "unexpected incidence count for vertex {:?} after delete", + vertices[i] + ); + for incidence in graph.incidences(vertices[i]) { + let pos = remaining + .iter() + .position(|(v, e)| *v == incidence.0 && *e == incidence.1) + .expect(&format!( + "unexpected incidence {incidence:?} of vertex {:?} after delete", + vertices[i] + )); + remaining.swap_remove(pos); + } + assert!( + remaining.is_empty(), + "expected incidences {:?} of vertex {:?} not matched after delete", + remaining, + vertices[i] + ); + } + } + + #[test] + fn incidences_after_delete_edge() { + let (mut graph, vertices, edges, incidences) = make_test_graph(); + // Deletes the edge from vertices[1] to vertices[2]. + graph.delete_edge(edges[2]); + for i in 0..10 { + let mut remaining: Vec<_> = incidences[i] + .iter() + .filter(|(_, e)| *e != edges[2]) + .cloned() + .collect(); + assert_eq!( + graph.incidences(vertices[i]).count(), + remaining.len(), + "unexpected incidence count for vertex {:?} after delete", + vertices[i] + ); + for incidence in graph.incidences(vertices[i]) { + let pos = remaining + .iter() + .position(|(v, e)| *v == incidence.0 && *e == incidence.1) + .expect(&format!( + "unexpected incidence {incidence:?} of vertex {:?} after delete", + vertices[i] + )); + remaining.swap_remove(pos); + } + assert!( + remaining.is_empty(), + "expected incidences {:?} of vertex {:?} not matched after delete", + remaining, + vertices[i] + ); + } + } }; } diff --git a/src/traits.rs b/src/traits.rs index 51f18cf..d38d0ad 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -19,6 +19,7 @@ pub trait GraphTopology { fn vertices(&self) -> impl Iterator; fn adjacent_vertices(&self, v: Self::Vertex) -> impl Iterator; fn edges(&self) -> impl Iterator; + fn incidences(&self, v: Self::Vertex) -> impl Iterator; fn add_vertex(&mut self) -> Self::Vertex; fn add_edge(&mut self, v1: Self::Vertex, v2: Self::Vertex) -> Self::Edge; }