From f862ac55ec590e9e402938a4d931b30841c556b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Wed, 13 May 2026 10:59:38 +0200 Subject: [PATCH] Add GraphTopology::incident_edges() and tests --- src/models/append_graph.rs | 8 ++ src/models/graph.rs | 4 + src/testing/graph_topology_testing.rs | 172 ++++++++++++++++++++++++++ src/traits.rs | 2 +- 4 files changed, 185 insertions(+), 1 deletion(-) diff --git a/src/models/append_graph.rs b/src/models/append_graph.rs index 4902e9b..eef3575 100644 --- a/src/models/append_graph.rs +++ b/src/models/append_graph.rs @@ -130,6 +130,14 @@ impl GraphTopology for AppendGraph { (0..self.incidences.len()).step_by(2).map(Incidence) } + fn incident_edges(&self, v: Self::Vertex) -> impl Iterator { + RawIncidenceIterator { + graph: self, + incidence: self.vertices[v.0].first_incidence, + } + .map(|(_, e)| e.normalize()) + } + fn incidences(&self, v: Self::Vertex) -> impl Iterator { RawIncidenceIterator { graph: self, diff --git a/src/models/graph.rs b/src/models/graph.rs index 70372b3..0dc4be3 100644 --- a/src/models/graph.rs +++ b/src/models/graph.rs @@ -200,6 +200,10 @@ impl GraphTopology for Graph { .map(|(i, _)| i) } + fn incident_edges(&self, v: Self::Vertex) -> impl Iterator { + self.raw_incidences(v).map(|(_, e)| self.normalize_edge(e)) + } + fn incidences(&self, v: Self::Vertex) -> impl Iterator { self.raw_incidences(v) .map(|(vs, e)| (self.vertices.get_idx(vs.0).unwrap(), self.normalize_edge(e))) diff --git a/src/testing/graph_topology_testing.rs b/src/testing/graph_topology_testing.rs index 05595a4..f8bae96 100644 --- a/src/testing/graph_topology_testing.rs +++ b/src/testing/graph_topology_testing.rs @@ -452,6 +452,92 @@ macro_rules! graph_topology_tests { } } + #[test] + fn incident_edges_empty() { + let mut graph = <$T>::new(); + let v = graph.add_vertex(); + assert_eq!( + graph.incident_edges(v).count(), + 0, + "incident edge iterator of vertex with degree 0 should have no elements" + ); + } + + #[test] + fn incident_edges() { + let (graph, vertices, _, incidences) = make_test_graph(); + for i in 0..10 { + assert_eq!( + graph.incident_edges(vertices[i]).count(), + incidences[i].len(), + "unexpected incident edge count for vertex {:?}", + vertices[i] + ); + let mut expected: Vec<_> = incidences[i].iter().map(|(_, e)| *e).collect(); + for e in graph.incident_edges(vertices[i]) { + let pos = expected + .iter() + .position(|f| *f == e) + .expect(&format!( + "unexpected incident edge {e:?} of vertex {:?} from the iterator", + vertices[i] + )); + expected.swap_remove(pos); + } + assert!( + expected.is_empty(), + "expected incident edges {:?} of vertex {:?} were not matched by the iterator", + expected, + vertices[i] + ); + } + } + + #[test] + fn incident_edges_loop_edge() { + let mut graph = <$T>::new(); + let v = graph.add_vertex(); + let e = graph.add_edge(v, v); + let mut iter = graph.incident_edges(v); + assert_eq!(iter.next(), Some(e), "loop edge should appear in incident edges"); + assert_eq!( + iter.next(), + Some(e), + "loop edge should appear twice in incident edges" + ); + assert_eq!(iter.next(), None, "too many incident edges from iterator"); + } + + #[test] + fn incident_edges_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 expected = edges.clone(); + for e in graph.incident_edges(vertices[i]) { + let pos = expected + .iter() + .position(|f| *f == e) + .expect(&format!( + "unexpected incident edge {e:?} of vertex {:?}", + vertices[i] + )); + expected.swap_remove(pos); + } + assert!( + expected.is_empty(), + "expected incident edges {:?} of vertex {:?} were not matched by the iterator", + expected, + vertices[i] + ); + } + } + #[test] fn incidences_empty() { let mut graph = <$T>::new(); @@ -591,6 +677,28 @@ macro_rules! graph_topology_tests { } } } + + #[test] + fn incident_edges_incidences_consistency() { + let (graph, _, _, _) = make_test_graph(); + for u in graph.vertices() { + let mut expected: Vec<_> = graph.incidences(u).map(|(_, e)| e).collect(); + for e in graph.incident_edges(u) { + let pos = expected + .iter() + .position(|f| *f == e) + .expect(&format!( + "incident edge {e:?} of vertex {u:?} missing from incidences" + )); + expected.swap_remove(pos); + } + assert!( + expected.is_empty(), + "incidence edges {:?} of vertex {u:?} not matched by incident_edges", + expected + ); + } + } }; } @@ -916,6 +1024,70 @@ macro_rules! graph_topology_deletion_tests { } } + #[test] + fn incident_edges_after_delete_edge() { + let (mut graph, vertices, edges, incidences) = make_test_graph(); + graph.delete_edge(edges[2].0); + for i in 0..10 { + let mut expected: Vec<_> = incidences[i] + .iter() + .filter(|(_, e)| *e != edges[2].0) + .map(|(_, e)| *e) + .collect(); + assert_eq!( + graph.incident_edges(vertices[i]).count(), + expected.len(), + "unexpected incident edge count for vertex {:?} after delete", + vertices[i] + ); + for e in graph.incident_edges(vertices[i]) { + let pos = expected.iter().position(|f| *f == e).expect(&format!( + "unexpected incident edge {e:?} of vertex {:?} after delete", + vertices[i] + )); + expected.swap_remove(pos); + } + assert!( + expected.is_empty(), + "expected incident edges {:?} of vertex {:?} not matched after delete", + expected, + vertices[i] + ); + } + } + + #[test] + fn incident_edges_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 expected: Vec<_> = incidences[i] + .iter() + .filter(|(v, _)| *v != vertices[2]) + .map(|(_, e)| *e) + .collect(); + assert_eq!( + graph.incident_edges(vertices[i]).count(), + expected.len(), + "unexpected incident edge count for vertex {:?} after delete", + vertices[i] + ); + for e in graph.incident_edges(vertices[i]) { + let pos = expected.iter().position(|f| *f == e).expect(&format!( + "unexpected incident edge {e:?} of vertex {:?} after delete", + vertices[i] + )); + expected.swap_remove(pos); + } + assert!( + expected.is_empty(), + "expected incident edges {:?} of vertex {:?} not matched after delete", + expected, + vertices[i] + ); + } + } + #[test] fn incidences_after_delete_vertex() { let (mut graph, vertices, _, incidences) = make_test_graph(); diff --git a/src/traits.rs b/src/traits.rs index d622c80..fecd07c 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -1,7 +1,6 @@ use crate::maps::{EdgeMap, VertexMap}; // TODO: Add functions to reserve memory for vertices and edges. -// TODO: Add iterator of incident edges for a vertex. // TODO: Split out GraphTopologyAddition trait. pub trait GraphTopology { type Vertex: Copy + Eq; @@ -19,6 +18,7 @@ pub trait GraphTopology { fn adjacent_vertices(&self, v: Self::Vertex) -> impl Iterator; fn incident_vertices(&self, e: Self::Edge) -> (Self::Vertex, Self::Vertex); fn edges(&self) -> impl Iterator; + fn incident_edges(&self, v: Self::Vertex) -> 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;