Merge branch 'main' into release-0.3.0

This commit is contained in:
2026-07-17 11:29:41 +02:00
8 changed files with 440 additions and 31 deletions
+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>>
+129
View File
@@ -1,3 +1,132 @@
//! 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
pub mod algorithms;
pub mod maps;
pub mod models;
+2 -2
View File
@@ -13,7 +13,7 @@ impl<V: Copy, T: Clone> VertexMap<V, T> {
}
}
// Reads beyond capacity() 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()
}
@@ -48,7 +48,7 @@ impl<E: Copy, T: Clone> EdgeMap<E, T> {
}
}
// Reads beyond capacity() 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()
}
+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};
+4 -4
View File
@@ -53,8 +53,8 @@ macro_rules! vertex_map_tests {
let mut graph = <$T>::new();
graph.add_vertex();
let mut map = graph.vertex_map(42);
let initial_len = map.capacity();
while graph.vertex_capacity() <= initial_len {
let capacity_before = map.capacity();
while graph.vertex_capacity() <= capacity_before {
graph.add_vertex();
}
assert!(
@@ -197,8 +197,8 @@ 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.capacity();
while graph.edge_capacity() <= initial_len {
let capacity_before = map.capacity();
while graph.edge_capacity() <= capacity_before {
graph.add_edge(v1, v2);
}
assert!(
+209
View File
@@ -2,35 +2,244 @@ use crate::maps::{EdgeMap, VertexMap};
// TODO: Add functions to reserve memory for vertices and edges.
// TODO: Split out GraphTopologyAddition trait.
/// A trait representing an undirected graph topology.
///
/// An undirected graph is a set of vertices and undirected edges, where each edge connects either
/// exactly two vertices or one vertex with itself (loop edge). This trait provides methods for
/// querying a graph topology, iterating over vertices and edges, and adding new vertices and edges.
///
/// # Vertices and Edges
///
/// Vertices and edges are identified by opaque handles ([`Vertex`] and [`Edge`]) that implement
/// [`Copy`] and [`Eq`]. Handles remain valid for the lifetime of the graph unless the graph also
/// implements [`GraphTopologyDeletion`] and the item identified by the handle is explicitly
/// deleted.
///
/// Methods accepting vertices or edges as parameters panic if the handle was invalidated by a
/// deletion, and return incorrect results if the handle was not produced by this graph instance.
///
/// # Deletion
///
/// This trait covers graph construction and querying only. To delete vertices and edges, see
/// [`GraphTopologyDeletion`].
///
/// [`Edge`]: GraphTopology::Edge
/// [`Vertex`]: GraphTopology::Vertex
pub trait GraphTopology {
/// An opaque, stable handle identifying a vertex.
type Vertex: Copy + Eq;
/// An opaque, stable handle identifying an edge.
type Edge: Copy + Eq;
/// A resumable position in the incidence list of a vertex.
///
/// Prefer [`incidences`](Self::incidences) for straightforward iteration. A cursor is useful
/// when an algorithm needs to pause traversal, perform other graph queries or mutations, and
/// then continue from where it left off. This cursor type can be obtained via
/// [`incidence_cursor`](Self::incidence_cursor).
type IncidenceCursor: IncidenceCursor<Self>;
/// Returns the number of vertices in the graph.
fn vertex_count(&self) -> usize;
/// Returns the total number of vertices the graph can hold without reallocating.
fn vertex_capacity(&self) -> usize;
/// Creates and returns a [`VertexMap`] with every slot initialised to `default`.
///
/// # Examples
///
/// ```
/// # use grapherity::prelude::*;
/// # use grapherity::models::Graph;
/// let mut graph = Graph::new();
/// let v1 = graph.add_vertex();
/// let mut labels = graph.vertex_map("z");
/// assert_eq!(labels[v1], "z");
/// labels[v1] = "a";
/// assert_eq!(labels[v1], "a");
///
/// // A new vertex is immediately available for read and write.
/// let v2 = graph.add_vertex();
/// assert_eq!(labels[v2], "z");
/// labels[v2] = "b";
/// assert_eq!(labels[v2], "b");
/// ```
fn vertex_map<T: Clone>(&self, default: T) -> VertexMap<Self::Vertex, T>;
/// Returns the number of edges in the graph.
fn edge_count(&self) -> usize;
/// Returns the total number of edges the graph can hold without reallocating.
fn edge_capacity(&self) -> usize;
/// Creates and returns an [`EdgeMap`] with every slot initialised to `default`.
///
/// # Examples
///
/// ```
/// # use grapherity::prelude::*;
/// # use grapherity::models::Graph;
/// let mut graph = Graph::new();
/// let v1 = graph.add_vertex();
/// let v2 = graph.add_vertex();
/// let e1 = graph.add_edge(v1, v2);
/// let mut weights = graph.edge_map(5);
/// assert_eq!(weights[e1], 5);
/// weights[e1] = 1;
/// assert_eq!(weights[e1], 1);
///
/// // A new edge is immediately available for read and write.
/// let e2 = graph.add_edge(v1, v2);
/// assert_eq!(weights[e2], 5);
/// weights[e2] = 2;
/// assert_eq!(weights[e2], 2);
/// ```
fn edge_map<T: Clone>(&self, default: T) -> EdgeMap<Self::Edge, T>;
/// Returns the degree of `v`, i.e. the number of incident edges, where each loop contributes 2.
///
/// # Panics
///
/// Panics if `v` is not a valid vertex of this graph.
fn degree(&self, v: Self::Vertex) -> usize;
/// Returns `true` if there is at least one edge between `v1` and `v2`, and `false` otherwise.
///
/// A vertex is adjacent to itself if and only if it has a loop edge.
///
/// # Panics
///
/// Panics if `v1` or `v2` is not a valid vertex of this graph.
fn are_adjacent(&self, v1: Self::Vertex, v2: Self::Vertex) -> bool;
/// Returns an iterator over all vertices in the graph.
fn vertices(&self) -> impl Iterator<Item = Self::Vertex>;
/// Returns an iterator over all vertices adjacent to `v`.
///
/// Algorithms may prefer this over [`incidences`](Self::incidences) if the edges are not
/// required, since implementors may be able to provide an iterator faster than the trivial
/// mapping. The complexity of this method must not exceed that of
/// [`incidences`](Self::incidences).
///
/// # Panics
///
/// Panics if `v` is not a valid vertex of this graph.
fn adjacent_vertices(&self, v: Self::Vertex) -> impl Iterator<Item = Self::Vertex>;
/// Returns the two endpoints of edge `e`, which are identical if and only if `e` is a loop
/// edge.
///
/// # Panics
///
/// Panics if `e` is not a valid edge of this graph.
fn incident_vertices(&self, e: Self::Edge) -> (Self::Vertex, Self::Vertex);
/// Returns an iterator over all edges in the graph.
fn edges(&self) -> impl Iterator<Item = Self::Edge>;
/// Returns an iterator over all edges incident to `v`.
///
/// Algorithms may prefer this over [`incidences`](Self::incidences) if the vertices are not
/// required, since implementors may be able to provide an iterator faster than the trivial
/// mapping. The complexity of this method must not exceed that of
/// [`incidences`](Self::incidences).
///
/// # Panics
///
/// Panics if `v` is not a valid vertex of this graph.
fn incident_edges(&self, v: Self::Vertex) -> impl Iterator<Item = Self::Edge>;
/// Returns an iterator over all incidences of `v`, i.e. vertex-edge pairs `(u, e)` such that
/// `u` is adjacent to `v` and `e` is an edge between them.
///
/// Use [`incidence_cursor`](Self::incidence_cursor) instead for a traversal that needs to
/// suspend and resume across other state updates.
///
/// # Panics
///
/// Panics if `v` is not a valid vertex of this graph.
fn incidences(&self, v: Self::Vertex) -> impl Iterator<Item = (Self::Vertex, Self::Edge)>;
/// Returns a cursor over all incidences of `v`, analogously to
/// [`incidences`](Self::incidences), initially positioned before the first one.
///
/// See [`IncidenceCursor`](Self::IncidenceCursor) for when to prefer a cursor over
/// [`incidences`](Self::incidences).
///
/// # Panics
///
/// Panics if `v` is not a valid vertex of this graph.
fn incidence_cursor(&self, v: Self::Vertex) -> Self::IncidenceCursor;
/// Adds a new isolated vertex and returns its handle.
fn add_vertex(&mut self) -> Self::Vertex;
/// Adds a new edge between `v1` and `v2` and returns its handle.
///
/// # Panics
///
/// Panics if `v1` or `v2` is not a valid vertex of this graph.
fn add_edge(&mut self, v1: Self::Vertex, v2: Self::Vertex) -> Self::Edge;
}
/// A trait that adds deletion operations to an undirected graph topology.
///
/// This trait provides methods for deletion of vertices and edges in an undirected graph. These
/// operations will invalidate handles to all deleted vertices and edges. Methods accepting invalid
/// vertices or edges as parameters panic.
pub trait GraphTopologyDeletion: GraphTopology {
/// Deletes the vertex `v` and all its incident edges from the graph. Note that this also
/// invalidates the handles of all edges incident to `v`.
///
/// # Panics
///
/// Panics if `v` is not a valid vertex of this graph.
fn delete_vertex(&mut self, v: Self::Vertex);
/// Deletes the edge `e` from the graph. This operation only invalidates `e` and no other vertex
/// or edge handles.
///
/// # Panics
///
/// Panics if `e` is not a valid edge of this graph.
fn delete_edge(&mut self, e: Self::Edge);
}
/// A cursor for traversing the incidences of a vertex one step at a time.
///
/// Cursors are obtained via [`GraphTopology::incidence_cursor`]. See
/// [`GraphTopology::IncidenceCursor`] for guidance on when to prefer a cursor over
/// [`GraphTopology::incidences`].
pub trait IncidenceCursor<G: GraphTopology + ?Sized>: Copy {
/// Advances the cursor and returns the next incidence as `Some((u, e))`, or `None` if the
/// traversal is exhausted.
///
/// # Examples
///
/// ```
/// # use grapherity::prelude::*;
/// # use grapherity::models::Graph;
/// // Constructs a graph with two vertices connected to `v`.
/// let mut graph = Graph::new();
/// let v = graph.add_vertex();
/// for _ in 0..2 {
/// let u = graph.add_vertex();
/// graph.add_edge(u, v);
/// }
///
/// // Iterates over the incidences of `v` with a cursor.
/// let mut c1 = graph.incidence_cursor(v);
/// assert!(c1.next(&graph).is_some());
/// let mut c2 = c1;
/// // Continues iteration with original cursor.
/// assert!(c1.next(&graph).is_some());
/// assert!(c1.next(&graph).is_none());
/// // Iterates over the last incidence again with the copied cursor.
/// assert!(c2.next(&graph).is_some());
/// assert!(c2.next(&graph).is_none());
/// ```
fn next(&mut self, graph: &G) -> Option<(G::Vertex, G::Edge)>;
}