Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 95f9b06088 | |||
| b752e3f9d4 | |||
| 1b79809e2f | |||
| 242aba09e8 | |||
| 1b8df19d00 | |||
| 517b83b328 | |||
| dd4e959554 | |||
| cf1dafceb7 | |||
| 9a1e2faa9a | |||
| 08b2c4db65 | |||
| dfa8f8f0df | |||
| 43c4ba45a2 | |||
| 99bab32f8c | |||
| 6e80b2fc9a | |||
| b5cc3b1c3f | |||
| 21e79c660f | |||
| 9f76ce4ce3 | |||
| d9dc102993 | |||
| 3fa4b66981 | |||
| 8f9f0bf4c8 | |||
| 5fa415841e | |||
| e8568a084a | |||
| e6b08d9148 | |||
| b62c263b08 | |||
| 7345684f00 | |||
| 2c53d92943 | |||
| c131f520f4 | |||
| b6c27ea605 | |||
| 818b37ec15 |
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "grapherity"
|
name = "grapherity"
|
||||||
version = "0.2.2"
|
version = "0.2.4"
|
||||||
authors = ["Stefan Müller"]
|
authors = ["Stefan Müller"]
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
rust-version = "1.85.0"
|
rust-version = "1.85.0"
|
||||||
|
|||||||
@@ -1,14 +1,69 @@
|
|||||||
# grapherity
|
# 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
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// 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
|
## License
|
||||||
|
|
||||||
This project is dual-licensed under the terms of both:
|
This project is dual-licensed under the terms of both:
|
||||||
|
|
||||||
- Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or https://www.apache.org/licenses/LICENSE-2.0)
|
- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or https://www.apache.org/licenses/LICENSE-2.0)
|
||||||
- MIT license ([LICENSE-MIT](LICENSE-MIT) or https://opensource.org/license/mit)
|
- MIT license ([LICENSE-MIT](LICENSE-MIT) or https://opensource.org/license/mit)
|
||||||
|
|
||||||
You may choose either license for your use of this software.
|
You may choose either license for your use of this software.
|
||||||
|
|||||||
+446
-7
@@ -1,3 +1,33 @@
|
|||||||
|
//! Algorithms for graph topologies.
|
||||||
|
//!
|
||||||
|
//! # Dijkstra's algorithm
|
||||||
|
//!
|
||||||
|
//! [Dijkstra's algorithm] finds the shortest distances from a source vertex to all other vertices
|
||||||
|
//! in the graph. Note that for unweighted graphs it has worse time and space complexity than
|
||||||
|
//! [Breadth-first search](#breadth-first-search).
|
||||||
|
//!
|
||||||
|
//! Variants: [`dijkstra`], [`dijkstra_distances`], [`dijkstra_unweighted`],
|
||||||
|
//! [`dijkstra_distances_unweighted`].
|
||||||
|
//!
|
||||||
|
//! # Breadth-first search
|
||||||
|
//!
|
||||||
|
//! [Breadth-first search] traverses a graph topology, exploring all neighboring vertices first
|
||||||
|
//! before descending further into their neighborhoods.
|
||||||
|
//!
|
||||||
|
//! Variants: [`bfs`], [`bfs_distances`], [`bfs_find`], [`bfs_find_where`].
|
||||||
|
//!
|
||||||
|
//! # Depth-first search
|
||||||
|
//!
|
||||||
|
//! [Depth-first search] traverses a graph topology, exploring each branch path as far as possible
|
||||||
|
//! first before backtracking and exploring other branches.
|
||||||
|
//!
|
||||||
|
//! Variants: [`dfs`], [`dfs_visited`], [`dfs_find`], [`dfs_find_where`], [`dfs_find_path`],
|
||||||
|
//! [`dfs_find_path_where`].
|
||||||
|
//!
|
||||||
|
//! [Breadth-first search]: https://en.wikipedia.org/wiki/Breadth-first_search
|
||||||
|
//! [Depth-first search]: https://en.wikipedia.org/wiki/Depth-first_search
|
||||||
|
//! [Dijkstra's algorithm]: https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm
|
||||||
|
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use std::collections::{BinaryHeap, VecDeque};
|
use std::collections::{BinaryHeap, VecDeque};
|
||||||
|
|
||||||
@@ -22,12 +52,45 @@ impl<V: Eq> Ord for DistanceOrderedVertex<V> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return data type for [`dijkstra`] and [`dijkstra_unweighted`].
|
||||||
pub struct DijkstraResult<V: Copy> {
|
pub struct DijkstraResult<V: Copy> {
|
||||||
|
/// Vertex map of minimum distances from a given `source` vertex.
|
||||||
pub distances: VertexMap<V, Option<u32>>,
|
pub distances: VertexMap<V, Option<u32>>,
|
||||||
|
/// Vertex map of predecessors on some shortest path from a given `source` vertex.
|
||||||
pub predecessors: VertexMap<V, Option<V>>,
|
pub predecessors: VertexMap<V, Option<V>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Generalize the return type of the weight function.
|
// TODO: Generalize the return type of the weight function.
|
||||||
|
/// [Dijkstra's algorithm] with custom edge weights, returns minimum distances and predecessors.
|
||||||
|
///
|
||||||
|
/// Calculates the shortest paths from `source` to all vertices in `graph` with edge weights given
|
||||||
|
/// by `weight` function. Returns the distances from `source` to each vertex, and the predecessors
|
||||||
|
/// of each vertex on some shortest path from `source` to that vertex. Returns `None` for any vertex
|
||||||
|
/// not connected to `source`.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if `source` is not a valid vertex of `graph`.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use grapherity::prelude::*;
|
||||||
|
/// # use grapherity::algorithms::dijkstra;
|
||||||
|
/// # use grapherity::models::Graph;
|
||||||
|
/// let mut graph = Graph::new();
|
||||||
|
/// let source = graph.add_vertex();
|
||||||
|
/// let target = graph.add_vertex();
|
||||||
|
/// let e = graph.add_edge(source, target);
|
||||||
|
/// let mut weights = graph.edge_map(1);
|
||||||
|
/// weights[e] = 5;
|
||||||
|
///
|
||||||
|
/// let result = dijkstra(&graph, source, |e| weights[e]);
|
||||||
|
/// assert_eq!(result.distances[target], Some(5));
|
||||||
|
/// assert_eq!(result.predecessors[target], Some(source));
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// [Dijkstra's algorithm]: https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm
|
||||||
pub fn dijkstra<G, W>(graph: &G, source: G::Vertex, weight: W) -> DijkstraResult<G::Vertex>
|
pub fn dijkstra<G, W>(graph: &G, source: G::Vertex, weight: W) -> DijkstraResult<G::Vertex>
|
||||||
where
|
where
|
||||||
G: GraphTopology,
|
G: GraphTopology,
|
||||||
@@ -43,13 +106,34 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn dijkstra_unweighted<G>(graph: &G, source: G::Vertex) -> DijkstraResult<G::Vertex>
|
/// [Dijkstra's algorithm] with custom edge weights, returns minimum distances.
|
||||||
where
|
///
|
||||||
G: GraphTopology,
|
/// Calculates the shortest paths from `source` to all vertices in `graph` with edge weights given
|
||||||
{
|
/// by `weight` function. Returns the distances from `source` to each vertex. Returns `None` for any
|
||||||
dijkstra(graph, source, |_| 1)
|
/// vertex not connected to `source`.
|
||||||
}
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if `source` is not a valid vertex of `graph`.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use grapherity::prelude::*;
|
||||||
|
/// # use grapherity::algorithms::dijkstra_distances;
|
||||||
|
/// # use grapherity::models::Graph;
|
||||||
|
/// let mut graph = Graph::new();
|
||||||
|
/// let source = graph.add_vertex();
|
||||||
|
/// let target = graph.add_vertex();
|
||||||
|
/// let e = graph.add_edge(source, target);
|
||||||
|
/// let mut weights = graph.edge_map(1);
|
||||||
|
/// weights[e] = 5;
|
||||||
|
///
|
||||||
|
/// let distances = dijkstra_distances(&graph, source, |e| weights[e]);
|
||||||
|
/// assert_eq!(distances[target], Some(5));
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// [Dijkstra's algorithm]: https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm
|
||||||
pub fn dijkstra_distances<G, W>(
|
pub fn dijkstra_distances<G, W>(
|
||||||
graph: &G,
|
graph: &G,
|
||||||
source: G::Vertex,
|
source: G::Vertex,
|
||||||
@@ -62,6 +146,70 @@ where
|
|||||||
dijkstra_impl(graph, source, weight, |_, _| {})
|
dijkstra_impl(graph, source, weight, |_, _| {})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// [Dijkstra's algorithm] with unit edge weights, returns minimum distances and predecessors.
|
||||||
|
///
|
||||||
|
/// Calculates the shortest paths from `source` to all vertices in an unweighted `graph`. Returns
|
||||||
|
/// the distances from `source` to each vertex, and the predecessors of each vertex on some shortest
|
||||||
|
/// path from `source` to that vertex. Returns `None` for any vertex not connected to `source`.
|
||||||
|
///
|
||||||
|
/// Prefer [`bfs`] for unweighted graphs, it has better time and space complexity.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if `source` is not a valid vertex of `graph`.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use grapherity::prelude::*;
|
||||||
|
/// # use grapherity::algorithms::dijkstra_unweighted;
|
||||||
|
/// # use grapherity::models::Graph;
|
||||||
|
/// let mut graph = Graph::new();
|
||||||
|
/// let source = graph.add_vertex();
|
||||||
|
/// let target = graph.add_vertex();
|
||||||
|
/// graph.add_edge(source, target);
|
||||||
|
///
|
||||||
|
/// let result = dijkstra_unweighted(&graph, source);
|
||||||
|
/// assert_eq!(result.distances[target], Some(1));
|
||||||
|
/// assert_eq!(result.predecessors[target], Some(source));
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// [Dijkstra's algorithm]: https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm
|
||||||
|
pub fn dijkstra_unweighted<G>(graph: &G, source: G::Vertex) -> DijkstraResult<G::Vertex>
|
||||||
|
where
|
||||||
|
G: GraphTopology,
|
||||||
|
{
|
||||||
|
dijkstra(graph, source, |_| 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// [Dijkstra's algorithm] with unit edge weights, returns minimum distances.
|
||||||
|
///
|
||||||
|
/// Calculates the shortest paths from `source` to all vertices in an unweighted `graph`. Returns
|
||||||
|
/// the distances from `source` to each vertex. Returns `None` for any vertex not connected to
|
||||||
|
/// `source`.
|
||||||
|
///
|
||||||
|
/// Prefer [`bfs_distances`] for unweighted graphs, it has better time and space complexity.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if `source` is not a valid vertex of `graph`.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use grapherity::prelude::*;
|
||||||
|
/// # use grapherity::algorithms::dijkstra_distances_unweighted;
|
||||||
|
/// # use grapherity::models::Graph;
|
||||||
|
/// let mut graph = Graph::new();
|
||||||
|
/// let source = graph.add_vertex();
|
||||||
|
/// let target = graph.add_vertex();
|
||||||
|
/// graph.add_edge(source, target);
|
||||||
|
///
|
||||||
|
/// let distances = dijkstra_distances_unweighted(&graph, source);
|
||||||
|
/// assert_eq!(distances[target], Some(1));
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// [Dijkstra's algorithm]: https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm
|
||||||
pub fn dijkstra_distances_unweighted<G>(
|
pub fn dijkstra_distances_unweighted<G>(
|
||||||
graph: &G,
|
graph: &G,
|
||||||
source: G::Vertex,
|
source: G::Vertex,
|
||||||
@@ -112,11 +260,44 @@ where
|
|||||||
distances
|
distances
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return data type for [`bfs`].
|
||||||
pub struct BfsResult<V: Copy> {
|
pub struct BfsResult<V: Copy> {
|
||||||
|
/// Vertex map of minimum distances from a given `source` vertex.
|
||||||
pub distances: VertexMap<V, Option<u32>>,
|
pub distances: VertexMap<V, Option<u32>>,
|
||||||
|
/// Vertex map of predecessors on some shortest path from a given `source` vertex.
|
||||||
pub predecessors: VertexMap<V, Option<V>>,
|
pub predecessors: VertexMap<V, Option<V>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// [Breadth-first search] traversal from `source`, returns distances and predecessors.
|
||||||
|
///
|
||||||
|
/// Traverses vertices in `graph` starting from `source`, exploring all neighbors before descending
|
||||||
|
/// further. Returns the distances from `source` to each vertex, and the predecessors of each vertex
|
||||||
|
/// on some shortest path from `source` to that vertex. Returns `None` for any vertex not connected
|
||||||
|
/// to `source`.
|
||||||
|
///
|
||||||
|
/// Time complexity is *O(|V| + |E|)*, space complexity is *O(|V|)*.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if `source` is not a valid vertex of `graph`.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use grapherity::prelude::*;
|
||||||
|
/// # use grapherity::algorithms::bfs;
|
||||||
|
/// # use grapherity::models::Graph;
|
||||||
|
/// let mut graph = Graph::new();
|
||||||
|
/// let source = graph.add_vertex();
|
||||||
|
/// let target = graph.add_vertex();
|
||||||
|
/// graph.add_edge(source, target);
|
||||||
|
///
|
||||||
|
/// let result = bfs(&graph, source);
|
||||||
|
/// assert_eq!(result.distances[target], Some(1));
|
||||||
|
/// assert_eq!(result.predecessors[target], Some(source));
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// [Breadth-first search]: https://en.wikipedia.org/wiki/Breadth-first_search
|
||||||
pub fn bfs<G>(graph: &G, source: G::Vertex) -> BfsResult<G::Vertex>
|
pub fn bfs<G>(graph: &G, source: G::Vertex) -> BfsResult<G::Vertex>
|
||||||
where
|
where
|
||||||
G: GraphTopology,
|
G: GraphTopology,
|
||||||
@@ -132,6 +313,34 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// [Breadth-first search] traversal from `source`, returns distances.
|
||||||
|
///
|
||||||
|
/// Traverses vertices in `graph` starting from `source`, exploring all neighbors before descending
|
||||||
|
/// further. Returns the distances from `source` to each vertex. Returns `None` for any vertex not
|
||||||
|
/// connected to `source`.
|
||||||
|
///
|
||||||
|
/// Time complexity is *O(|V| + |E|)*, space complexity is *O(|V|)*.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if `source` is not a valid vertex of `graph`.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use grapherity::prelude::*;
|
||||||
|
/// # use grapherity::algorithms::bfs_distances;
|
||||||
|
/// # use grapherity::models::Graph;
|
||||||
|
/// let mut graph = Graph::new();
|
||||||
|
/// let source = graph.add_vertex();
|
||||||
|
/// let target = graph.add_vertex();
|
||||||
|
/// graph.add_edge(source, target);
|
||||||
|
///
|
||||||
|
/// let distances = bfs_distances(&graph, source);
|
||||||
|
/// assert_eq!(distances[target], Some(1));
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// [Breadth-first search]: https://en.wikipedia.org/wiki/Breadth-first_search
|
||||||
pub fn bfs_distances<G>(graph: &G, source: G::Vertex) -> VertexMap<G::Vertex, Option<u32>>
|
pub fn bfs_distances<G>(graph: &G, source: G::Vertex) -> VertexMap<G::Vertex, Option<u32>>
|
||||||
where
|
where
|
||||||
G: GraphTopology,
|
G: GraphTopology,
|
||||||
@@ -139,6 +348,33 @@ where
|
|||||||
bfs_impl(graph, source, |_, _| true).distances
|
bfs_impl(graph, source, |_, _| true).distances
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// [Breadth-first search] from `source` for `target`, returns the distance.
|
||||||
|
///
|
||||||
|
/// Traverses vertices in `graph` starting from `source` until `target` is found, exploring all
|
||||||
|
/// neighbors before descending further. Returns the minimum distance from `source` to `target` if
|
||||||
|
/// `target` is a valid vertex of `graph` connected to `source`, or `None` otherwise.
|
||||||
|
///
|
||||||
|
/// Time complexity is *O(|V| + |E|)*, space complexity is *O(|V|)*.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if `source` is not a valid vertex of `graph`.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use grapherity::prelude::*;
|
||||||
|
/// # use grapherity::algorithms::bfs_find;
|
||||||
|
/// # use grapherity::models::Graph;
|
||||||
|
/// let mut graph = Graph::new();
|
||||||
|
/// let source = graph.add_vertex();
|
||||||
|
/// let target = graph.add_vertex();
|
||||||
|
/// graph.add_edge(source, target);
|
||||||
|
///
|
||||||
|
/// assert_eq!(bfs_find(&graph, source, target), Some(1));
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// [Breadth-first search]: https://en.wikipedia.org/wiki/Breadth-first_search
|
||||||
pub fn bfs_find<G>(graph: &G, source: G::Vertex, target: G::Vertex) -> Option<u32>
|
pub fn bfs_find<G>(graph: &G, source: G::Vertex, target: G::Vertex) -> Option<u32>
|
||||||
where
|
where
|
||||||
G: GraphTopology,
|
G: GraphTopology,
|
||||||
@@ -146,6 +382,35 @@ where
|
|||||||
bfs_find_where(graph, source, |v| v == target).map(|(_, distance)| distance)
|
bfs_find_where(graph, source, |v| v == target).map(|(_, distance)| distance)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// [Breadth-first search] from `source` for a vertex matching `predicate`, returns the vertex and
|
||||||
|
/// its distance.
|
||||||
|
///
|
||||||
|
/// Traverses vertices in `graph` starting from `source` until a vertex satisfying `predicate` is
|
||||||
|
/// found, exploring all neighbors before descending further. Returns the first vertex satisfying
|
||||||
|
/// `predicate` and its minimum distance from `source`, or `None` if no such vertex is connected to
|
||||||
|
/// `source`.
|
||||||
|
///
|
||||||
|
/// Time complexity is *O(|V| + |E|)*, space complexity is *O(|V|)*.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if `source` is not a valid vertex of `graph`.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use grapherity::prelude::*;
|
||||||
|
/// # use grapherity::algorithms::bfs_find_where;
|
||||||
|
/// # use grapherity::models::Graph;
|
||||||
|
/// let mut graph = Graph::new();
|
||||||
|
/// let source = graph.add_vertex();
|
||||||
|
/// let target = graph.add_vertex();
|
||||||
|
/// graph.add_edge(source, target);
|
||||||
|
///
|
||||||
|
/// assert_eq!(bfs_find_where(&graph, source, |v| v == target), Some((target, 1)));
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// [Breadth-first search]: https://en.wikipedia.org/wiki/Breadth-first_search
|
||||||
pub fn bfs_find_where<G, P>(graph: &G, source: G::Vertex, predicate: P) -> Option<(G::Vertex, u32)>
|
pub fn bfs_find_where<G, P>(graph: &G, source: G::Vertex, predicate: P) -> Option<(G::Vertex, u32)>
|
||||||
where
|
where
|
||||||
G: GraphTopology,
|
G: GraphTopology,
|
||||||
@@ -194,11 +459,44 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return data type for [`dfs`].
|
||||||
pub struct DfsResult<V: Copy> {
|
pub struct DfsResult<V: Copy> {
|
||||||
|
/// Vertex map indicating which vertices were visited during the search.
|
||||||
pub visited: VertexMap<V, bool>,
|
pub visited: VertexMap<V, bool>,
|
||||||
|
/// Vertex map of predecessors on the DFS tree path from a given `source` vertex.
|
||||||
pub predecessors: VertexMap<V, Option<V>>,
|
pub predecessors: VertexMap<V, Option<V>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// [Depth-first search] traversal from `source`, returns visited vertices and predecessors.
|
||||||
|
///
|
||||||
|
/// Traverses vertices in `graph` starting from `source`, exploring each branch as far as possible
|
||||||
|
/// before backtracking. Returns for each vertex whether it was visited, and the predecessors of
|
||||||
|
/// each vertex on the DFS tree path from `source` to that vertex. Any vertex not connected to
|
||||||
|
/// `source` is marked unvisited and has no predecessor.
|
||||||
|
///
|
||||||
|
/// Time complexity is *O(|V| + |E|)*, space complexity is *O(|V|)*.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if `source` is not a valid vertex of `graph`.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use grapherity::prelude::*;
|
||||||
|
/// # use grapherity::algorithms::dfs;
|
||||||
|
/// # use grapherity::models::Graph;
|
||||||
|
/// let mut graph = Graph::new();
|
||||||
|
/// let source = graph.add_vertex();
|
||||||
|
/// let target = graph.add_vertex();
|
||||||
|
/// graph.add_edge(source, target);
|
||||||
|
///
|
||||||
|
/// let result = dfs(&graph, source);
|
||||||
|
/// assert!(result.visited[target]);
|
||||||
|
/// assert_eq!(result.predecessors[target], Some(source));
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// [Depth-first search]: https://en.wikipedia.org/wiki/Depth-first_search
|
||||||
pub fn dfs<G>(graph: &G, source: G::Vertex) -> DfsResult<G::Vertex>
|
pub fn dfs<G>(graph: &G, source: G::Vertex) -> DfsResult<G::Vertex>
|
||||||
where
|
where
|
||||||
G: GraphTopology,
|
G: GraphTopology,
|
||||||
@@ -214,6 +512,34 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// [Depth-first search] traversal from `source`, returns visited vertices.
|
||||||
|
///
|
||||||
|
/// Traverses vertices in `graph` starting from `source`, exploring each branch as far as possible
|
||||||
|
/// before backtracking. Returns a map indicating which vertices were visited, i.e. which vertices
|
||||||
|
/// are connected to `source`.
|
||||||
|
///
|
||||||
|
/// Time complexity is *O(|V| + |E|)*, space complexity is *O(|V|)*.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if `source` is not a valid vertex of `graph`.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use grapherity::prelude::*;
|
||||||
|
/// # use grapherity::algorithms::dfs_visited;
|
||||||
|
/// # use grapherity::models::Graph;
|
||||||
|
/// let mut graph = Graph::new();
|
||||||
|
/// let source = graph.add_vertex();
|
||||||
|
/// let target = graph.add_vertex();
|
||||||
|
/// graph.add_edge(source, target);
|
||||||
|
///
|
||||||
|
/// let visited = dfs_visited(&graph, source);
|
||||||
|
/// assert!(visited[target]);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// [Depth-first search]: https://en.wikipedia.org/wiki/Depth-first_search
|
||||||
pub fn dfs_visited<G>(graph: &G, source: G::Vertex) -> VertexMap<G::Vertex, bool>
|
pub fn dfs_visited<G>(graph: &G, source: G::Vertex) -> VertexMap<G::Vertex, bool>
|
||||||
where
|
where
|
||||||
G: GraphTopology,
|
G: GraphTopology,
|
||||||
@@ -221,6 +547,33 @@ where
|
|||||||
dfs_impl(graph, source, |_, _| true).visited
|
dfs_impl(graph, source, |_, _| true).visited
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// [Depth-first search] from `source` for `target`, returns whether it was found.
|
||||||
|
///
|
||||||
|
/// Traverses vertices in `graph` starting from `source` until `target` is found, exploring each
|
||||||
|
/// branch as far as possible before backtracking. Returns `true` if `target` is a valid vertex of
|
||||||
|
/// `graph` connected to `source`, or `false` otherwise.
|
||||||
|
///
|
||||||
|
/// Time complexity is *O(|V| + |E|)*, space complexity is *O(|V|)*.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if `source` is not a valid vertex of `graph`.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use grapherity::prelude::*;
|
||||||
|
/// # use grapherity::algorithms::dfs_find;
|
||||||
|
/// # use grapherity::models::Graph;
|
||||||
|
/// let mut graph = Graph::new();
|
||||||
|
/// let source = graph.add_vertex();
|
||||||
|
/// let target = graph.add_vertex();
|
||||||
|
/// graph.add_edge(source, target);
|
||||||
|
///
|
||||||
|
/// assert!(dfs_find(&graph, source, target));
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// [Depth-first search]: https://en.wikipedia.org/wiki/Depth-first_search
|
||||||
pub fn dfs_find<G>(graph: &G, source: G::Vertex, target: G::Vertex) -> bool
|
pub fn dfs_find<G>(graph: &G, source: G::Vertex, target: G::Vertex) -> bool
|
||||||
where
|
where
|
||||||
G: GraphTopology,
|
G: GraphTopology,
|
||||||
@@ -228,6 +581,33 @@ where
|
|||||||
dfs_find_where(graph, source, |v| v == target).is_some()
|
dfs_find_where(graph, source, |v| v == target).is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// [Depth-first search] from `source` for a vertex matching `predicate`, returns the vertex.
|
||||||
|
///
|
||||||
|
/// Traverses vertices in `graph` starting from `source` until a vertex satisfying `predicate` is
|
||||||
|
/// found, exploring each branch as far as possible before backtracking. Returns the first vertex
|
||||||
|
/// satisfying `predicate`, or `None` if no such vertex is connected to `source`.
|
||||||
|
///
|
||||||
|
/// Time complexity is *O(|V| + |E|)*, space complexity is *O(|V|)*.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if `source` is not a valid vertex of `graph`.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use grapherity::prelude::*;
|
||||||
|
/// # use grapherity::algorithms::dfs_find_where;
|
||||||
|
/// # use grapherity::models::Graph;
|
||||||
|
/// let mut graph = Graph::new();
|
||||||
|
/// let source = graph.add_vertex();
|
||||||
|
/// let target = graph.add_vertex();
|
||||||
|
/// graph.add_edge(source, target);
|
||||||
|
///
|
||||||
|
/// assert_eq!(dfs_find_where(&graph, source, |v| v == target), Some(target));
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// [Depth-first search]: https://en.wikipedia.org/wiki/Depth-first_search
|
||||||
pub fn dfs_find_where<G, P>(graph: &G, source: G::Vertex, predicate: P) -> Option<G::Vertex>
|
pub fn dfs_find_where<G, P>(graph: &G, source: G::Vertex, predicate: P) -> Option<G::Vertex>
|
||||||
where
|
where
|
||||||
G: GraphTopology,
|
G: GraphTopology,
|
||||||
@@ -275,6 +655,35 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// [Depth-first search] from `source` for `target`, returns the path as a sequence of edges.
|
||||||
|
///
|
||||||
|
/// Traverses vertices in `graph` starting from `source` until `target` is found, exploring each
|
||||||
|
/// branch as far as possible before backtracking. Returns some path from `source` to `target` as a
|
||||||
|
/// sequence of edges in traversal order if `target` is a valid vertex of `graph` connected to
|
||||||
|
/// `source`, or `None` otherwise. The returned path is empty if and only if `source` equals
|
||||||
|
/// `target`.
|
||||||
|
///
|
||||||
|
/// Time complexity is *O(|V| + |E|)*, space complexity is *O(|V|)*.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if `source` is not a valid vertex of `graph`.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use grapherity::prelude::*;
|
||||||
|
/// # use grapherity::algorithms::dfs_find_path;
|
||||||
|
/// # use grapherity::models::Graph;
|
||||||
|
/// let mut graph = Graph::new();
|
||||||
|
/// let source = graph.add_vertex();
|
||||||
|
/// let target = graph.add_vertex();
|
||||||
|
/// let e = graph.add_edge(source, target);
|
||||||
|
///
|
||||||
|
/// assert_eq!(dfs_find_path(&graph, source, target), Some(vec![e]));
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// [Depth-first search]: https://en.wikipedia.org/wiki/Depth-first_search
|
||||||
pub fn dfs_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
|
where
|
||||||
G: GraphTopology,
|
G: GraphTopology,
|
||||||
@@ -282,6 +691,36 @@ where
|
|||||||
dfs_find_path_where(graph, source, |v| v == target)
|
dfs_find_path_where(graph, source, |v| v == target)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// [Depth-first search] from `source` for a vertex matching `predicate`, returns the path as a
|
||||||
|
/// sequence of edges.
|
||||||
|
///
|
||||||
|
/// Traverses vertices in `graph` starting from `source` until a vertex satisfying `predicate` is
|
||||||
|
/// found, exploring each branch as far as possible before backtracking. Returns some path from
|
||||||
|
/// `source` to the first vertex satisfying `predicate` as a sequence of edges in traversal order,
|
||||||
|
/// or `None` if no such vertex is connected to `source`. The returned path is empty if and only if
|
||||||
|
/// `source` satisfies `predicate`.
|
||||||
|
///
|
||||||
|
/// Time complexity is *O(|V| + |E|)*, space complexity is *O(|V|)*.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if `source` is not a valid vertex of `graph`.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use grapherity::prelude::*;
|
||||||
|
/// # use grapherity::algorithms::dfs_find_path_where;
|
||||||
|
/// # use grapherity::models::Graph;
|
||||||
|
/// let mut graph = Graph::new();
|
||||||
|
/// let source = graph.add_vertex();
|
||||||
|
/// let target = graph.add_vertex();
|
||||||
|
/// let e = graph.add_edge(source, target);
|
||||||
|
///
|
||||||
|
/// assert_eq!(dfs_find_path_where(&graph, source, |v| v == target), Some(vec![e]));
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// [Depth-first search]: https://en.wikipedia.org/wiki/Depth-first_search
|
||||||
pub fn dfs_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
|
where
|
||||||
G: GraphTopology,
|
G: GraphTopology,
|
||||||
|
|||||||
+132
@@ -1,8 +1,140 @@
|
|||||||
|
//! 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 algorithms;
|
||||||
pub mod maps;
|
pub mod maps;
|
||||||
pub mod models;
|
pub mod models;
|
||||||
pub mod traits;
|
pub mod traits;
|
||||||
|
|
||||||
|
/// Convenience re-exports of graph topology traits for common use.
|
||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
pub use crate::traits::{GraphTopology, GraphTopologyDeletion};
|
pub use crate::traits::{GraphTopology, GraphTopologyDeletion};
|
||||||
}
|
}
|
||||||
|
|||||||
+67
-6
@@ -1,31 +1,65 @@
|
|||||||
|
//! Provides maps to associate custom data to graph vertices and edges.
|
||||||
|
//!
|
||||||
|
//! [`VertexMap`] and [`EdgeMap`] provide copy-on-write, [`Vec`]-backed maps to associate data to
|
||||||
|
//! all vertices or all edges in a graph, respectively.
|
||||||
|
|
||||||
use std::ops::{Index, IndexMut};
|
use std::ops::{Index, IndexMut};
|
||||||
|
|
||||||
use crate::traits::GraphTopology;
|
use crate::traits::GraphTopology;
|
||||||
|
|
||||||
|
/// A map to associate custom data to graph vertices.
|
||||||
|
///
|
||||||
|
/// This map uses raw entity indices to associate homogenous custom data of type `T` to graph
|
||||||
|
/// vertices. The implementation uses a [`Vec`], allocating contiguous slots for the data, which
|
||||||
|
/// means that the provided index conversion function should map vertices to contiguous indices, or
|
||||||
|
/// indices with relatively few gaps.
|
||||||
|
///
|
||||||
|
/// Data allocation happens as copy-on-write, i.e. the backing [`Vec`] is only resized if a value
|
||||||
|
/// beyond current capacity is written, but the `default` value can transparently be read. No bound
|
||||||
|
/// or validity checks are performed by the map on the provided vertex handles.
|
||||||
|
///
|
||||||
|
/// Use [`GraphTopology::vertex_map`] to obtain a `VertexMap`.
|
||||||
pub struct VertexMap<V: Copy, T: Clone> {
|
pub struct VertexMap<V: Copy, T: Clone> {
|
||||||
inner: EntityMap<V, T>,
|
inner: EntityMap<V, T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<V: Copy, T: Clone> VertexMap<V, T> {
|
impl<V: Copy, T: Clone> VertexMap<V, T> {
|
||||||
|
/// Creates a new map with the given `default` value, an index conversion function `to_index`,
|
||||||
|
/// and an initial `capacity`.
|
||||||
pub fn new(default: T, to_index: fn(V) -> usize, capacity: usize) -> Self {
|
pub fn new(default: T, to_index: fn(V) -> usize, capacity: usize) -> Self {
|
||||||
Self {
|
Self {
|
||||||
inner: EntityMap::new(default, to_index, capacity),
|
inner: EntityMap::new(default, to_index, capacity),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reads beyond 'capacity' are valid and return the default value.
|
/// Returns the total number of data entries the map can write without reallocating. Reads
|
||||||
|
/// beyond `capacity` are valid and return the default value.
|
||||||
pub fn capacity(&self) -> usize {
|
pub fn capacity(&self) -> usize {
|
||||||
self.inner.capacity()
|
self.inner.capacity()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[deprecated(since = "0.2.2", note = "use 'capacity' instead")]
|
#[deprecated(since = "0.2.2", note = "use 'capacity' instead")]
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub fn len(&self) -> usize {
|
pub fn len(&self) -> usize {
|
||||||
self.capacity()
|
self.capacity()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Expands the internal data storage capacity of the map to `capacity`. Does nothing if
|
||||||
|
/// capacity is already sufficient.
|
||||||
|
///
|
||||||
|
/// Use this before writing data for new graph vertices to avoid incremental growth on the first
|
||||||
|
/// write to each new vertex.
|
||||||
|
pub fn expand(&mut self, capacity: usize) {
|
||||||
|
self.inner.expand(capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[deprecated(
|
||||||
|
since = "0.2.4",
|
||||||
|
note = "use 'expand(graph.vertex_capacity())' instead"
|
||||||
|
)]
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub fn sync<G: GraphTopology<Vertex = V>>(&mut self, graph: &G) {
|
pub fn sync<G: GraphTopology<Vertex = V>>(&mut self, graph: &G) {
|
||||||
self.inner.resize(graph.vertex_capacity());
|
self.inner.expand(graph.vertex_capacity());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,29 +77,56 @@ impl<V: Copy, T: Clone> IndexMut<V> for VertexMap<V, T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A map to associate custom data to graph edges.
|
||||||
|
///
|
||||||
|
/// This map uses raw entity indices to associate homogenous custom data of type `T` to graph edges.
|
||||||
|
/// The implementation uses a [`Vec`], allocating contiguous slots for the data, which means that
|
||||||
|
/// the provided index conversion function should map edges to contiguous indices, or indices with
|
||||||
|
/// relatively few gaps.
|
||||||
|
///
|
||||||
|
/// Data allocation happens as copy-on-write, i.e. the backing [`Vec`] is only resized if a value
|
||||||
|
/// beyond current capacity is written, but the `default` value can transparently be read. No bound
|
||||||
|
/// or validity checks are performed by the map on the provided edge handles.
|
||||||
|
///
|
||||||
|
/// Use [`GraphTopology::edge_map`] to obtain an `EdgeMap`.
|
||||||
pub struct EdgeMap<E: Copy, T: Clone> {
|
pub struct EdgeMap<E: Copy, T: Clone> {
|
||||||
inner: EntityMap<E, T>,
|
inner: EntityMap<E, T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E: Copy, T: Clone> EdgeMap<E, T> {
|
impl<E: Copy, T: Clone> EdgeMap<E, T> {
|
||||||
|
/// Creates a new map with the given `default` value, an index conversion function `to_index`,
|
||||||
|
/// and an initial `capacity`.
|
||||||
pub fn new(default: T, to_index: fn(E) -> usize, capacity: usize) -> Self {
|
pub fn new(default: T, to_index: fn(E) -> usize, capacity: usize) -> Self {
|
||||||
Self {
|
Self {
|
||||||
inner: EntityMap::new(default, to_index, capacity),
|
inner: EntityMap::new(default, to_index, capacity),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reads beyond 'capacity' are valid and return the default value.
|
/// Returns the total number of data entries the map can write without reallocating. Reads
|
||||||
|
/// beyond `capacity` are valid and return the default value.
|
||||||
pub fn capacity(&self) -> usize {
|
pub fn capacity(&self) -> usize {
|
||||||
self.inner.capacity()
|
self.inner.capacity()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[deprecated(since = "0.2.2", note = "use 'capacity' instead")]
|
#[deprecated(since = "0.2.2", note = "use 'capacity' instead")]
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub fn len(&self) -> usize {
|
pub fn len(&self) -> usize {
|
||||||
self.capacity()
|
self.capacity()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Expands the internal data storage capacity of the map to `capacity`. Does nothing if
|
||||||
|
/// capacity is already sufficient.
|
||||||
|
///
|
||||||
|
/// Use this before writing data for new graph edges to avoid incremental growth on the first
|
||||||
|
/// write to each new edge.
|
||||||
|
pub fn expand(&mut self, capacity: usize) {
|
||||||
|
self.inner.expand(capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[deprecated(since = "0.2.4", note = "use 'expand(graph.edge_capacity())' instead")]
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub fn sync<G: GraphTopology<Edge = E>>(&mut self, graph: &G) {
|
pub fn sync<G: GraphTopology<Edge = E>>(&mut self, graph: &G) {
|
||||||
self.inner.resize(graph.edge_capacity());
|
self.inner.expand(graph.edge_capacity());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,8 +163,8 @@ impl<E: Copy, T: Clone> EntityMap<E, T> {
|
|||||||
self.data.len()
|
self.data.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn resize(&mut self, capacity: usize) {
|
pub fn expand(&mut self, capacity: usize) {
|
||||||
if capacity > self.data.len() {
|
if capacity > self.data.capacity() {
|
||||||
self.data.resize(capacity, self.default.clone());
|
self.data.resize(capacity, self.default.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
|
//! Concrete graph topology models.
|
||||||
|
|
||||||
pub mod append_graph;
|
pub mod append_graph;
|
||||||
pub mod graph;
|
pub mod graph;
|
||||||
|
|
||||||
|
// TODO: Compressed-sparse-row graph model.
|
||||||
|
|
||||||
pub use append_graph::{AppendGraph, AppendGraphEdgeMap, AppendGraphVertexMap};
|
pub use append_graph::{AppendGraph, AppendGraphEdgeMap, AppendGraphVertexMap};
|
||||||
pub use graph::{Graph, GraphEdgeMap, GraphVertexMap};
|
pub use graph::{Graph, GraphEdgeMap, GraphVertexMap};
|
||||||
|
|||||||
@@ -1,13 +1,26 @@
|
|||||||
|
//! [`AppendGraph`], an undirected graph topology supporting addition only.
|
||||||
|
|
||||||
use crate::maps::{EdgeMap, VertexMap};
|
use crate::maps::{EdgeMap, VertexMap};
|
||||||
use crate::traits::{GraphTopology, IncidenceCursor};
|
use crate::traits::{GraphTopology, IncidenceCursor};
|
||||||
|
|
||||||
|
/// An opaque handle identifying a vertex in an [`AppendGraph`].
|
||||||
|
///
|
||||||
|
/// Handles are stable for the lifetime of the graph. Obtain via graph methods like
|
||||||
|
/// [`AppendGraph::add_vertex`] and [`AppendGraph::vertices`].
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||||
pub struct Vertex(usize);
|
pub struct Vertex(usize);
|
||||||
|
|
||||||
|
/// An opaque handle identifying an edge in an [`AppendGraph`].
|
||||||
|
///
|
||||||
|
/// Handles are stable for the lifetime of the graph. Obtain via graph methods like
|
||||||
|
/// [`AppendGraph::add_edge`] and [`AppendGraph::edges`].
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||||
pub struct Edge(usize);
|
pub struct Edge(usize);
|
||||||
|
|
||||||
|
/// A [`VertexMap`] for [`AppendGraph`] vertices.
|
||||||
pub type AppendGraphVertexMap<T> = VertexMap<Vertex, T>;
|
pub type AppendGraphVertexMap<T> = VertexMap<Vertex, T>;
|
||||||
|
|
||||||
|
/// An [`EdgeMap`] for [`AppendGraph`] edges.
|
||||||
pub type AppendGraphEdgeMap<T> = EdgeMap<Edge, T>;
|
pub type AppendGraphEdgeMap<T> = EdgeMap<Edge, T>;
|
||||||
|
|
||||||
impl Edge {
|
impl Edge {
|
||||||
@@ -16,6 +29,7 @@ impl Edge {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Check if VertexIncidenceHeader and IncidenceEntry can be made smaller. Currently they both take 24 bytes (on 64bit), see https://stackoverflow.com/a/79653173
|
||||||
struct VertexIncidenceHeader {
|
struct VertexIncidenceHeader {
|
||||||
incidence_count: usize,
|
incidence_count: usize,
|
||||||
first_incidence: Option<Edge>,
|
first_incidence: Option<Edge>,
|
||||||
@@ -27,6 +41,9 @@ struct IncidenceEntry {
|
|||||||
adjacent: Vertex,
|
adjacent: Vertex,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A resumable cursor over the incidences of a single vertex in an [`AppendGraph`].
|
||||||
|
///
|
||||||
|
/// Obtain via [`AppendGraph::incidence_cursor`]. See [`IncidenceCursor`] on usage guidance.
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub struct AppendGraphIncidenceCursor {
|
pub struct AppendGraphIncidenceCursor {
|
||||||
incidence: Option<Edge>,
|
incidence: Option<Edge>,
|
||||||
@@ -34,16 +51,50 @@ pub struct AppendGraphIncidenceCursor {
|
|||||||
|
|
||||||
impl IncidenceCursor<AppendGraph> for AppendGraphIncidenceCursor {
|
impl IncidenceCursor<AppendGraph> for AppendGraphIncidenceCursor {
|
||||||
fn next(&mut self, graph: &AppendGraph) -> Option<(Vertex, Edge)> {
|
fn next(&mut self, graph: &AppendGraph) -> Option<(Vertex, Edge)> {
|
||||||
graph.step_incidence(&mut self.incidence)
|
graph
|
||||||
|
.step_incidence(&mut self.incidence)
|
||||||
|
.map(|(v, e)| (v, e.normalize()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An undirected graph that supports adding vertices and edges, but not deleting them.
|
||||||
|
///
|
||||||
|
/// `AppendGraph` is optimised for workloads that incrementally build a graph and query it
|
||||||
|
/// repeatedly. [`Vertex`] and [`Edge`] handles are never invalidated. Use [`Graph`] instead if you
|
||||||
|
/// need to remove vertices or edges.
|
||||||
|
///
|
||||||
|
/// Incidences are stored as interleaved adjacency lists in a single flat [`Vec`]. In general,
|
||||||
|
/// vertex neighborhood traversals result in scattered index jumps.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use grapherity::prelude::*;
|
||||||
|
/// use grapherity::models::AppendGraph;
|
||||||
|
///
|
||||||
|
/// let mut graph = AppendGraph::new();
|
||||||
|
/// let v1 = graph.add_vertex();
|
||||||
|
/// let v2 = graph.add_vertex();
|
||||||
|
/// let e = graph.add_edge(v1, v2);
|
||||||
|
/// assert!(graph.are_adjacent(v1, v2));
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Time and space complexity
|
||||||
|
///
|
||||||
|
/// Both [`add_vertex`] and [`add_edge`] run in amortised *O(1)* time. [`degree`] runs in *O(1)*
|
||||||
|
/// time since vertex degrees are stored. Space complexity is *O(|V| + |E|)*.
|
||||||
|
///
|
||||||
|
/// [`add_vertex`]: Self::add_vertex
|
||||||
|
/// [`add_edge`]: Self::add_edge
|
||||||
|
/// [`degree`]: Self::degree
|
||||||
|
/// [`Graph`]: crate::models::Graph
|
||||||
pub struct AppendGraph {
|
pub struct AppendGraph {
|
||||||
vertices: Vec<VertexIncidenceHeader>,
|
vertices: Vec<VertexIncidenceHeader>,
|
||||||
incidences: Vec<IncidenceEntry>,
|
incidences: Vec<IncidenceEntry>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppendGraph {
|
impl AppendGraph {
|
||||||
|
/// Creates an empty graph instance with no vertices or edges.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
vertices: vec![],
|
vertices: vec![],
|
||||||
@@ -51,8 +102,8 @@ impl AppendGraph {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adds a single incidence of an edge, which is composed by two such incidences, to the
|
/// Adds a single incidence of an edge, which is composed by two such incidences, to the
|
||||||
// incidences vector.
|
/// incidences vector.
|
||||||
fn add_incidence(&mut self, v1: Vertex, v2: Vertex) {
|
fn add_incidence(&mut self, v1: Vertex, v2: Vertex) {
|
||||||
self.incidences.push(IncidenceEntry {
|
self.incidences.push(IncidenceEntry {
|
||||||
next: self.vertices[v1.0].first_incidence.take(),
|
next: self.vertices[v1.0].first_incidence.take(),
|
||||||
@@ -91,7 +142,7 @@ impl GraphTopology for AppendGraph {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn vertex_capacity(&self) -> usize {
|
fn vertex_capacity(&self) -> usize {
|
||||||
self.vertices.len()
|
self.vertices.capacity()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn vertex_map<T: Clone>(&self, default: T) -> VertexMap<Self::Vertex, T> {
|
fn vertex_map<T: Clone>(&self, default: T) -> VertexMap<Self::Vertex, T> {
|
||||||
@@ -103,7 +154,7 @@ impl GraphTopology for AppendGraph {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn edge_capacity(&self) -> usize {
|
fn edge_capacity(&self) -> usize {
|
||||||
self.incidences.len() / 2
|
self.incidences.capacity() / 2
|
||||||
}
|
}
|
||||||
|
|
||||||
fn edge_map<T: Clone>(&self, default: T) -> EdgeMap<Self::Edge, T> {
|
fn edge_map<T: Clone>(&self, default: T) -> EdgeMap<Self::Edge, T> {
|
||||||
|
|||||||
+88
-11
@@ -1,12 +1,26 @@
|
|||||||
|
//! [`Graph`], an undirected graph topology supporting addition and deletion.
|
||||||
|
|
||||||
use typed_generational_arena::{Arena, Index};
|
use typed_generational_arena::{Arena, Index};
|
||||||
|
|
||||||
use crate::maps::{EdgeMap, VertexMap};
|
use crate::maps::{EdgeMap, VertexMap};
|
||||||
use crate::traits::{GraphTopology, GraphTopologyDeletion, IncidenceCursor};
|
use crate::traits::{GraphTopology, GraphTopologyDeletion, IncidenceCursor};
|
||||||
|
|
||||||
|
/// An opaque handle identifying a vertex in a [`Graph`].
|
||||||
|
///
|
||||||
|
/// Handles remain valid until the vertex is explicitly deleted. Obtain via graph methods like
|
||||||
|
/// [`Graph::add_vertex`] and [`Graph::vertices`].
|
||||||
pub type Vertex = Index<VertexIncidenceHeader, usize, usize>;
|
pub type Vertex = Index<VertexIncidenceHeader, usize, usize>;
|
||||||
|
|
||||||
|
/// An opaque handle identifying an edge in a [`Graph`].
|
||||||
|
///
|
||||||
|
/// Handles remain valid until the edge is explicitly deleted, or one of its endpoint vertices is
|
||||||
|
/// deleted. Obtain via graph methods like [`Graph::add_edge`] and [`Graph::edges`].
|
||||||
pub type Edge = Index<IncidenceEntry, usize, usize>;
|
pub type Edge = Index<IncidenceEntry, usize, usize>;
|
||||||
|
|
||||||
|
/// A [`VertexMap`] for [`Graph`] vertices.
|
||||||
pub type GraphVertexMap<T> = VertexMap<Vertex, T>;
|
pub type GraphVertexMap<T> = VertexMap<Vertex, T>;
|
||||||
|
|
||||||
|
/// An [`EdgeMap`] for [`Graph`] edges.
|
||||||
pub type GraphEdgeMap<T> = EdgeMap<Edge, T>;
|
pub type GraphEdgeMap<T> = EdgeMap<Edge, T>;
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||||
@@ -15,11 +29,18 @@ struct VertexSlot(usize);
|
|||||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||||
struct IncidenceSlot(usize);
|
struct IncidenceSlot(usize);
|
||||||
|
|
||||||
|
// TODO: Check if VertexIncidenceHeader and IncidenceEntry can be made smaller. Currently they both take 24 bytes (on 64bit), see https://stackoverflow.com/a/79653173
|
||||||
|
/// `pub` because [`Vertex`] references it as a type parameter of the underlying arena. Not
|
||||||
|
/// intended for direct external use.
|
||||||
|
#[doc(hidden)]
|
||||||
pub struct VertexIncidenceHeader {
|
pub struct VertexIncidenceHeader {
|
||||||
incidence_count: usize,
|
incidence_count: usize,
|
||||||
first_incidence: Option<IncidenceSlot>,
|
first_incidence: Option<IncidenceSlot>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `pub` because [`Edge`] references it as a type parameter of the underlying arena. Not intended
|
||||||
|
/// for direct external use.
|
||||||
|
#[doc(hidden)]
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub struct IncidenceEntry {
|
pub struct IncidenceEntry {
|
||||||
next: Option<IncidenceSlot>,
|
next: Option<IncidenceSlot>,
|
||||||
@@ -36,6 +57,9 @@ impl IncidentEdgeCursor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A resumable cursor over the incidences of a single vertex in a [`Graph`].
|
||||||
|
///
|
||||||
|
/// Obtain via [`Graph::incidence_cursor`]. See [`IncidenceCursor`] on usage guidance.
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub struct GraphIncidenceCursor {
|
pub struct GraphIncidenceCursor {
|
||||||
incidence: Option<IncidenceSlot>,
|
incidence: Option<IncidenceSlot>,
|
||||||
@@ -43,12 +67,58 @@ pub struct GraphIncidenceCursor {
|
|||||||
|
|
||||||
impl IncidenceCursor<Graph> for GraphIncidenceCursor {
|
impl IncidenceCursor<Graph> for GraphIncidenceCursor {
|
||||||
fn next(&mut self, graph: &Graph) -> Option<(Vertex, Edge)> {
|
fn next(&mut self, graph: &Graph) -> Option<(Vertex, Edge)> {
|
||||||
graph
|
graph.step_incidence(&mut self.incidence).map(|(vs, e)| {
|
||||||
.step_incidence(&mut self.incidence)
|
(
|
||||||
.map(|(vs, e)| (graph.vertices.get_idx(vs.0).unwrap(), e))
|
graph.vertices.get_idx(vs.0).unwrap(),
|
||||||
|
graph.normalize_edge(e),
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An undirected graph that supports adding vertices and edges, and deleting them.
|
||||||
|
///
|
||||||
|
/// `Graph` is suited for workloads that need to modify the graph structure over time, i.e. adding
|
||||||
|
/// amd removing vertices and edges. [`Vertex`] and [`Edge`] handles remain valid until the vertex
|
||||||
|
/// or edge they identify is explicitly deleted. Use [`AppendGraph`] instead if you only need to
|
||||||
|
/// append vertices and edges.
|
||||||
|
///
|
||||||
|
/// Incidences are stored as interleaved adjacency lists in a generational [`Arena`]. In general,
|
||||||
|
/// vertex neighborhood traversals result in scattered memory accesses.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use grapherity::prelude::*;
|
||||||
|
/// use grapherity::models::Graph;
|
||||||
|
///
|
||||||
|
/// let mut graph = Graph::new();
|
||||||
|
/// let v1 = graph.add_vertex();
|
||||||
|
/// let v2 = graph.add_vertex();
|
||||||
|
/// let _e = graph.add_edge(v1, v2);
|
||||||
|
/// assert!(graph.are_adjacent(v1, v2));
|
||||||
|
/// graph.delete_vertex(v1);
|
||||||
|
/// assert_eq!(graph.degree(v2), 0);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Time and space complexity
|
||||||
|
///
|
||||||
|
/// Both [`add_vertex`] and [`add_edge`] run in amortised *O(1)* time. [`degree`] runs in *O(1)*
|
||||||
|
/// time since vertex degrees are stored. [`delete_vertex`] runs in *O(degree(v))* time.
|
||||||
|
/// [`delete_edge`] runs in *O(degree(u) + degree(v))* time, where *u* and *v* are the edge's
|
||||||
|
/// endpoints.
|
||||||
|
///
|
||||||
|
/// Space complexity is *O(|V| + |E|)*, but freed slots for vertices and edges are not compacted and
|
||||||
|
/// their allocation is never reclaimed. Capacity only grows. If additions and deletions are both
|
||||||
|
/// planned, performing deletions first allows freed slots to be reused by subsequent additions,
|
||||||
|
/// potentially avoiding reallocation.
|
||||||
|
///
|
||||||
|
/// [`add_vertex`]: Self::add_vertex
|
||||||
|
/// [`add_edge`]: Self::add_edge
|
||||||
|
/// [`degree`]: Self::degree
|
||||||
|
/// [`delete_vertex`]: Self::delete_vertex
|
||||||
|
/// [`delete_edge`]: Self::delete_edge
|
||||||
|
/// [`AppendGraph`]: crate::models::AppendGraph
|
||||||
pub struct Graph {
|
pub struct Graph {
|
||||||
// TODO: Arena index and generation types could be externalized to Graph.
|
// TODO: Arena index and generation types could be externalized to Graph.
|
||||||
vertices: Arena<VertexIncidenceHeader, usize, usize>,
|
vertices: Arena<VertexIncidenceHeader, usize, usize>,
|
||||||
@@ -56,6 +126,7 @@ pub struct Graph {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Graph {
|
impl Graph {
|
||||||
|
/// Creates an empty graph instance with no vertices or edges.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
vertices: Arena::new(),
|
vertices: Arena::new(),
|
||||||
@@ -63,8 +134,8 @@ impl Graph {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adds a single incidence of an edge, which is composed by two such incidences, to the
|
/// Adds a single incidence of an edge, which is composed by two such incidences, to the
|
||||||
// incidences arena, and returns its index.
|
/// incidences arena, and returns its index.
|
||||||
fn add_incidence(&mut self, v1: Vertex, v2: Vertex) -> Edge {
|
fn add_incidence(&mut self, v1: Vertex, v2: Vertex) -> Edge {
|
||||||
let edge = self.incidences.insert(IncidenceEntry {
|
let edge = self.incidences.insert(IncidenceEntry {
|
||||||
next: self.vertices[v1].first_incidence.take(),
|
next: self.vertices[v1].first_incidence.take(),
|
||||||
@@ -91,8 +162,9 @@ impl Graph {
|
|||||||
(f, e_entry, f_entry)
|
(f, e_entry, f_entry)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Updates the source vertex incidence list after the incidence "e" was deleted from the
|
/// Updates the `source` vertex incidence list after the incidence `e` was deleted from the
|
||||||
// incidence arena. "next" is the next incidence after "e" in the source vertex incidence list.
|
/// incidence arena. `next` is the next incidence after `e` in the `source` vertex incidence
|
||||||
|
/// list.
|
||||||
fn update_incidence_list(
|
fn update_incidence_list(
|
||||||
&mut self,
|
&mut self,
|
||||||
e: Edge,
|
e: Edge,
|
||||||
@@ -140,7 +212,12 @@ impl Graph {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn normalize_edge(&self, e: Edge) -> Edge {
|
fn normalize_edge(&self, e: Edge) -> Edge {
|
||||||
self.incidences.get_idx(e.arr_idx() & !1).unwrap()
|
let i = e.arr_idx();
|
||||||
|
if i & 1 == 0 {
|
||||||
|
e
|
||||||
|
} else {
|
||||||
|
self.incidences.get_idx(i ^ 1).unwrap()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -267,10 +344,10 @@ impl GraphTopologyDeletion for Graph {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The incidence entries are removed before patching the linked lists. This is safe because
|
|
||||||
// update_incidence_list() searches by raw slot index (IncidenceSlot.0) and only dereferences
|
|
||||||
// the predecessor, never the removed entries themselves.
|
|
||||||
fn delete_edge(&mut self, e: Self::Edge) {
|
fn delete_edge(&mut self, e: Self::Edge) {
|
||||||
|
// The incidence entries are removed before patching the linked lists. This is safe because
|
||||||
|
// `update_incidence_list` searches by raw slot index (IncidenceSlot.0) and only
|
||||||
|
// dereferences the predecessor, never the removed entries themselves.
|
||||||
let (f, e_entry, f_entry) = self.remove_incidence_pair(e);
|
let (f, e_entry, f_entry) = self.remove_incidence_pair(e);
|
||||||
if e_entry.adjacent != f_entry.adjacent {
|
if e_entry.adjacent != f_entry.adjacent {
|
||||||
self.update_incidence_list(e, f_entry.adjacent, e_entry.next, false);
|
self.update_incidence_list(e, f_entry.adjacent, e_entry.next, false);
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
//! Test fixture and test macros for graph topology and algorithm implementations.
|
||||||
|
|
||||||
pub(crate) mod bfs_testing;
|
pub(crate) mod bfs_testing;
|
||||||
pub(crate) mod dfs_testing;
|
pub(crate) mod dfs_testing;
|
||||||
pub(crate) mod dijkstra_testing;
|
pub(crate) mod dijkstra_testing;
|
||||||
|
|||||||
@@ -298,7 +298,7 @@ macro_rules! graph_topology_tests {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
vertices.iter().filter(|&x| *x == v).count(),
|
vertices.iter().filter(|&x| *x == v).count(),
|
||||||
1,
|
1,
|
||||||
"unexpected vertex {v:?} from the iterator"
|
"unexpected vertex {v:?} from iterator"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -339,7 +339,7 @@ macro_rules! graph_topology_tests {
|
|||||||
.iter()
|
.iter()
|
||||||
.position(|w| *w == v)
|
.position(|w| *w == v)
|
||||||
.expect(&format!(
|
.expect(&format!(
|
||||||
"unexpected adjacent vertex {v:?} of {:?} from the iterator",
|
"unexpected adjacent vertex {v:?} of {:?} from iterator",
|
||||||
vertices[4]
|
vertices[4]
|
||||||
));
|
));
|
||||||
expected_adjacency.swap_remove(i);
|
expected_adjacency.swap_remove(i);
|
||||||
@@ -347,7 +347,7 @@ macro_rules! graph_topology_tests {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
expected_adjacency.len(),
|
expected_adjacency.len(),
|
||||||
0,
|
0,
|
||||||
"expected adjacent vertices {:?} of {:?} were not matched by the iterator",
|
"expected adjacent vertices {:?} of {:?} were not matched by iterator",
|
||||||
expected_adjacency,
|
expected_adjacency,
|
||||||
vertices[4]
|
vertices[4]
|
||||||
);
|
);
|
||||||
@@ -484,7 +484,7 @@ macro_rules! graph_topology_tests {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
edges.iter().filter(|&&(f, _, _)| f == e).count(),
|
edges.iter().filter(|&&(f, _, _)| f == e).count(),
|
||||||
1,
|
1,
|
||||||
"unexpected edge {e:?} from the iterator"
|
"unexpected edge {e:?} from iterator"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -518,14 +518,14 @@ macro_rules! graph_topology_tests {
|
|||||||
.iter()
|
.iter()
|
||||||
.position(|f| *f == e)
|
.position(|f| *f == e)
|
||||||
.expect(&format!(
|
.expect(&format!(
|
||||||
"unexpected incident edge {e:?} of vertex {:?} from the iterator",
|
"unexpected incident edge {e:?} of vertex {:?} from iterator",
|
||||||
vertices[i]
|
vertices[i]
|
||||||
));
|
));
|
||||||
expected.swap_remove(pos);
|
expected.swap_remove(pos);
|
||||||
}
|
}
|
||||||
assert!(
|
assert!(
|
||||||
expected.is_empty(),
|
expected.is_empty(),
|
||||||
"expected incident edges {:?} of vertex {:?} were not matched by the iterator",
|
"expected incident edges {:?} of vertex {:?} were not matched by iterator",
|
||||||
expected,
|
expected,
|
||||||
vertices[i]
|
vertices[i]
|
||||||
);
|
);
|
||||||
@@ -572,7 +572,7 @@ macro_rules! graph_topology_tests {
|
|||||||
}
|
}
|
||||||
assert!(
|
assert!(
|
||||||
expected.is_empty(),
|
expected.is_empty(),
|
||||||
"expected incident edges {:?} of vertex {:?} were not matched by the iterator",
|
"expected incident edges {:?} of vertex {:?} were not matched by iterator",
|
||||||
expected,
|
expected,
|
||||||
vertices[i]
|
vertices[i]
|
||||||
);
|
);
|
||||||
@@ -608,14 +608,14 @@ macro_rules! graph_topology_tests {
|
|||||||
.iter()
|
.iter()
|
||||||
.position(|(v, e)| *v == incidence.0 && *e == incidence.1)
|
.position(|(v, e)| *v == incidence.0 && *e == incidence.1)
|
||||||
.expect(&format!(
|
.expect(&format!(
|
||||||
"unexpected incidence {incidence:?} of vertex {:?} from the iterator",
|
"unexpected incidence {incidence:?} of vertex {:?} from iterator",
|
||||||
vertices[i]
|
vertices[i]
|
||||||
));
|
));
|
||||||
expected.swap_remove(pos);
|
expected.swap_remove(pos);
|
||||||
}
|
}
|
||||||
assert!(
|
assert!(
|
||||||
expected.is_empty(),
|
expected.is_empty(),
|
||||||
"expected incidences {:?} of vertex {:?} were not matched by the iterator",
|
"expected incidences {:?} of vertex {:?} were not matched by iterator",
|
||||||
expected,
|
expected,
|
||||||
vertices[i]
|
vertices[i]
|
||||||
);
|
);
|
||||||
@@ -710,6 +710,131 @@ macro_rules! graph_topology_tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn incidence_cursor_empty() {
|
||||||
|
use $crate::traits::{GraphTopology, IncidenceCursor};
|
||||||
|
let mut graph = <$T>::new();
|
||||||
|
let v = graph.add_vertex();
|
||||||
|
let mut cursor = graph.incidence_cursor(v);
|
||||||
|
assert_eq!(
|
||||||
|
cursor.next(&graph),
|
||||||
|
None,
|
||||||
|
"incidence cursor of vertex with degree 0 should immediately be exhausted"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn incidence_cursor() {
|
||||||
|
use $crate::traits::{GraphTopology, IncidenceCursor};
|
||||||
|
let (graph, vertices, _, incidences) = make_test_graph();
|
||||||
|
for i in 0..10 {
|
||||||
|
let mut expected = incidences[i].clone();
|
||||||
|
let mut cursor = graph.incidence_cursor(vertices[i]);
|
||||||
|
while let Some(incidence) = cursor.next(&graph) {
|
||||||
|
let pos = expected
|
||||||
|
.iter()
|
||||||
|
.position(|(v, e)| *v == incidence.0 && *e == incidence.1)
|
||||||
|
.expect(&format!(
|
||||||
|
"unexpected incidence {incidence:?} of vertex {:?} from cursor",
|
||||||
|
vertices[i]
|
||||||
|
));
|
||||||
|
expected.swap_remove(pos);
|
||||||
|
}
|
||||||
|
assert!(
|
||||||
|
expected.is_empty(),
|
||||||
|
"expected incidences {:?} of vertex {:?} were not matched by cursor",
|
||||||
|
expected,
|
||||||
|
vertices[i]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn incidence_cursor_loop_edge() {
|
||||||
|
use $crate::traits::{GraphTopology, IncidenceCursor};
|
||||||
|
let mut graph = <$T>::new();
|
||||||
|
let v = graph.add_vertex();
|
||||||
|
let e = graph.add_edge(v, v);
|
||||||
|
let mut cursor = graph.incidence_cursor(v);
|
||||||
|
assert_eq!(
|
||||||
|
cursor.next(&graph),
|
||||||
|
Some((v, e)),
|
||||||
|
"vertex should be adjacent to itself"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
cursor.next(&graph),
|
||||||
|
Some((v, e)),
|
||||||
|
"vertex should be adjacent to itself twice"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
cursor.next(&graph),
|
||||||
|
None,
|
||||||
|
"too many incidences from cursor"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn incidence_cursor_multiple_edges() {
|
||||||
|
use $crate::traits::{GraphTopology, IncidenceCursor};
|
||||||
|
let k = 3;
|
||||||
|
let mut graph = <$T>::new();
|
||||||
|
let vertices = [graph.add_vertex(), graph.add_vertex()];
|
||||||
|
let mut edges = Vec::new();
|
||||||
|
for _ in 0..k {
|
||||||
|
edges.push(graph.add_edge(vertices[0], vertices[1]));
|
||||||
|
}
|
||||||
|
for i in 0..2 {
|
||||||
|
let mut cursor = graph.incidence_cursor(vertices[i]);
|
||||||
|
for j in 0..k {
|
||||||
|
let current = cursor.next(&graph).expect(&format!(
|
||||||
|
"incidence {j} missing, expected {k} incidences for vertex {:?}",
|
||||||
|
vertices[i]
|
||||||
|
));
|
||||||
|
assert_eq!(
|
||||||
|
current.0,
|
||||||
|
vertices[1 - i],
|
||||||
|
"unexpected adjacent vertex of vertex {:?} in incidence {j}",
|
||||||
|
vertices[i]
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
edges.iter().filter(|e| **e == current.1).count(),
|
||||||
|
1,
|
||||||
|
"unexpected incident edge {:?} of vertex {:?}",
|
||||||
|
current.1,
|
||||||
|
vertices[i],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
assert_eq!(
|
||||||
|
cursor.next(&graph),
|
||||||
|
None,
|
||||||
|
"too many incidences of {:?} from cursor",
|
||||||
|
vertices[i]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn incidence_cursor_copy() {
|
||||||
|
use $crate::traits::{GraphTopology, IncidenceCursor};
|
||||||
|
// Constructs a graph with two vertices connected to `v`.
|
||||||
|
let mut graph = <$T>::new();
|
||||||
|
let v = graph.add_vertex();
|
||||||
|
for _ in 0..2 {
|
||||||
|
let u = graph.add_vertex();
|
||||||
|
graph.add_edge(u, v);
|
||||||
|
}
|
||||||
|
let mut c1 = graph.incidence_cursor(v);
|
||||||
|
assert!(c1.next(&graph).is_some(), "expected first incidence");
|
||||||
|
// Copies cursor mid-traversal.
|
||||||
|
let mut c2 = c1;
|
||||||
|
// Continues iteration with original cursor.
|
||||||
|
assert!(c1.next(&graph).is_some(), "expected second incidence from original cursor");
|
||||||
|
assert!(c1.next(&graph).is_none(), "expected original cursor to be exhausted");
|
||||||
|
// Replays from the copy point with the copied cursor.
|
||||||
|
assert!(c2.next(&graph).is_some(), "expected second incidence from copied cursor");
|
||||||
|
assert!(c2.next(&graph).is_none(), "expected copied cursor to be exhausted");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn incident_vertices_incidences_consistency() {
|
fn incident_vertices_incidences_consistency() {
|
||||||
use $crate::traits::GraphTopology;
|
use $crate::traits::GraphTopology;
|
||||||
@@ -1103,8 +1228,7 @@ macro_rules! graph_topology_deletion_tests {
|
|||||||
for i in 0..10 {
|
for i in 0..10 {
|
||||||
let mut expected: Vec<_> = incidences[i]
|
let mut expected: Vec<_> = incidences[i]
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|(_, e)| *e != edges[2].0)
|
.filter_map(|(_, e)| (*e != edges[2].0).then_some(*e))
|
||||||
.map(|(_, e)| *e)
|
|
||||||
.collect();
|
.collect();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
graph.incident_edges(vertices[i]).count(),
|
graph.incident_edges(vertices[i]).count(),
|
||||||
@@ -1137,8 +1261,7 @@ macro_rules! graph_topology_deletion_tests {
|
|||||||
for i in [0, 1, 3, 4, 5, 6, 7, 8, 9] {
|
for i in [0, 1, 3, 4, 5, 6, 7, 8, 9] {
|
||||||
let mut expected: Vec<_> = incidences[i]
|
let mut expected: Vec<_> = incidences[i]
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|(v, _)| *v != vertices[2])
|
.filter_map(|(v, e)| (*v != vertices[2]).then_some(*e))
|
||||||
.map(|(_, e)| *e)
|
|
||||||
.collect();
|
.collect();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
graph.incident_edges(vertices[i]).count(),
|
graph.incident_edges(vertices[i]).count(),
|
||||||
@@ -1213,14 +1336,73 @@ macro_rules! graph_topology_deletion_tests {
|
|||||||
.iter()
|
.iter()
|
||||||
.position(|(u, e)| *u == incidence.0 && *e == incidence.1)
|
.position(|(u, e)| *u == incidence.0 && *e == incidence.1)
|
||||||
.expect(&format!(
|
.expect(&format!(
|
||||||
"unexpected incidence {incidence:?} of vertex {:?} after delete",
|
"unexpected incidence {incidence:?} of vertex {:?} from iterator after delete",
|
||||||
v
|
v
|
||||||
));
|
));
|
||||||
expected.swap_remove(pos);
|
expected.swap_remove(pos);
|
||||||
}
|
}
|
||||||
assert!(
|
assert!(
|
||||||
expected.is_empty(),
|
expected.is_empty(),
|
||||||
"expected incidences {:?} of vertex {:?} not matched after delete",
|
"expected incidences {:?} of vertex {:?} not matched by iterator after delete",
|
||||||
|
expected,
|
||||||
|
v
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn incidence_cursor_after_delete_vertex() {
|
||||||
|
use $crate::traits::GraphTopologyDeletion;
|
||||||
|
let (mut graph, vertices, _, incidences) = make_test_graph();
|
||||||
|
graph.delete_vertex(vertices[2]);
|
||||||
|
for i in [0, 1, 3, 4, 5, 6, 7, 8, 9] {
|
||||||
|
let remaining = incidences[i]
|
||||||
|
.iter()
|
||||||
|
.filter(|(v, _)| *v != vertices[2])
|
||||||
|
.cloned()
|
||||||
|
.collect();
|
||||||
|
assert_vertex_incidence_cursor(&graph, vertices[i], remaining);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn incidence_cursor_after_delete_edge() {
|
||||||
|
use $crate::traits::GraphTopologyDeletion;
|
||||||
|
let (mut graph, vertices, edges, incidences) = make_test_graph();
|
||||||
|
// Deletes the edge from vertices[1] to vertices[2].
|
||||||
|
graph.delete_edge(edges[2].0);
|
||||||
|
for i in 0..10 {
|
||||||
|
let remaining = incidences[i]
|
||||||
|
.iter()
|
||||||
|
.filter(|(_, e)| *e != edges[2].0)
|
||||||
|
.cloned()
|
||||||
|
.collect();
|
||||||
|
assert_vertex_incidence_cursor(&graph, vertices[i], remaining);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assert_vertex_incidence_cursor(
|
||||||
|
graph: &$T,
|
||||||
|
v: <$T as $crate::traits::GraphTopology>::Vertex,
|
||||||
|
mut expected: Vec<(
|
||||||
|
<$T as $crate::traits::GraphTopology>::Vertex,
|
||||||
|
<$T as $crate::traits::GraphTopology>::Edge,
|
||||||
|
)>,
|
||||||
|
) {
|
||||||
|
use $crate::traits::IncidenceCursor;
|
||||||
|
let mut cursor = graph.incidence_cursor(v);
|
||||||
|
while let Some(incidence) = cursor.next(graph) {
|
||||||
|
let pos = expected
|
||||||
|
.iter()
|
||||||
|
.position(|(u, e)| *u == incidence.0 && *e == incidence.1)
|
||||||
|
.expect(&format!(
|
||||||
|
"unexpected incidence {incidence:?} of vertex {:?} from cursor after delete",
|
||||||
|
v
|
||||||
|
));
|
||||||
|
expected.swap_remove(pos);
|
||||||
|
}
|
||||||
|
assert!(
|
||||||
|
expected.is_empty(),
|
||||||
|
"expected incidences {:?} of vertex {:?} not matched by cursor after delete",
|
||||||
expected,
|
expected,
|
||||||
v
|
v
|
||||||
);
|
);
|
||||||
|
|||||||
+10
-44
@@ -48,7 +48,7 @@ macro_rules! vertex_map_tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn sync_expands_to_new_vertices() {
|
fn expand_to_new_vertices() {
|
||||||
use $crate::traits::GraphTopology;
|
use $crate::traits::GraphTopology;
|
||||||
let mut graph = <$T>::new();
|
let mut graph = <$T>::new();
|
||||||
graph.add_vertex();
|
graph.add_vertex();
|
||||||
@@ -59,21 +59,21 @@ macro_rules! vertex_map_tests {
|
|||||||
}
|
}
|
||||||
assert!(
|
assert!(
|
||||||
map.capacity() < graph.vertex_capacity(),
|
map.capacity() < graph.vertex_capacity(),
|
||||||
"precondition: map is stale before sync"
|
"precondition: map is stale before expand"
|
||||||
);
|
);
|
||||||
map.sync(&graph);
|
map.expand(graph.vertex_capacity());
|
||||||
assert_eq!(map.capacity(), graph.vertex_capacity());
|
assert_eq!(map.capacity(), graph.vertex_capacity());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn sync_does_not_overwrite_existing_values() {
|
fn expand_does_not_overwrite_existing_values() {
|
||||||
use $crate::traits::GraphTopology;
|
use $crate::traits::GraphTopology;
|
||||||
let mut graph = <$T>::new();
|
let mut graph = <$T>::new();
|
||||||
let v = graph.add_vertex();
|
let v = graph.add_vertex();
|
||||||
let mut map = graph.vertex_map(0);
|
let mut map = graph.vertex_map(0);
|
||||||
map[v] = 5;
|
map[v] = 5;
|
||||||
graph.add_vertex();
|
graph.add_vertex();
|
||||||
map.sync(&graph);
|
map.expand(graph.vertex_capacity());
|
||||||
assert_eq!(map[v], 5);
|
assert_eq!(map[v], 5);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -97,22 +97,6 @@ macro_rules! vertex_map_deletion_tests {
|
|||||||
assert_eq!(map[v1], 1);
|
assert_eq!(map[v1], 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn capacity_does_not_shrink_after_delete() {
|
|
||||||
use $crate::traits::GraphTopology;
|
|
||||||
use $crate::traits::GraphTopologyDeletion;
|
|
||||||
let mut graph = <$T>::new();
|
|
||||||
let v1 = graph.add_vertex();
|
|
||||||
let v2 = graph.add_vertex();
|
|
||||||
let mut map = graph.vertex_map(0);
|
|
||||||
map[v1] = 5;
|
|
||||||
let capacity_before = graph.vertex_capacity();
|
|
||||||
graph.delete_vertex(v2);
|
|
||||||
map.sync(&graph);
|
|
||||||
assert_eq!(map.capacity(), capacity_before);
|
|
||||||
assert_eq!(map[v1], 5);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn reused_slot_returns_old_value() {
|
fn reused_slot_returns_old_value() {
|
||||||
use $crate::traits::GraphTopology;
|
use $crate::traits::GraphTopology;
|
||||||
@@ -190,7 +174,7 @@ macro_rules! edge_map_tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn sync_expands_to_new_edges() {
|
fn expand_to_new_edges() {
|
||||||
use $crate::traits::GraphTopology;
|
use $crate::traits::GraphTopology;
|
||||||
let mut graph = <$T>::new();
|
let mut graph = <$T>::new();
|
||||||
let v1 = graph.add_vertex();
|
let v1 = graph.add_vertex();
|
||||||
@@ -203,14 +187,14 @@ macro_rules! edge_map_tests {
|
|||||||
}
|
}
|
||||||
assert!(
|
assert!(
|
||||||
map.capacity() < graph.edge_capacity(),
|
map.capacity() < graph.edge_capacity(),
|
||||||
"precondition: map is stale before sync"
|
"precondition: map is stale before expand"
|
||||||
);
|
);
|
||||||
map.sync(&graph);
|
map.expand(graph.edge_capacity());
|
||||||
assert_eq!(map.capacity(), graph.edge_capacity());
|
assert_eq!(map.capacity(), graph.edge_capacity());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn sync_does_not_overwrite_existing_values() {
|
fn expand_does_not_overwrite_existing_values() {
|
||||||
use $crate::traits::GraphTopology;
|
use $crate::traits::GraphTopology;
|
||||||
let mut graph = <$T>::new();
|
let mut graph = <$T>::new();
|
||||||
let v1 = graph.add_vertex();
|
let v1 = graph.add_vertex();
|
||||||
@@ -219,7 +203,7 @@ macro_rules! edge_map_tests {
|
|||||||
let mut map = graph.edge_map(0);
|
let mut map = graph.edge_map(0);
|
||||||
map[e] = 5;
|
map[e] = 5;
|
||||||
graph.add_edge(v1, v2);
|
graph.add_edge(v1, v2);
|
||||||
map.sync(&graph);
|
map.expand(graph.edge_capacity());
|
||||||
assert_eq!(map[e], 5);
|
assert_eq!(map[e], 5);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -245,24 +229,6 @@ macro_rules! edge_map_deletion_tests {
|
|||||||
assert_eq!(map[e1], 1);
|
assert_eq!(map[e1], 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn capacity_does_not_shrink_after_delete() {
|
|
||||||
use $crate::traits::GraphTopology;
|
|
||||||
use $crate::traits::GraphTopologyDeletion;
|
|
||||||
let mut graph = <$T>::new();
|
|
||||||
let v1 = graph.add_vertex();
|
|
||||||
let v2 = graph.add_vertex();
|
|
||||||
let e1 = graph.add_edge(v1, v2);
|
|
||||||
let e2 = graph.add_edge(v1, v2);
|
|
||||||
let mut map = graph.edge_map(0);
|
|
||||||
map[e1] = 5;
|
|
||||||
let capacity_before = graph.edge_capacity();
|
|
||||||
graph.delete_edge(e2);
|
|
||||||
map.sync(&graph);
|
|
||||||
assert_eq!(map.capacity(), capacity_before);
|
|
||||||
assert_eq!(map[e1], 5);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn reused_slot_returns_old_value() {
|
fn reused_slot_returns_old_value() {
|
||||||
use $crate::traits::GraphTopology;
|
use $crate::traits::GraphTopology;
|
||||||
|
|||||||
+213
@@ -1,36 +1,249 @@
|
|||||||
|
//! Core traits for undirected graph topologies.
|
||||||
|
|
||||||
use crate::maps::{EdgeMap, VertexMap};
|
use crate::maps::{EdgeMap, VertexMap};
|
||||||
|
|
||||||
// TODO: Add functions to reserve memory for vertices and edges.
|
// TODO: Add functions to reserve memory for vertices and edges.
|
||||||
// TODO: Split out GraphTopologyAddition trait.
|
// TODO: Split out GraphTopologyAddition trait.
|
||||||
|
// TODO: Introduce an Incidence struct.
|
||||||
|
/// 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 {
|
pub trait GraphTopology {
|
||||||
|
/// An opaque, stable handle identifying a vertex.
|
||||||
type Vertex: Copy + Eq;
|
type Vertex: Copy + Eq;
|
||||||
|
|
||||||
|
/// An opaque, stable handle identifying an edge.
|
||||||
type Edge: Copy + Eq;
|
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> + Copy;
|
type IncidenceCursor: IncidenceCursor<Self> + Copy;
|
||||||
|
|
||||||
|
/// Returns the number of vertices in the graph.
|
||||||
fn vertex_count(&self) -> usize;
|
fn vertex_count(&self) -> usize;
|
||||||
|
|
||||||
|
/// Returns the total number of vertices the graph can hold without reallocating.
|
||||||
fn vertex_capacity(&self) -> usize;
|
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>;
|
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;
|
fn edge_count(&self) -> usize;
|
||||||
|
|
||||||
|
/// Returns the total number of edges the graph can hold without reallocating.
|
||||||
fn edge_capacity(&self) -> usize;
|
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>;
|
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;
|
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;
|
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>;
|
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>;
|
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);
|
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>;
|
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>;
|
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)>;
|
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;
|
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;
|
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;
|
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 {
|
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);
|
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);
|
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> {
|
pub trait IncidenceCursor<G: GraphTopology + ?Sized> {
|
||||||
|
/// 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;
|
||||||
|
/// # use grapherity::traits::IncidenceCursor;
|
||||||
|
/// // 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)>;
|
fn next(&mut self, graph: &G) -> Option<(G::Vertex, G::Edge)>;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user