From 720f497b648536463cd6de30f5736082644c6c0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Wed, 8 Oct 2025 14:32:52 +0200 Subject: [PATCH] Add "algorithms" and "models" modules for existing code and related todos --- src/algorithms.rs | 70 ++++++++++++++++++++++ src/main.rs | 150 ++-------------------------------------------- src/models.rs | 81 +++++++++++++++++++++++++ 3 files changed, 156 insertions(+), 145 deletions(-) create mode 100644 src/algorithms.rs create mode 100644 src/models.rs diff --git a/src/algorithms.rs b/src/algorithms.rs new file mode 100644 index 0000000..f263163 --- /dev/null +++ b/src/algorithms.rs @@ -0,0 +1,70 @@ +use std::cmp::Ordering; +use std::collections::BinaryHeap; + +use crate::models::Graph; +use crate::models::Vertex; + +#[derive(PartialEq, Eq)] +struct DistanceOrderedVertex { + distance: u32, + vertex: Vertex, +} + +impl PartialOrd for DistanceOrderedVertex { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.distance.cmp(&other.distance)) + } +} + +impl Ord for DistanceOrderedVertex { + fn cmp(&self, other: &Self) -> Ordering { + other.distance.cmp(&self.distance) + } +} + +// 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>) { + 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); + heap.push(DistanceOrderedVertex { + vertex: source, + distance: 0, + }); + while let Some(v) = heap.pop() { + // TODO: Simplify with a neighbor iterator. + // Finds the first neighbor of v. + let Some(mut incidence) = graph.incidence_headers[v.vertex.0].first_incidence else { + break; + }; + loop { + // Processes one neighbor of v. + let neighbor = graph.incidence_vertices[incidence.0]; + // 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] { + None => true, + Some(old_distance) if old_distance > new_distance => true, + _ => false, + } { + distances[neighbor.0] = Some(new_distance); + predecessors[neighbor.0] = Some(v.vertex); + heap.push(DistanceOrderedVertex { + vertex: neighbor, + distance: new_distance, + }); + } + + // Finds next neighbor of v. + let Some(next) = graph.next_incidences[incidence.0] else { + break; + }; + incidence = next; + } + } + (distances, predecessors) +} diff --git a/src/main.rs b/src/main.rs index 45f0f33..bc1306a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,148 +1,8 @@ -use std::cmp::Ordering; -use std::collections::BinaryHeap; +mod algorithms; +mod models; -#[derive(Copy, Clone, PartialEq, Eq, Debug)] -struct Vertex(usize); - -#[derive(Copy, Clone, PartialEq, Eq)] -struct Incidence(usize); - -struct VertexIncidenceHeader { - incidence_count: usize, - first_incidence: Option, -} - -// TODO: Visibility of Graph members. -struct Graph { - // TODO: Index 'incidence_headers' by a 'Vertex' instead of 'Vertex.0'? - incidence_headers: Vec, - next_incidences: Vec>, - incidence_vertices: Vec, -} - -// TODO: Add iterator of vertices. -// TODO: Add iterator of edges. -// TODO: Add iterator of vertex neighbors, see 'fn add_adjacent()'. -impl Graph { - fn vertex_count(&self) -> usize { - self.incidence_headers.len() - } - - fn edge_count(&self) -> usize { - self.next_incidences.len() / 2 - } - - fn degree(&self, v: Vertex) -> usize { - self.incidence_headers[v.0].incidence_count - } - - // TODO: 'fn are_adjacent()' could probably be done with a neighbor vertex iterator. - fn are_adjacent(&self, v1: Vertex, v2: Vertex) -> bool { - let Some(mut incidence) = self.incidence_headers[v1.0].first_incidence else { - return false; - }; - loop { - if self.incidence_vertices[incidence.0] == v2 { - return true; - } - let Some(next) = self.next_incidences[incidence.0] else { - return false; - }; - incidence = next; - } - } - - // TODO: 'fn add_vertex()' should not be publicly accessible for all graph implementations. - // TODO: 'fn add_vertex()' needs variants for custom vertex data. - fn add_vertex(&mut self) -> Vertex { - self.incidence_headers.push(VertexIncidenceHeader { - incidence_count: 0, - first_incidence: None, - }); - Vertex(self.incidence_headers.len() - 1) - } - - // TODO: 'fn add_edge()' should not be publicly accessible for all graph implementations. - // TODO: 'fn add_edge()' needs variants for custom edge data. - 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.incidence_vertices.push(v2); - self.next_incidences - .push(self.incidence_headers[v1.0].first_incidence.take()); - self.incidence_headers[v1.0].incidence_count += 1; - self.incidence_headers[v1.0].first_incidence = - Some(Incidence(self.incidence_vertices.len() - 1)); - } -} - -#[derive(PartialEq, Eq)] -struct DistanceOrderedVertex { - distance: u32, - vertex: Vertex, -} - -impl PartialOrd for DistanceOrderedVertex { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.distance.cmp(&other.distance)) - } -} - -impl Ord for DistanceOrderedVertex { - fn cmp(&self, other: &Self) -> Ordering { - other.distance.cmp(&self.distance) - } -} - -// TODO: Maybe introduce a return struct type for Dijkstra's algorithm? -// TODO: Maybe variant of Dijkstra's algorithm without predecessors? -fn dijkstra(graph: &Graph, source: Vertex) -> (Vec>, Vec>) { - 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); - heap.push(DistanceOrderedVertex { - vertex: source, - distance: 0, - }); - while let Some(v) = heap.pop() { - // TODO: Simplify with a neighbor iterator. - // Finds the first neighbor of v. - let Some(mut incidence) = graph.incidence_headers[v.vertex.0].first_incidence else { - break; - }; - loop { - // Processes one neighbor of v. - let neighbor = graph.incidence_vertices[incidence.0]; - // 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] { - None => true, - Some(old_distance) if old_distance > new_distance => true, - _ => false, - } { - distances[neighbor.0] = Some(new_distance); - predecessors[neighbor.0] = Some(v.vertex); - heap.push(DistanceOrderedVertex { - vertex: neighbor, - distance: new_distance, - }); - } - - // Finds next neighbor of v. - let Some(next) = graph.next_incidences[incidence.0] else { - break; - }; - incidence = next; - } - } - (distances, predecessors) -} +use models::Graph; +use models::Vertex; fn main() { // TODO: Move this graph example into one or more tests. @@ -221,7 +81,7 @@ fn main() { ); } - let (distances, predecessors) = dijkstra(&graph, vertices[0]); + let (distances, predecessors) = algorithms::dijkstra(&graph, vertices[0]); let expected_distances_from_v0 = [ Some(0), Some(1), diff --git a/src/models.rs b/src/models.rs new file mode 100644 index 0000000..4847121 --- /dev/null +++ b/src/models.rs @@ -0,0 +1,81 @@ +// TODO: Vertex value must not be public. +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub struct Vertex(pub usize); + +// TODO: Incidence value must not be public. +// TODO: Incidence must not be public. +#[derive(Copy, Clone, PartialEq, Eq)] +pub struct Incidence(pub usize); + +// TODO: VertexIncidenceHeader and its fields must not be public, but without iterators there is currently no other way to access vertex incidences. +pub struct VertexIncidenceHeader { + incidence_count: usize, + pub first_incidence: Option, +} + +// TODO: Graph fields must not be public. +pub struct Graph { + // TODO: Index 'incidence_headers' by a 'Vertex' instead of 'Vertex.0'? + pub incidence_headers: Vec, + pub next_incidences: Vec>, + pub incidence_vertices: Vec, +} + +// TODO: Add iterator of vertices. +// TODO: Add iterator of edges. +// TODO: Add iterator of vertex neighbors, see 'fn add_adjacent()'. +impl Graph { + pub fn vertex_count(&self) -> usize { + self.incidence_headers.len() + } + + pub fn edge_count(&self) -> usize { + self.next_incidences.len() / 2 + } + + pub fn degree(&self, v: Vertex) -> usize { + self.incidence_headers[v.0].incidence_count + } + + // TODO: 'fn are_adjacent()' could probably be done with a neighbor vertex iterator. + pub fn are_adjacent(&self, v1: Vertex, v2: Vertex) -> bool { + let Some(mut incidence) = self.incidence_headers[v1.0].first_incidence else { + return false; + }; + loop { + if self.incidence_vertices[incidence.0] == v2 { + return true; + } + let Some(next) = self.next_incidences[incidence.0] else { + return false; + }; + incidence = next; + } + } + + // TODO: 'fn add_vertex()' should not be publicly accessible for all graph implementations. + // TODO: 'fn add_vertex()' needs variants for custom vertex data. + pub fn add_vertex(&mut self) -> Vertex { + self.incidence_headers.push(VertexIncidenceHeader { + incidence_count: 0, + first_incidence: None, + }); + Vertex(self.incidence_headers.len() - 1) + } + + // TODO: 'fn add_edge()' should not be publicly accessible for all graph implementations. + // 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.incidence_vertices.push(v2); + self.next_incidences + .push(self.incidence_headers[v1.0].first_incidence.take()); + self.incidence_headers[v1.0].incidence_count += 1; + self.incidence_headers[v1.0].first_incidence = + Some(Incidence(self.incidence_vertices.len() - 1)); + } +}