From 038e984c97a5467f4a24015e62a862fd70fadfa1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Sat, 19 Sep 2026 17:53:44 +0200 Subject: [PATCH] Add FromGraph trait, and add implementation for FrozenGraph --- src/lib.rs | 3 +- src/models/frozen_graph.rs | 131 ++++++++++++++++++------------------- src/testing/fixtures.rs | 2 +- src/traits.rs | 33 ++++++++++ 4 files changed, 99 insertions(+), 70 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d2ea1fb..1fadff8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -145,7 +145,8 @@ pub mod traits; /// Convenience re-exports of graph topology traits for common use. pub mod prelude { pub use crate::traits::{ - GraphTopology, GraphTopologyAddition, GraphTopologyDeletion, Incidence, IncidenceCursor, + FromGraph, GraphTopology, GraphTopologyAddition, GraphTopologyDeletion, Incidence, + IncidenceCursor, }; } diff --git a/src/models/frozen_graph.rs b/src/models/frozen_graph.rs index d090a39..f87d737 100644 --- a/src/models/frozen_graph.rs +++ b/src/models/frozen_graph.rs @@ -3,7 +3,7 @@ use std::ops::IndexMut; use crate::maps::ElementMap; -use crate::traits::{GraphTopology, Incidence, IncidenceCursor}; +use crate::traits::{FromGraph, GraphTopology, Incidence, IncidenceCursor}; /// An opaque handle identifying a vertex in a [`FrozenGraph`]. /// @@ -103,67 +103,6 @@ pub struct FrozenGraph { } impl FrozenGraph { - // TODO: Move this to a new FromGraph trait and adapt the MakeTestGraph impl to use helper functions like "fn fixture_empty() -> G { G::from_graph(&AppendGraph::empty()).0 }". - /// Creates a graph instance as a copy of another graph. - /// - /// Returns the copied graph instance and two mappings of the `source` graph vertices and edges - /// to the new ones. Note that the returned mappings cannot support additional growth, i.e. any - /// vertex or edge added to the `source` graph after calling `from_graph` will map to an invalid - /// vertex or edge in the new `FrozenGraph`. - pub fn from_graph( - source: &G, - ) -> ( - Self, - ElementMap, - ElementMap, - ) { - // Builds vertex list with sentinel and vertex map. - let mut vertex_map = source.vertex_map(Vertex(usize::MAX)); - let mut vertices = Vec::with_capacity(source.vertex_count() + 1); - let mut offset = 0; - for (i, v) in source.vertices().enumerate() { - vertex_map[v] = Vertex(i); - vertices.push(offset); - offset += source.degree(v); - } - vertices.push(offset); - - // Builds incidence list and edge map. Uses "usize::MAX" as "unset" here: an edge can never - // be normalized to "usize::MAX", since it must be the smaller index of two incidences. - const UNSET: usize = usize::MAX; - let mut edge_map = source.edge_map(Edge(UNSET)); - let mut incidences = Vec::with_capacity(source.edge_count() * 2); - for v in source.vertices() { - for Incidence { vertex: u, edge: e } in source.incidences(v) { - let em = edge_map.index_mut(e); - if em.0 == UNSET { - // Adds the first incidence for the edge, but we don't know the opposite, yet. - *em = Edge(incidences.len()); - incidences.push(IncidenceEntry { - adjacent: vertex_map[u], - opposite: 0, - }); - } else { - // Adds the second incidence for the edge and fills the opposite for the first. - incidences[em.0].opposite = incidences.len(); - incidences.push(IncidenceEntry { - adjacent: vertex_map[u], - opposite: em.0, - }); - } - } - } - - ( - Self { - vertices, - incidences, - }, - vertex_map, - edge_map, - ) - } - fn raw_incidences(&self, v: Vertex) -> impl Iterator { self.vertices[v.0]..self.vertices[v.0 + 1] } @@ -186,12 +125,6 @@ impl FrozenGraph { } } -impl From<&G> for FrozenGraph { - fn from(graph: &G) -> Self { - Self::from_graph(graph).0 - } -} - impl GraphTopology for FrozenGraph { type Vertex = Vertex; type Edge = Edge; @@ -257,6 +190,68 @@ impl GraphTopology for FrozenGraph { } } +impl From<&G> for FrozenGraph { + fn from(graph: &G) -> Self { + Self::from_graph(graph).0 + } +} + +impl FromGraph for FrozenGraph { + fn from_graph( + source: &G, + ) -> ( + Self, + ElementMap, + ElementMap, + ) { + // Builds vertex list with sentinel and vertex map. + let mut vertex_map = source.vertex_map(Vertex(usize::MAX)); + let mut vertices = Vec::with_capacity(source.vertex_count() + 1); + let mut offset = 0; + for (i, v) in source.vertices().enumerate() { + vertex_map[v] = Vertex(i); + vertices.push(offset); + offset += source.degree(v); + } + vertices.push(offset); + + // Builds incidence list and edge map. Uses "usize::MAX" as "unset" here: an edge can never + // be normalized to "usize::MAX", since it must be the smaller index of two incidences. + const UNSET: usize = usize::MAX; + let mut edge_map = source.edge_map(Edge(UNSET)); + let mut incidences = Vec::with_capacity(source.edge_count() * 2); + for v in source.vertices() { + for Incidence { vertex: u, edge: e } in source.incidences(v) { + let em = edge_map.index_mut(e); + if em.0 == UNSET { + // Adds the first incidence for the edge, but we don't know the opposite, yet. + *em = Edge(incidences.len()); + incidences.push(IncidenceEntry { + adjacent: vertex_map[u], + opposite: 0, + }); + } else { + // Adds the second incidence for the edge and fills the opposite for the first. + incidences[em.0].opposite = incidences.len(); + incidences.push(IncidenceEntry { + adjacent: vertex_map[u], + opposite: em.0, + }); + } + } + } + + ( + Self { + vertices, + incidences, + }, + vertex_map, + edge_map, + ) + } +} + #[cfg(test)] mod trait_tests { use super::FrozenGraph; diff --git a/src/testing/fixtures.rs b/src/testing/fixtures.rs index 0701f91..f60dcf3 100644 --- a/src/testing/fixtures.rs +++ b/src/testing/fixtures.rs @@ -2,7 +2,7 @@ use std::fmt::Debug; use crate::maps::ElementMap; use crate::models::{AppendGraph, FrozenGraph}; -use crate::traits::{GraphTopology, GraphTopologyAddition, Incidence}; +use crate::traits::{FromGraph, GraphTopology, GraphTopologyAddition, Incidence}; pub trait MakeTestGraph: GraphTopology + Sized { fn standard() -> ( diff --git a/src/traits.rs b/src/traits.rs index 9fdfccd..961f40a 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -237,6 +237,39 @@ pub trait GraphTopologyDeletion: GraphTopology { fn delete_edge(&mut self, e: Self::Edge); } +/// A trait for constructing an undirected graph topology by converting another graph +/// implementation to this one. +pub trait FromGraph: GraphTopology { + /// Creates a new graph instance that is isomorphic to `source`. + /// + /// Returns the copied graph instance and two mappings from the vertices and edges of `source` + /// to the corresponding vertices and edges of the returned graph. + /// + /// # Examples + /// + /// ``` + /// use grapherity::prelude::*; + /// use grapherity::models::{AppendGraph, FrozenGraph}; + /// + /// let mut source = AppendGraph::new(); + /// let u = source.add_vertex(); + /// let v = source.add_vertex(); + /// let e = source.add_edge(u, v); + /// let (graph, vertex_map, edge_map) = FrozenGraph::from_graph(&source); + /// + /// assert!(graph.are_adjacent(vertex_map[u], vertex_map[v])); + /// ``` + fn from_graph( + source: &G, + ) -> ( + Self, + ElementMap, + ElementMap, + ) + where + Self: Sized; +} + /// A cursor for traversing the incidences of a vertex one step at a time. /// /// Because the cursor is [`Copy`], its state can be saved and restored to replay or branch a