240 lines
7.3 KiB
Rust
240 lines
7.3 KiB
Rust
//! [`AppendGraph`], an undirected graph topology supporting addition only.
|
|
|
|
use crate::maps::{EdgeMap, VertexMap};
|
|
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)]
|
|
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)]
|
|
pub struct Edge(usize);
|
|
|
|
/// A [`VertexMap`] for [`AppendGraph`] vertices.
|
|
pub type AppendGraphVertexMap<T> = VertexMap<Vertex, T>;
|
|
|
|
/// An [`EdgeMap`] for [`AppendGraph`] edges.
|
|
pub type AppendGraphEdgeMap<T> = EdgeMap<Edge, T>;
|
|
|
|
impl Edge {
|
|
fn normalize(&self) -> Self {
|
|
Self(self.0 & !1)
|
|
}
|
|
}
|
|
|
|
// 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 {
|
|
incidence_count: usize,
|
|
first_incidence: Option<Edge>,
|
|
}
|
|
|
|
#[derive(Copy, Clone)]
|
|
struct IncidenceEntry {
|
|
next: Option<Edge>,
|
|
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)]
|
|
pub struct AppendGraphIncidenceCursor {
|
|
incidence: Option<Edge>,
|
|
}
|
|
|
|
impl IncidenceCursor<AppendGraph> for AppendGraphIncidenceCursor {
|
|
fn next(&mut self, graph: &AppendGraph) -> Option<(Vertex, Edge)> {
|
|
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 {
|
|
vertices: Vec<VertexIncidenceHeader>,
|
|
incidences: Vec<IncidenceEntry>,
|
|
}
|
|
|
|
impl AppendGraph {
|
|
/// Creates an empty graph instance with no vertices or edges.
|
|
pub fn new() -> Self {
|
|
Self {
|
|
vertices: vec![],
|
|
incidences: vec![],
|
|
}
|
|
}
|
|
|
|
/// Adds a single incidence of an edge, which is composed by two such incidences, to the
|
|
/// incidences vector.
|
|
fn add_incidence(&mut self, v1: Vertex, v2: Vertex) {
|
|
self.incidences.push(IncidenceEntry {
|
|
next: self.vertices[v1.0].first_incidence.take(),
|
|
adjacent: v2,
|
|
});
|
|
self.vertices[v1.0].incidence_count += 1;
|
|
self.vertices[v1.0].first_incidence = Some(Edge(self.incidences.len() - 1));
|
|
}
|
|
|
|
fn raw_incidences(&self, v: Vertex) -> impl Iterator<Item = (Vertex, Edge)> {
|
|
let mut incidence = self.vertices[v.0].first_incidence;
|
|
std::iter::from_fn(move || self.step_incidence(&mut incidence))
|
|
}
|
|
|
|
fn step_incidence(&self, incidence: &mut Option<Edge>) -> Option<(Vertex, Edge)> {
|
|
let current = (*incidence)?;
|
|
let entry = self.incidences[current.0];
|
|
*incidence = entry.next;
|
|
Some((entry.adjacent, current))
|
|
}
|
|
}
|
|
|
|
impl Default for AppendGraph {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
impl GraphTopology for AppendGraph {
|
|
type Vertex = Vertex;
|
|
type Edge = Edge;
|
|
type IncidenceCursor = AppendGraphIncidenceCursor;
|
|
|
|
fn vertex_count(&self) -> usize {
|
|
self.vertices.len()
|
|
}
|
|
|
|
fn vertex_capacity(&self) -> usize {
|
|
self.vertices.capacity()
|
|
}
|
|
|
|
fn vertex_map<T: Clone>(&self, default: T) -> VertexMap<Self::Vertex, T> {
|
|
VertexMap::new(default, |v| v.0, self.vertex_capacity())
|
|
}
|
|
|
|
fn edge_count(&self) -> usize {
|
|
self.incidences.len() / 2
|
|
}
|
|
|
|
fn edge_capacity(&self) -> usize {
|
|
self.incidences.capacity() / 2
|
|
}
|
|
|
|
fn edge_map<T: Clone>(&self, default: T) -> EdgeMap<Self::Edge, T> {
|
|
EdgeMap::new(default, |e| e.0 / 2, self.edge_capacity())
|
|
}
|
|
|
|
fn degree(&self, v: Self::Vertex) -> usize {
|
|
self.vertices[v.0].incidence_count
|
|
}
|
|
|
|
fn are_adjacent(&self, v1: Self::Vertex, v2: Self::Vertex) -> bool {
|
|
self.adjacent_vertices(v1).any(|x| x == v2)
|
|
}
|
|
|
|
fn vertices(&self) -> impl Iterator<Item = Self::Vertex> {
|
|
(0..self.vertices.len()).map(Vertex)
|
|
}
|
|
|
|
fn adjacent_vertices(&self, v: Self::Vertex) -> impl Iterator<Item = Self::Vertex> {
|
|
self.raw_incidences(v).map(|(v, _)| v)
|
|
}
|
|
|
|
fn incident_vertices(&self, e: Self::Edge) -> (Self::Vertex, Self::Vertex) {
|
|
let v2 = self.incidences[e.0].adjacent;
|
|
let v1 = self.incidences[e.0 ^ 1].adjacent;
|
|
(v1, v2)
|
|
}
|
|
|
|
fn edges(&self) -> impl Iterator<Item = Self::Edge> {
|
|
(0..self.incidences.len()).step_by(2).map(Edge)
|
|
}
|
|
|
|
fn incident_edges(&self, v: Self::Vertex) -> impl Iterator<Item = Self::Edge> {
|
|
self.raw_incidences(v).map(|(_, e)| e.normalize())
|
|
}
|
|
|
|
fn incidences(&self, v: Self::Vertex) -> impl Iterator<Item = (Self::Vertex, Self::Edge)> {
|
|
self.raw_incidences(v).map(|(v, e)| (v, e.normalize()))
|
|
}
|
|
|
|
fn incidence_cursor(&self, v: Self::Vertex) -> Self::IncidenceCursor {
|
|
AppendGraphIncidenceCursor {
|
|
incidence: self.vertices[v.0].first_incidence,
|
|
}
|
|
}
|
|
|
|
fn add_vertex(&mut self) -> Self::Vertex {
|
|
self.vertices.push(VertexIncidenceHeader {
|
|
incidence_count: 0,
|
|
first_incidence: None,
|
|
});
|
|
Vertex(self.vertices.len() - 1)
|
|
}
|
|
|
|
fn add_edge(&mut self, v1: Self::Vertex, v2: Self::Vertex) -> Self::Edge {
|
|
self.add_incidence(v1, v2);
|
|
self.add_incidence(v2, v1);
|
|
Edge(self.incidences.len() - 2)
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
crate::graph_topology_test_fixtures!(AppendGraph);
|
|
crate::graph_topology_tests!(AppendGraph);
|
|
|
|
#[test]
|
|
fn incident_vertices_paired_index() {
|
|
let mut graph = AppendGraph::new();
|
|
let v1 = graph.add_vertex();
|
|
let v2 = graph.add_vertex();
|
|
let e = graph.add_edge(v1, v2);
|
|
let f = Edge(e.0 + 1);
|
|
let (u1, u2) = graph.incident_vertices(f);
|
|
assert!(
|
|
(u1 == v1 && u2 == v2) || (u1 == v2 && u2 == v1),
|
|
"unexpected incident vertices {u1:?} and {u2:?} for edge {f:?}"
|
|
);
|
|
}
|
|
}
|