10 Commits

Author SHA1 Message Date
warrence c131f520f4 Add crate doc front-page, and update readme 2026-07-14 20:01:08 +02:00
warrence b6c27ea605 Merge branch 'main' into docs 2026-07-14 09:54:17 +02:00
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 818b37ec15 Add linter warning for missing docs 2026-07-10 10:18:39 +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
7 changed files with 256 additions and 40 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "grapherity"
version = "0.2.0"
version = "0.2.2"
authors = ["Stefan Müller"]
edition = "2024"
rust-version = "1.85.0"
+57 -2
View File
@@ -1,8 +1,63 @@
# grapherity
Library for graph models and algorithms.
Graph models and algorithms.
This library is still experimental and its API may therefore change frequently.
This crate provides data structures to model graphs and to perform algorithms on these data structures. The functionality presented is designed to be easy to use, flexible, and performant.
Currently supported are:
* Undirected graph types `Graph` and `AppendGraph`, built on a flat, index-based adjacency list,
* Maps to freely associate custom data with vertices and edges,
* Connectivity and pathing algorithms: Dijkstra's algorithm, DFS, and BFS in different variants,
* Exposed traits to implement custom graph data structures or algorithms.
For more information, see the [documentation](https://docs.rs/grapherity/latest/grapherity/).
## Example
```
// Brings commonly used traits into scope.
use grapherity::prelude::*;
use grapherity::models::Graph;
use grapherity::algorithms;
let mut graph = Graph::new();
// Creates a map for edge weights with a default weight.
let mut weights = graph.edge_map(1);
// Adds three vertices and two edges.
let v1 = graph.add_vertex();
let v2 = graph.add_vertex();
let v3 = graph.add_vertex();
graph.add_edge(v1, v2);
let e = graph.add_edge(v2, v3);
// Sets a non-default weight for the second edge.
weights[e] = 5;
// Checks the number of vertices.
assert_eq!(graph.vertex_count(), 3);
// Checks the number of edges.
assert_eq!(graph.edge_count(), 2);
// Checks that v1 and v2 are adjacent.
assert!(graph.are_adjacent(v1, v2));
// Checks that v1 and v3 are not adjacent.
assert!(!graph.are_adjacent(v1, v3));
// Checks the sum of all vertex degrees.
let sum: usize = graph.vertices().map(|v| graph.degree(v)).sum();
assert_eq!(sum, 1 + 2 + 1);
// Calls Dijkstra's algorithm using the edge weights to find the shortest path from v1 to v3.
let result = algorithms::dijkstra(&graph, v1, |e| weights[e]);
assert_eq!(result.distances[v3], Some(1 + 5));
assert_eq!(result.predecessors[v3], Some(v2));
// Deletes the middle vertex and its incident edges.
graph.delete_vertex(v2);
// Calls Dijkstra's algorithm again on the now disconnected graph.
let result = algorithms::dijkstra(&graph, v1, |e| weights[e]);
assert_eq!(result.distances[v3], None);
assert_eq!(result.predecessors[v3], None);
```
## License
+36 -22
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,7 +269,10 @@ where
}
}
}
(visited, None)
DfsImplResult {
visited,
found: None,
}
}
pub fn dfs_find_path<G>(graph: &G, source: G::Vertex, target: G::Vertex) -> Option<Vec<G::Edge>>
+135
View File
@@ -1,6 +1,141 @@
//! Graph models and algorithms.
//!
//! This crate provides data structures to model graphs and to perform algorithms on these data
//! structures. The functionality presented is designed to be easy to use, flexible, and performant.
//!
//! Currently supported are:
//!
//! * Undirected graph types [`Graph`] and [`AppendGraph`] built on a flat, index-based adjacency
//! list,
//! * [`VertexMap`] and [`EdgeMap`] to freely associate custom data with vertices and edges,
//! * Connectivity and pathing algorithms: [Dijkstra's algorithm], [DFS], and [BFS] in different
//! variants,
//! * Exposed traits to implement custom graph data structures or algorithms.
//!
//! # Usage example
//!
//! This example demonstrates some of this library's features.
//!
//! ```
//! // Brings commonly used traits into scope.
//! use grapherity::prelude::*;
//! use grapherity::models::Graph;
//! use grapherity::algorithms;
//!
//! let mut graph = Graph::new();
//! // Creates a map for edge weights with a default weight.
//! let mut weights = graph.edge_map(1);
//! // Adds three vertices and two edges.
//! let v1 = graph.add_vertex();
//! let v2 = graph.add_vertex();
//! let v3 = graph.add_vertex();
//! graph.add_edge(v1, v2);
//! let e = graph.add_edge(v2, v3);
//! // Sets a non-default weight for the second edge.
//! weights[e] = 5;
//!
//! // Checks the number of vertices.
//! assert_eq!(graph.vertex_count(), 3);
//! // Checks the number of edges.
//! assert_eq!(graph.edge_count(), 2);
//! // Checks that v1 and v2 are adjacent.
//! assert!(graph.are_adjacent(v1, v2));
//! // Checks that v1 and v3 are not adjacent.
//! assert!(!graph.are_adjacent(v1, v3));
//! // Checks the sum of all vertex degrees.
//! let sum: usize = graph.vertices().map(|v| graph.degree(v)).sum();
//! assert_eq!(sum, 1 + 2 + 1);
//!
//! // Calls Dijkstra's algorithm using the edge weights to find the shortest path from v1 to v3.
//! let result = algorithms::dijkstra(&graph, v1, |e| weights[e]);
//! assert_eq!(result.distances[v3], Some(1 + 5));
//! assert_eq!(result.predecessors[v3], Some(v2));
//!
//! // Deletes the middle vertex and its incident edges.
//! graph.delete_vertex(v2);
//!
//! // Calls Dijkstra's algorithm again on the now disconnected graph.
//! let result = algorithms::dijkstra(&graph, v1, |e| weights[e]);
//! assert_eq!(result.distances[v3], None);
//! assert_eq!(result.predecessors[v3], None);
//! ```
//!
//! # Graph types
//!
//! [`Graph`] and [`AppendGraph`] both implement [`GraphTopology`] using a flat, index-based adjacency
//! list to store edges, which means that vertex and edge insertions happen in `O(1)`. Degree
//! lookups are also `O(1)`.
//!
//! [`Graph`] additionally implements [`GraphTopologyDeletion`], thereby supporting deletion of
//! vertices and edges.
//!
//! [`AppendGraph`] does not support deletion, vertices and edges can only be added, allowing it to
//! be smaller and more performant compared to [`Graph`] by not requiring the per-element generation
//! tracking needed for stable handles after deletion.
//!
//! # Vertices and edges
//!
//! Graphs in this library use stable indices [`GraphTopology::Vertex`] and [`GraphTopology::Edge`]
//! to denote vertices and edges. This means that vertices and edges returned by graph functions
//! never change, even if the underlying graph data structure is mutated. However, vertices and
//! edges can become invalid when the denoted element is deleted. Code using an invalid vertex or
//! edge in a graph method will panic.
//!
//! ```should_panic
//! # use grapherity::prelude::*;
//! # use grapherity::models::Graph;
//! let mut graph = Graph::new();
//! let v = graph.add_vertex();
//! graph.delete_vertex(v);
//! // This will panic because 'v' was deleted and is not valid anymore.
//! graph.degree(v);
//! ```
//!
//! [`VertexMap`] and [`EdgeMap`] are not required to panic if provided with an invalid
//! vertex or edge.
//!
//! Vertices and edges are provided by the graph and not intended to be generated by users.
//!
//! # Loops and multi-edges
//!
//! Loops and multi edges are supported as well. In the following example, `v1` will have two loops
//! and two edges to `v2`.
//!
//! ```
//! # use grapherity::prelude::*;
//! # use grapherity::models::Graph;
//! let mut graph = Graph::new();
//! let v1 = graph.add_vertex();
//! let v2 = graph.add_vertex();
//! for _ in 0..2 {
//! graph.add_edge(v1, v1);
//! graph.add_edge(v1, v2);
//! }
//! assert_eq!(graph.edge_count(), 4);
//! assert_eq!(graph.degree(v1), 6);
//! ```
//!
//! [BFS]: crate::algorithms::bfs
//! [Dijkstra's algorithm]: crate::algorithms::dijkstra
//! [DFS]: crate::algorithms::dfs
//! [`EdgeMap`]: crate::maps::EdgeMap
//! [`VertexMap`]: crate::maps::VertexMap
//! [`AppendGraph`]: crate::models::AppendGraph
//! [`Graph`]: crate::models::Graph
//! [`GraphTopology`]: crate::traits::GraphTopology
//! [`GraphTopology::Edge`]: crate::traits::GraphTopology::Edge
//! [`GraphTopology::Vertex`]: crate::traits::GraphTopology::Vertex
//! [`GraphTopologyDeletion`]: crate::traits::GraphTopologyDeletion
#![warn(missing_docs)]
pub mod algorithms;
pub mod maps;
pub mod models;
pub mod traits;
pub mod prelude {
pub use crate::traits::{GraphTopology, GraphTopologyDeletion};
}
mod testing;
+15 -5
View File
@@ -13,9 +13,14 @@ impl<V: Copy, T: Clone> VertexMap<V, 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<Vertex = V>>(&mut self, graph: &G) {
@@ -48,9 +53,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 +97,7 @@ impl<E: Copy, T: Clone> EntityMap<E, T> {
}
}
pub fn len(&self) -> usize {
pub fn capacity(&self) -> usize {
self.data.len()
}
+2
View File
@@ -1,5 +1,7 @@
pub mod append_graph;
pub mod graph;
// TODO: Compressed-sparse-row graph model.
pub use append_graph::{AppendGraph, AppendGraphEdgeMap, AppendGraphVertexMap};
pub use graph::{Graph, GraphEdgeMap, GraphVertexMap};
+10 -10
View File
@@ -53,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]
@@ -109,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);
}
@@ -197,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]
@@ -259,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);
}