use typed_generational_arena::{Arena, Index}; use crate::maps::{EdgeMap, VertexMap}; use crate::traits::{GraphTopology, GraphTopologyDeletion}; type Vertex = Index; type Edge = Index; #[derive(Copy, Clone, PartialEq, Eq, Debug)] struct VertexSlot(usize); #[derive(Copy, Clone, PartialEq, Eq, Debug)] struct IncidenceSlot(usize); pub struct VertexIncidenceHeader { incidence_count: usize, first_incidence: Option, } #[derive(Copy, Clone)] pub struct IncidenceEntry { next: Option, adjacent: VertexSlot, } struct IncidentEdgeCursor { incidence: Option, } impl IncidentEdgeCursor { fn next(&mut self, graph: &Graph) -> Option { graph.step_incidence(&mut self.incidence).map(|(_, e)| e) } } pub struct Graph { // TODO: Arena index and generation types could be externalized to Graph. vertices: Arena, incidences: Arena, } impl Graph { pub fn new() -> Self { Self { vertices: Arena::new(), incidences: Arena::new(), } } // Adds a single incidence of an edge, which is composed by two such incidences, to the // incidences arena, and returns its index. fn add_incidence(&mut self, v1: Vertex, v2: Vertex) -> Edge { let edge = self.incidences.insert(IncidenceEntry { next: self.vertices[v1].first_incidence.take(), adjacent: VertexSlot(v2.arr_idx()), }); self.vertices[v1].incidence_count += 1; self.vertices[v1].first_incidence = Some(IncidenceSlot(edge.arr_idx())); edge } fn remove_incidence_pair(&mut self, e: Edge) -> (Edge, IncidenceEntry, IncidenceEntry) { let f = self .incidences .get_idx(e.arr_idx() ^ 1) .expect("invalid paired incidence index, corrupt internal data state"); let e_entry = self .incidences .remove(e) .expect("attempt to delete an invalid edge"); let f_entry = self .incidences .remove(f) .expect("cannot read paired incidence, corrupt internal data state"); (f, e_entry, f_entry) } // 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. fn update_incidence_list( &mut self, e: Edge, source: VertexSlot, next: Option, is_loop: bool, ) { let source_vertex = self .vertices .get_idx(source.0) .expect("missing incident vertex, corrupt internal data state"); let vertex_header = &mut self.vertices[source_vertex]; vertex_header.incidence_count -= if is_loop { 2 } else { 1 }; let first = vertex_header .first_incidence .expect("incident vertex without incidences, corrupt internal data state"); if first.0 == e.arr_idx() { vertex_header.first_incidence = next; } else { let graph: &Graph = self; let mut incidence = self.vertices[source_vertex].first_incidence; let (_, previous) = std::iter::from_fn(move || graph.step_incidence(&mut incidence)) .find(|(_, f)| { graph.incidences[*f] .next .is_some_and(|i| i.0 == e.arr_idx()) }) .expect("cannot find previous incidence, corrupt internal data state"); self.incidences[previous].next = next; } } fn step_incidence(&self, incidence: &mut Option) -> Option<(VertexSlot, Edge)> { let current = (*incidence)?; let e = self.incidences.get_idx(current.0).unwrap(); let entry = self.incidences[e]; *incidence = entry.next; Some((entry.adjacent, e)) } fn normalize_edge(&self, e: Edge) -> Edge { self.incidences.get_idx(e.arr_idx() & !1).unwrap() } } impl Default for Graph { fn default() -> Self { Self::new() } } impl GraphTopology for Graph { type Vertex = Vertex; type Edge = Edge; fn vertex_count(&self) -> usize { self.vertices.len() } fn vertex_capacity(&self) -> usize { self.vertices.capacity() } fn vertex_map(&self, default: T) -> VertexMap { VertexMap::new(default, |v| v.arr_idx(), self.vertex_capacity()) } fn edge_count(&self) -> usize { self.incidences.len() / 2 } fn edge_capacity(&self) -> usize { self.incidences.capacity() / 2 } fn edge_map(&self, default: T) -> EdgeMap { EdgeMap::new(default, |e| e.arr_idx() / 2, self.edge_capacity()) } fn degree(&self, v: Self::Vertex) -> usize { self.vertices[v].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 { self.vertices.iter().map(|(i, _)| i) } fn adjacent_vertices(&self, v: Self::Vertex) -> impl Iterator { let mut incidence = self.vertices[v].first_incidence; std::iter::from_fn(move || self.step_incidence(&mut incidence)) .map(|(vs, _)| self.vertices.get_idx(vs.0).unwrap()) } fn incident_vertices(&self, e: Self::Edge) -> (Self::Vertex, Self::Vertex) { let v2 = self .vertices .get_idx(self.incidences[e].adjacent.0) .unwrap(); let f = self.incidences.get_idx(e.arr_idx() ^ 1).unwrap(); let v1 = self .vertices .get_idx(self.incidences[f].adjacent.0) .unwrap(); (v1, v2) } fn edges(&self) -> impl Iterator { self.incidences .iter() .filter(|(i, _)| i.arr_idx() % 2 == 0) .map(|(i, _)| i) } fn incidences(&self, v: Self::Vertex) -> impl Iterator { let mut incidence = self.vertices[v].first_incidence; std::iter::from_fn(move || self.step_incidence(&mut incidence)) .map(|(vs, e)| (self.vertices.get_idx(vs.0).unwrap(), self.normalize_edge(e))) } fn add_vertex(&mut self) -> Self::Vertex { self.vertices.insert(VertexIncidenceHeader { incidence_count: 0, first_incidence: None, }) } fn add_edge(&mut self, v1: Self::Vertex, v2: Self::Vertex) -> Self::Edge { let first = self.add_incidence(v1, v2); self.add_incidence(v2, v1); first } } // TODO: Benchmark delete with storing "previous" in O(1) vs. linear lookup in O(degree). impl GraphTopologyDeletion for Graph { fn delete_vertex(&mut self, v: Self::Vertex) { let v_header = self .vertices .remove(v) .expect("attempt to delete an invalid vertex"); let mut cursor = IncidentEdgeCursor { incidence: v_header.first_incidence, }; while let Some(e) = cursor.next(self) { // Since v is being deleted, there are no update_incidence_list() calls for e, no need // to fix v's incidence list. let (f, e_entry, f_entry) = self.remove_incidence_pair(e); if e_entry.adjacent != f_entry.adjacent { self.update_incidence_list(f, e_entry.adjacent, f_entry.next, false); } else { cursor.incidence = f_entry.next; } } } // 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) { let (f, e_entry, f_entry) = self.remove_incidence_pair(e); if e_entry.adjacent != f_entry.adjacent { self.update_incidence_list(e, f_entry.adjacent, e_entry.next, false); self.update_incidence_list(f, e_entry.adjacent, f_entry.next, false); } else if f_entry.next.is_some_and(|i| e.arr_idx() == i.0) { self.update_incidence_list(f, e_entry.adjacent, e_entry.next, true); } else { self.update_incidence_list(e, e_entry.adjacent, f_entry.next, true); } } } #[cfg(test)] mod tests { use super::*; crate::graph_topology_test_fixtures!(Graph); crate::graph_topology_tests!(Graph); crate::graph_topology_deletion_tests!(Graph); #[test] fn incident_vertices_paired_index() { let mut graph = Graph::new(); let v1 = graph.add_vertex(); let v2 = graph.add_vertex(); let e = graph.add_edge(v1, v2); let f = graph .incidences .get_idx(e.arr_idx() + 1) .expect("paired index should be valid"); 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:?}" ); } #[test] fn delete_edge_paired_index() { let mut graph = Graph::new(); let v1 = graph.add_vertex(); let v2 = graph.add_vertex(); let e = graph.add_edge(v1, v2); let f = graph .incidences .get_idx(e.arr_idx() + 1) .expect("paired index should be valid"); graph.delete_edge(f); assert_eq!(graph.edge_count(), 0, "unexpected edge count after delete"); } #[test] fn delete_edge_loop_paired_index() { let mut graph = Graph::new(); let v = graph.add_vertex(); let e = graph.add_edge(v, v); let f = graph .incidences .get_idx(e.arr_idx() + 1) .expect("paired index should be valid"); graph.delete_edge(f); assert_eq!(graph.edge_count(), 0, "unexpected edge count after delete"); } }