Compare commits
7 Commits
fixes-major
...
docs
| Author | SHA1 | Date | |
|---|---|---|---|
| c131f520f4 | |||
| b6c27ea605 | |||
| 5b30151010 | |||
| 2f549cc506 | |||
| cf31ee2f01 | |||
| e58261ba47 | |||
| 818b37ec15 |
+1
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "grapherity"
|
||||
version = "0.2.1"
|
||||
version = "0.2.2"
|
||||
authors = ["Stefan Müller"]
|
||||
edition = "2024"
|
||||
rust-version = "1.85.0"
|
||||
|
||||
@@ -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
@@ -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>>
|
||||
|
||||
+131
@@ -1,3 +1,134 @@
|
||||
//! 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;
|
||||
|
||||
+15
-5
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user