15 Commits

Author SHA1 Message Date
warrence 5b30151010 Merge pull request 'Minor fixes' (#5) from fixes-minor into main
Reviewed-on: #5
2026-07-13 10:58:50 +02:00
warrence 2f549cc506 Update package version 2026-07-13 10:49:25 +02:00
warrence cf31ee2f01 Replace VertexMap/EdgeMap.len with capacity
Rename EdgeMap.len to capacity.
Add VertexMap.capacity and EdgeMap.capacity.
Deprecate VertexMap.len and EdgeMap.len.
2026-07-13 10:48:16 +02:00
warrence e58261ba47 Add BfsImplResult and DfsImplResult as named return structs 2026-07-10 13:12:30 +02:00
warrence dd0399dcdd Merge pull request 'Add prelude' (#4) from feature/prelude into main
Reviewed-on: #4
2026-07-07 11:45:41 +02:00
warrence 3c4391ac1d Update package version 2026-07-07 11:44:57 +02:00
warrence bbd9506bbc Add prelude module for common traits 2026-07-07 11:44:17 +02:00
warrence 4b3586b1eb Merge pull request 'Type additions and renames' (#3) from dev into main
Reviewed-on: #3
2026-07-06 19:13:13 +02:00
warrence c8dda39b98 Update package version 2026-07-06 18:50:22 +02:00
warrence 1446103d34 Add concrete type aliases of VertexMap and EdgeMap for Graph and AppendGraph 2026-07-06 18:49:02 +02:00
warrence 266dee783b Rename append_graph::Incidence to Edge to unify public naming 2026-07-06 17:52:42 +02:00
warrence e3a24894f6 Changed graph::Vertex and graph::Edge to be public to simplify Graph usage 2026-07-06 17:51:15 +02:00
warrence 6d2d1de8ea Hide testing macros from docs 2026-07-03 22:49:14 +02:00
warrence 2d400628f7 Re-publish AppendGraph and Graph in models 2026-07-03 21:49:28 +02:00
warrence 6a0c426c77 Rename find_path* algorithms to dfs_find_path* 2026-07-03 21:43:55 +02:00
18 changed files with 125 additions and 77 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "grapherity"
version = "0.1.0"
version = "0.2.2"
authors = ["Stefan Müller"]
edition = "2024"
rust-version = "1.85.0"
+39 -25
View File
@@ -122,12 +122,12 @@ where
G: GraphTopology,
{
let mut predecessors = graph.vertex_map(None);
let (distances, _) = bfs_impl(graph, source, |neighbor, predecessor| {
let result = bfs_impl(graph, source, |neighbor, predecessor| {
predecessors[neighbor] = Some(predecessor);
true
});
BfsResult {
distances,
distances: result.distances,
predecessors,
}
}
@@ -136,7 +136,7 @@ pub fn bfs_distances<G>(graph: &G, source: G::Vertex) -> VertexMap<G::Vertex, Op
where
G: GraphTopology,
{
bfs_impl(graph, source, |_, _| true).0
bfs_impl(graph, source, |_, _| true).distances
}
pub fn bfs_find<G>(graph: &G, source: G::Vertex, target: G::Vertex) -> Option<u32>
@@ -154,14 +154,15 @@ where
if predicate(source) {
return Some((source, 0));
}
bfs_impl(graph, source, |neighbor, _| !predicate(neighbor)).1
bfs_impl(graph, source, |neighbor, _| !predicate(neighbor)).found
}
fn bfs_impl<G, F>(
graph: &G,
source: G::Vertex,
mut on_discover: F,
) -> (VertexMap<G::Vertex, Option<u32>>, Option<(G::Vertex, u32)>)
struct BfsImplResult<V: Copy> {
distances: VertexMap<V, Option<u32>>,
found: Option<(V, u32)>,
}
fn bfs_impl<G, F>(graph: &G, source: G::Vertex, mut on_discover: F) -> BfsImplResult<G::Vertex>
where
G: GraphTopology,
F: FnMut(G::Vertex, G::Vertex) -> bool,
@@ -178,13 +179,19 @@ where
let distance = distances[v].unwrap() + 1;
distances[neighbor] = Some(distance);
if !on_discover(neighbor, v) {
return (distances, Some((neighbor, distance)));
return BfsImplResult {
distances,
found: Some((neighbor, distance)),
};
}
queue.push_back(neighbor);
}
}
}
(distances, None)
BfsImplResult {
distances,
found: None,
}
}
pub struct DfsResult<V: Copy> {
@@ -197,12 +204,12 @@ where
G: GraphTopology,
{
let mut predecessors = graph.vertex_map(None);
let (visited, _) = dfs_impl(graph, source, |neighbor, predecessor| {
let result = dfs_impl(graph, source, |neighbor, predecessor| {
predecessors[neighbor] = Some(predecessor);
true
});
DfsResult {
visited,
visited: result.visited,
predecessors,
}
}
@@ -211,7 +218,7 @@ pub fn dfs_visited<G>(graph: &G, source: G::Vertex) -> VertexMap<G::Vertex, bool
where
G: GraphTopology,
{
dfs_impl(graph, source, |_, _| true).0
dfs_impl(graph, source, |_, _| true).visited
}
pub fn dfs_find<G>(graph: &G, source: G::Vertex, target: G::Vertex) -> bool
@@ -229,14 +236,15 @@ where
if predicate(source) {
return Some(source);
}
dfs_impl(graph, source, |neighbor, _| !predicate(neighbor)).1
dfs_impl(graph, source, |neighbor, _| !predicate(neighbor)).found
}
fn dfs_impl<G, F>(
graph: &G,
source: G::Vertex,
mut on_discover: F,
) -> (VertexMap<G::Vertex, bool>, Option<G::Vertex>)
struct DfsImplResult<V: Copy> {
visited: VertexMap<V, bool>,
found: Option<V>,
}
fn dfs_impl<G, F>(graph: &G, source: G::Vertex, mut on_discover: F) -> DfsImplResult<G::Vertex>
where
G: GraphTopology,
F: FnMut(G::Vertex, G::Vertex) -> bool,
@@ -248,7 +256,10 @@ where
while let Some((v, predecessor)) = stack.pop() {
if let Some(p) = predecessor {
if !on_discover(v, p) {
return (visited, Some(v));
return DfsImplResult {
visited,
found: Some(v),
};
}
}
for neighbor in graph.adjacent_vertices(v) {
@@ -258,17 +269,20 @@ where
}
}
}
(visited, None)
DfsImplResult {
visited,
found: None,
}
}
pub fn find_path<G>(graph: &G, source: G::Vertex, target: G::Vertex) -> Option<Vec<G::Edge>>
pub fn dfs_find_path<G>(graph: &G, source: G::Vertex, target: G::Vertex) -> Option<Vec<G::Edge>>
where
G: GraphTopology,
{
find_path_where(graph, source, |v| v == target)
dfs_find_path_where(graph, source, |v| v == target)
}
pub fn find_path_where<G, P>(graph: &G, source: G::Vertex, predicate: P) -> Option<Vec<G::Edge>>
pub fn dfs_find_path_where<G, P>(graph: &G, source: G::Vertex, predicate: P) -> Option<Vec<G::Edge>>
where
G: GraphTopology,
P: Fn(G::Vertex) -> bool,
+4
View File
@@ -3,4 +3,8 @@ pub mod maps;
pub mod models;
pub mod traits;
pub mod prelude {
pub use crate::traits::{GraphTopology, GraphTopologyDeletion};
}
mod testing;
+17 -6
View File
@@ -13,11 +13,17 @@ impl<V: Copy, T: Clone> VertexMap<V, T> {
}
}
// Reads beyond len() are valid and return the default value.
pub fn len(&self) -> usize {
self.inner.len()
// Reads beyond 'capacity' are valid and return the default value.
pub fn capacity(&self) -> usize {
self.inner.capacity()
}
#[deprecated(since = "0.2.2", note = "use 'capacity' instead")]
pub fn len(&self) -> usize {
self.capacity()
}
pub fn sync<G: GraphTopology<Vertex = V>>(&mut self, graph: &G) {
self.inner.resize(graph.vertex_capacity());
}
@@ -48,9 +54,14 @@ impl<E: Copy, T: Clone> EdgeMap<E, T> {
}
}
// Reads beyond len() are valid and return the default value.
// Reads beyond 'capacity' are valid and return the default value.
pub fn capacity(&self) -> usize {
self.inner.capacity()
}
#[deprecated(since = "0.2.2", note = "use 'capacity' instead")]
pub fn len(&self) -> usize {
self.inner.len()
self.capacity()
}
pub fn sync<G: GraphTopology<Edge = E>>(&mut self, graph: &G) {
@@ -87,7 +98,7 @@ impl<E: Copy, T: Clone> EntityMap<E, T> {
}
}
pub fn len(&self) -> usize {
pub fn capacity(&self) -> usize {
self.data.len()
}
+3
View File
@@ -1,2 +1,5 @@
pub mod append_graph;
pub mod graph;
pub use append_graph::{AppendGraph, AppendGraphEdgeMap, AppendGraphVertexMap};
pub use graph::{Graph, GraphEdgeMap, GraphVertexMap};
+16 -13
View File
@@ -5,9 +5,12 @@ use crate::traits::{GraphTopology, IncidenceCursor};
pub struct Vertex(usize);
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct Incidence(usize);
pub struct Edge(usize);
impl Incidence {
pub type AppendGraphVertexMap<T> = VertexMap<Vertex, T>;
pub type AppendGraphEdgeMap<T> = EdgeMap<Edge, T>;
impl Edge {
fn normalize(&self) -> Self {
Self(self.0 & !1)
}
@@ -15,22 +18,22 @@ impl Incidence {
struct VertexIncidenceHeader {
incidence_count: usize,
first_incidence: Option<Incidence>,
first_incidence: Option<Edge>,
}
#[derive(Copy, Clone)]
struct IncidenceEntry {
next: Option<Incidence>,
next: Option<Edge>,
adjacent: Vertex,
}
#[derive(Copy, Clone)]
pub struct AppendGraphIncidenceCursor {
incidence: Option<Incidence>,
incidence: Option<Edge>,
}
impl IncidenceCursor<AppendGraph> for AppendGraphIncidenceCursor {
fn next(&mut self, graph: &AppendGraph) -> Option<(Vertex, Incidence)> {
fn next(&mut self, graph: &AppendGraph) -> Option<(Vertex, Edge)> {
graph.step_incidence(&mut self.incidence)
}
}
@@ -56,15 +59,15 @@ impl AppendGraph {
adjacent: v2,
});
self.vertices[v1.0].incidence_count += 1;
self.vertices[v1.0].first_incidence = Some(Incidence(self.incidences.len() - 1));
self.vertices[v1.0].first_incidence = Some(Edge(self.incidences.len() - 1));
}
fn raw_incidences(&self, v: Vertex) -> impl Iterator<Item = (Vertex, Incidence)> {
fn raw_incidences(&self, v: Vertex) -> impl Iterator<Item = (Vertex, Edge)> {
let mut incidence = self.vertices[v.0].first_incidence;
std::iter::from_fn(move || self.step_incidence(&mut incidence))
}
fn step_incidence(&self, incidence: &mut Option<Incidence>) -> Option<(Vertex, Incidence)> {
fn step_incidence(&self, incidence: &mut Option<Edge>) -> Option<(Vertex, Edge)> {
let current = (*incidence)?;
let entry = self.incidences[current.0];
*incidence = entry.next;
@@ -80,7 +83,7 @@ impl Default for AppendGraph {
impl GraphTopology for AppendGraph {
type Vertex = Vertex;
type Edge = Incidence;
type Edge = Edge;
type IncidenceCursor = AppendGraphIncidenceCursor;
fn vertex_count(&self) -> usize {
@@ -130,7 +133,7 @@ impl GraphTopology for AppendGraph {
}
fn edges(&self) -> impl Iterator<Item = Self::Edge> {
(0..self.incidences.len()).step_by(2).map(Incidence)
(0..self.incidences.len()).step_by(2).map(Edge)
}
fn incident_edges(&self, v: Self::Vertex) -> impl Iterator<Item = Self::Edge> {
@@ -158,7 +161,7 @@ impl GraphTopology for AppendGraph {
fn add_edge(&mut self, v1: Self::Vertex, v2: Self::Vertex) -> Self::Edge {
self.add_incidence(v1, v2);
self.add_incidence(v2, v1);
Incidence(self.incidences.len() - 2)
Edge(self.incidences.len() - 2)
}
}
@@ -175,7 +178,7 @@ mod tests {
let v1 = graph.add_vertex();
let v2 = graph.add_vertex();
let e = graph.add_edge(v1, v2);
let f = Incidence(e.0 + 1);
let f = Edge(e.0 + 1);
let (u1, u2) = graph.incident_vertices(f);
assert!(
(u1 == v1 && u2 == v2) || (u1 == v2 && u2 == v1),
+4 -2
View File
@@ -3,9 +3,11 @@ use typed_generational_arena::{Arena, Index};
use crate::maps::{EdgeMap, VertexMap};
use crate::traits::{GraphTopology, GraphTopologyDeletion, IncidenceCursor};
type Vertex = Index<VertexIncidenceHeader, usize, usize>;
pub type Vertex = Index<VertexIncidenceHeader, usize, usize>;
pub type Edge = Index<IncidenceEntry, usize, usize>;
type Edge = Index<IncidenceEntry, usize, usize>;
pub type GraphVertexMap<T> = VertexMap<Vertex, T>;
pub type GraphEdgeMap<T> = EdgeMap<Edge, T>;
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
struct VertexSlot(usize);
+1
View File
@@ -1,3 +1,4 @@
#[doc(hidden)]
#[macro_export]
macro_rules! bfs_tests {
($T:ty) => {
+1
View File
@@ -1,3 +1,4 @@
#[doc(hidden)]
#[macro_export]
macro_rules! dfs_tests {
($T:ty) => {
+1
View File
@@ -1,3 +1,4 @@
#[doc(hidden)]
#[macro_export]
macro_rules! dijkstra_tests {
($T:ty) => {
+9 -8
View File
@@ -1,3 +1,4 @@
#[doc(hidden)]
#[macro_export]
macro_rules! find_path_tests {
($T:ty) => {
@@ -7,7 +8,7 @@ macro_rules! find_path_tests {
let mut graph = <$T>::new();
let v = graph.add_vertex();
assert_eq!(
$crate::algorithms::find_path(&graph, v, v),
$crate::algorithms::dfs_find_path(&graph, v, v),
Some(vec![]),
"path from source to itself should be empty"
);
@@ -17,7 +18,7 @@ macro_rules! find_path_tests {
fn find_path_disconnected() {
let (graph, vertices) = make_test_graph_disconnected();
assert_eq!(
$crate::algorithms::find_path(&graph, vertices[0], vertices[1]),
$crate::algorithms::dfs_find_path(&graph, vertices[0], vertices[1]),
None,
"no path should exist to disconnected vertex"
);
@@ -30,7 +31,7 @@ macro_rules! find_path_tests {
let v1 = graph.add_vertex();
let v2 = graph.add_vertex();
let e = graph.add_edge(v1, v2);
let path = $crate::algorithms::find_path(&graph, v1, v2)
let path = $crate::algorithms::dfs_find_path(&graph, v1, v2)
.expect("path should exist between adjacent vertices");
assert_eq!(
path.len(),
@@ -43,7 +44,7 @@ macro_rules! find_path_tests {
#[test]
fn find_path() {
let (graph, vertices, _, _) = make_test_graph();
let path = $crate::algorithms::find_path(&graph, vertices[0], vertices[9])
let path = $crate::algorithms::dfs_find_path(&graph, vertices[0], vertices[9])
.expect(&format!(
"path should exist between connected vertices {:?} and {:?}",
vertices[0], vertices[9]
@@ -57,7 +58,7 @@ macro_rules! find_path_tests {
let mut graph = <$T>::new();
let v = graph.add_vertex();
assert_eq!(
$crate::algorithms::find_path_where(&graph, v, |u| u == v),
$crate::algorithms::dfs_find_path_where(&graph, v, |u| u == v),
Some(vec![]),
"path from source to itself should be empty"
);
@@ -67,7 +68,7 @@ macro_rules! find_path_tests {
fn find_path_where_disconnected() {
let (graph, vertices) = make_test_graph_disconnected();
assert_eq!(
$crate::algorithms::find_path_where(&graph, vertices[0], |v| v == vertices[1]),
$crate::algorithms::dfs_find_path_where(&graph, vertices[0], |v| v == vertices[1]),
None,
"no path should exist to disconnected vertex"
);
@@ -77,7 +78,7 @@ macro_rules! find_path_tests {
fn find_path_where_no_match() {
let (graph, vertices, _, _) = make_test_graph();
assert_eq!(
$crate::algorithms::find_path_where(&graph, vertices[0], |_| false),
$crate::algorithms::dfs_find_path_where(&graph, vertices[0], |_| false),
None,
"no path should exist when predicate never matches"
);
@@ -87,7 +88,7 @@ macro_rules! find_path_tests {
fn find_path_where() {
let (graph, vertices, _, _) = make_test_graph();
let path =
$crate::algorithms::find_path_where(&graph, vertices[0], |v| v == vertices[9])
$crate::algorithms::dfs_find_path_where(&graph, vertices[0], |v| v == vertices[9])
.expect(&format!(
"path should exist between connected vertices {:?} and {:?}",
vertices[0], vertices[9]
+3
View File
@@ -1,3 +1,4 @@
#[doc(hidden)]
#[macro_export]
macro_rules! graph_topology_test_fixtures {
($T:ty) => {
@@ -101,6 +102,7 @@ macro_rules! graph_topology_test_fixtures {
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! graph_topology_tests {
($T:ty) => {
@@ -748,6 +750,7 @@ macro_rules! graph_topology_tests {
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! graph_topology_deletion_tests {
($T:ty) => {
+14 -10
View File
@@ -1,3 +1,4 @@
#[doc(hidden)]
#[macro_export]
macro_rules! vertex_map_tests {
($T:ty) => {
@@ -52,16 +53,16 @@ macro_rules! vertex_map_tests {
let mut graph = <$T>::new();
graph.add_vertex();
let mut map = graph.vertex_map(42);
let initial_len = map.len();
while graph.vertex_capacity() <= initial_len {
let capacity_before = map.capacity();
while graph.vertex_capacity() <= capacity_before {
graph.add_vertex();
}
assert!(
map.len() < graph.vertex_capacity(),
map.capacity() < graph.vertex_capacity(),
"precondition: map is stale before sync"
);
map.sync(&graph);
assert_eq!(map.len(), graph.vertex_capacity());
assert_eq!(map.capacity(), graph.vertex_capacity());
}
#[test]
@@ -78,6 +79,7 @@ macro_rules! vertex_map_tests {
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! vertex_map_deletion_tests {
($T:ty) => {
@@ -107,7 +109,7 @@ macro_rules! vertex_map_deletion_tests {
let capacity_before = graph.vertex_capacity();
graph.delete_vertex(v2);
map.sync(&graph);
assert_eq!(map.len(), capacity_before);
assert_eq!(map.capacity(), capacity_before);
assert_eq!(map[v1], 5);
}
@@ -130,6 +132,7 @@ macro_rules! vertex_map_deletion_tests {
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! edge_map_tests {
($T:ty) => {
@@ -194,16 +197,16 @@ macro_rules! edge_map_tests {
let v2 = graph.add_vertex();
graph.add_edge(v1, v2);
let mut map = graph.edge_map(42);
let initial_len = map.len();
while graph.edge_capacity() <= initial_len {
let capacity_before = map.capacity();
while graph.edge_capacity() <= capacity_before {
graph.add_edge(v1, v2);
}
assert!(
map.len() < graph.edge_capacity(),
map.capacity() < graph.edge_capacity(),
"precondition: map is stale before sync"
);
map.sync(&graph);
assert_eq!(map.len(), graph.edge_capacity());
assert_eq!(map.capacity(), graph.edge_capacity());
}
#[test]
@@ -222,6 +225,7 @@ macro_rules! edge_map_tests {
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! edge_map_deletion_tests {
($T:ty) => {
@@ -255,7 +259,7 @@ macro_rules! edge_map_deletion_tests {
let capacity_before = graph.edge_capacity();
graph.delete_edge(e2);
map.sync(&graph);
assert_eq!(map.len(), capacity_before);
assert_eq!(map.capacity(), capacity_before);
assert_eq!(map[e1], 5);
}
+2 -2
View File
@@ -1,12 +1,12 @@
mod append_graph_tests {
use grapherity::models::append_graph::AppendGraph;
use grapherity::models::AppendGraph;
grapherity::graph_topology_test_fixtures!(AppendGraph);
grapherity::bfs_tests!(AppendGraph);
}
mod graph_tests {
use grapherity::models::graph::Graph;
use grapherity::models::Graph;
grapherity::graph_topology_test_fixtures!(Graph);
grapherity::bfs_tests!(Graph);
+2 -2
View File
@@ -1,12 +1,12 @@
mod append_graph_tests {
use grapherity::models::append_graph::AppendGraph;
use grapherity::models::AppendGraph;
grapherity::graph_topology_test_fixtures!(AppendGraph);
grapherity::dfs_tests!(AppendGraph);
}
mod graph_tests {
use grapherity::models::graph::Graph;
use grapherity::models::Graph;
grapherity::graph_topology_test_fixtures!(Graph);
grapherity::dfs_tests!(Graph);
+2 -2
View File
@@ -1,12 +1,12 @@
mod append_graph_tests {
use grapherity::models::append_graph::AppendGraph;
use grapherity::models::AppendGraph;
grapherity::graph_topology_test_fixtures!(AppendGraph);
grapherity::dijkstra_tests!(AppendGraph);
}
mod graph_tests {
use grapherity::models::graph::Graph;
use grapherity::models::Graph;
grapherity::graph_topology_test_fixtures!(Graph);
grapherity::dijkstra_tests!(Graph);
+2 -2
View File
@@ -1,12 +1,12 @@
mod append_graph_tests {
use grapherity::models::append_graph::AppendGraph;
use grapherity::models::AppendGraph;
grapherity::graph_topology_test_fixtures!(AppendGraph);
grapherity::find_path_tests!(AppendGraph);
}
mod graph_tests {
use grapherity::models::graph::Graph;
use grapherity::models::Graph;
grapherity::graph_topology_test_fixtures!(Graph);
grapherity::find_path_tests!(Graph);
+4 -4
View File
@@ -1,24 +1,24 @@
mod append_graph_vertex_map_tests {
use grapherity::models::append_graph::AppendGraph;
use grapherity::models::AppendGraph;
grapherity::vertex_map_tests!(AppendGraph);
}
mod append_graph_edge_map_tests {
use grapherity::models::append_graph::AppendGraph;
use grapherity::models::AppendGraph;
grapherity::edge_map_tests!(AppendGraph);
}
mod graph_vertex_map_tests {
use grapherity::models::graph::Graph;
use grapherity::models::Graph;
grapherity::vertex_map_tests!(Graph);
grapherity::vertex_map_deletion_tests!(Graph);
}
mod graph_edge_map_tests {
use grapherity::models::graph::Graph;
use grapherity::models::Graph;
grapherity::edge_map_tests!(Graph);
grapherity::edge_map_deletion_tests!(Graph);