From da9ba7b0ad37ddbec1c6d9b02aa2e5d8dd0ea4ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Wed, 22 Apr 2026 14:26:35 +0200 Subject: [PATCH] Add GraphTopology trait and use it to interface graph model with algorithm --- src/algorithms.rs | 31 ++++++++++-------- src/lib.rs | 1 + src/models.rs | 82 ++++++++++++++++++++++++++--------------------- src/traits.rs | 12 +++++++ tests/dijkstra.rs | 6 ++-- 5 files changed, 79 insertions(+), 53 deletions(-) create mode 100644 src/traits.rs diff --git a/src/algorithms.rs b/src/algorithms.rs index 831d645..3bb54e9 100644 --- a/src/algorithms.rs +++ b/src/algorithms.rs @@ -1,22 +1,21 @@ use std::cmp::Ordering; use std::collections::BinaryHeap; -use crate::models::Graph; -use crate::models::Vertex; +use crate::traits::GraphTopology; #[derive(PartialEq, Eq)] -struct DistanceOrderedVertex { +struct DistanceOrderedVertex { distance: u32, - vertex: Vertex, + vertex: V, } -impl PartialOrd for DistanceOrderedVertex { +impl PartialOrd for DistanceOrderedVertex { fn partial_cmp(&self, other: &Self) -> Option { - Some(self.distance.cmp(&other.distance)) + Some(self.cmp(other)) } } -impl Ord for DistanceOrderedVertex { +impl Ord for DistanceOrderedVertex { fn cmp(&self, other: &Self) -> Ordering { other.distance.cmp(&self.distance) } @@ -24,28 +23,34 @@ impl Ord for DistanceOrderedVertex { // TODO: Maybe introduce a return struct type for Dijkstra's algorithm? // TODO: Maybe variant of Dijkstra's algorithm without predecessors? -pub fn dijkstra(graph: &Graph, source: Vertex) -> (Vec>, Vec>) { +// TODO: Replace Vec storage with VertexMap, this should make the bound on G::Vertex here obsolete. +pub fn dijkstra(graph: &G, source: G::Vertex) -> (Vec>, Vec>) +where + G: GraphTopology, + G::Vertex: Into, +{ let mut distances = vec![None; graph.vertex_count()]; let mut predecessors = vec![None; graph.vertex_count()]; let mut heap = BinaryHeap::new(); - distances[source.0] = Some(0); + distances[source.into()] = Some(0); heap.push(DistanceOrderedVertex { vertex: source, distance: 0, }); + while let Some(v) = heap.pop() { for neighbor in graph.neighbors(v.vertex) { // TODO: Add a way to provide custom edge weights for Dijkstra's algorithm. let edge_weight = 1; - let new_distance = distances[v.vertex.0].unwrap() + edge_weight; - if match distances[neighbor.0] { + let new_distance = distances[v.vertex.into()].unwrap() + edge_weight; + if match distances[neighbor.into()] { None => true, Some(old_distance) if old_distance > new_distance => true, _ => false, } { - distances[neighbor.0] = Some(new_distance); - predecessors[neighbor.0] = Some(v.vertex); + distances[neighbor.into()] = Some(new_distance); + predecessors[neighbor.into()] = Some(v.vertex); heap.push(DistanceOrderedVertex { vertex: neighbor, distance: new_distance, diff --git a/src/lib.rs b/src/lib.rs index 67722ce..2c999c7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,2 +1,3 @@ pub mod algorithms; pub mod models; +pub mod traits; diff --git a/src/models.rs b/src/models.rs index 942cc8d..9aeb4e5 100644 --- a/src/models.rs +++ b/src/models.rs @@ -1,6 +1,13 @@ -// TODO: Vertex value must not be public. +use crate::traits::GraphTopology; + #[derive(Copy, Clone, PartialEq, Eq, Debug)] -pub struct Vertex(pub usize); +pub struct Vertex(usize); + +impl From for usize { + fn from(v: Vertex) -> usize { + v.0 + } +} #[derive(Copy, Clone, PartialEq, Eq)] struct Incidence(usize); @@ -14,8 +21,7 @@ struct IncidenceEntry { next: Option, neighbor: Vertex, } - -pub struct VertexNeighborIterator<'a> { +struct VertexNeighborIterator<'a> { graph: &'a Graph, incidence: Option, } @@ -50,37 +56,6 @@ impl Graph { } } - pub fn vertex_count(&self) -> usize { - self.vertices.len() - } - - pub fn edge_count(&self) -> usize { - self.incidences.len() / 2 - } - - pub fn degree(&self, v: Vertex) -> usize { - self.vertices[v.0].incidence_count - } - - pub fn are_adjacent(&self, v1: Vertex, v2: Vertex) -> bool { - self.neighbors(v1).find(|&x| x == v2).is_some() - } - - // TODO: 'fn add_vertex()' needs variants for custom vertex data. - pub fn add_vertex(&mut self) -> Vertex { - self.vertices.push(VertexIncidenceHeader { - incidence_count: 0, - first_incidence: None, - }); - Vertex(self.vertices.len() - 1) - } - - // TODO: 'fn add_edge()' needs variants for custom edge data. - pub fn add_edge(&mut self, v1: Vertex, v2: Vertex) { - self.add_incidence(v1, v2); - self.add_incidence(v2, v1); - } - fn add_incidence(&mut self, v1: Vertex, v2: Vertex) { self.incidences.push(IncidenceEntry { next: self.vertices[v1.0].first_incidence.take(), @@ -89,17 +64,50 @@ impl Graph { self.vertices[v1.0].incidence_count += 1; self.vertices[v1.0].first_incidence = Some(Incidence(self.incidences.len() - 1)); } +} - pub fn vertices(&self) -> impl Iterator { +impl GraphTopology for Graph { + type Vertex = Vertex; + + fn vertex_count(&self) -> usize { + self.vertices.len() + } + + fn edge_count(&self) -> usize { + self.incidences.len() / 2 + } + + fn degree(&self, v: Self::Vertex) -> usize { + self.vertices[v.0].incidence_count + } + + fn are_adjacent(&self, v1: Self::Vertex, v2: Self::Vertex) -> bool { + self.neighbors(v1).find(|&x| x == v2).is_some() + } + + fn vertices(&self) -> impl Iterator { (0..self.vertices.len()).map(Vertex) } - pub fn neighbors(&self, v: Vertex) -> impl Iterator { + fn neighbors(&self, v: Self::Vertex) -> impl Iterator { VertexNeighborIterator { graph: self, incidence: self.vertices[v.0].first_incidence, } } + + fn add_vertex(&mut self) -> Self::Vertex { + self.vertices.push(VertexIncidenceHeader { + incidence_count: 0, + first_incidence: None, + }); + Vertex(self.vertices.len() - 1) + } + + fn add_edge(&mut self, v1: Self::Vertex, v2: Self::Vertex) { + self.add_incidence(v1, v2); + self.add_incidence(v2, v1); + } } #[cfg(test)] diff --git a/src/traits.rs b/src/traits.rs new file mode 100644 index 0000000..8a06dec --- /dev/null +++ b/src/traits.rs @@ -0,0 +1,12 @@ +pub trait GraphTopology { + type Vertex: Copy + Eq; + + fn vertex_count(&self) -> usize; + fn edge_count(&self) -> usize; + fn degree(&self, v: Self::Vertex) -> usize; + fn are_adjacent(&self, v1: Self::Vertex, v2: Self::Vertex) -> bool; + fn vertices(&self) -> impl Iterator; + fn neighbors(&self, v: Self::Vertex) -> impl Iterator; + fn add_vertex(&mut self) -> Self::Vertex; + fn add_edge(&mut self, v1: Self::Vertex, v2: Self::Vertex); +} diff --git a/tests/dijkstra.rs b/tests/dijkstra.rs index ba953e9..6fe87d0 100644 --- a/tests/dijkstra.rs +++ b/tests/dijkstra.rs @@ -1,6 +1,6 @@ use grapherity::algorithms; use grapherity::models::Graph; -use grapherity::models::Vertex; +use grapherity::traits::GraphTopology; #[test] fn dijkstra_single_vertex() { @@ -107,9 +107,9 @@ fn dijkstra() { } } -fn make_test_graph() -> (Graph, [Vertex; 10]) { +fn make_test_graph() -> (Graph, [::Vertex; 10]) { let mut graph = Graph::new(); - let vertices: [Vertex; 10] = core::array::from_fn(|_| graph.add_vertex()); + let vertices = core::array::from_fn::<_, 10, _>(|_| graph.add_vertex()); graph.add_edge(vertices[0], vertices[1]); graph.add_edge(vertices[1], vertices[2]); graph.add_edge(vertices[1], vertices[3]);