From 3d7dda6f7b194cf621f95efce35612f20aa14c81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Sun, 11 Oct 2026 22:24:00 +0200 Subject: [PATCH] Refactor ElementMap, removing its copy-on-write feature --- README.md | 6 +- src/lib.rs | 7 +- src/maps.rs | 189 +++++++++++------- src/models/append_graph.rs | 2 - src/models/frozen_graph.rs | 3 +- src/models/graph.rs | 2 - .../graph_topology_addition_testing.rs | 55 ++++- src/testing/graph_topology_testing.rs | 44 ++-- src/traits.rs | 86 +++++++- 9 files changed, 281 insertions(+), 113 deletions(-) diff --git a/README.md b/README.md index b471e97..a47f8b8 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ This crate provides data structures to model graphs and to perform algorithms on Currently supported are: * Undirected graph types `Graph`, `AppendGraph`, and `FrozenGraph`, built on a flat, index-based adjacency list, -* Maps to freely associate custom data with vertices and edges, +* `ElementMap` 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. @@ -22,14 +22,14 @@ 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); +// Creates a map for edge weights with a default weight, after creating the edge. +let mut weights = graph.edge_map(1); // Sets a non-default weight for the second edge. weights[e] = 5; diff --git a/src/lib.rs b/src/lib.rs index 2a0a884..8a0b27d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,14 +23,14 @@ //! 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); +//! // Creates a map for edge weights with a default weight, after creating the edges. +//! let mut weights = graph.edge_map(1); //! // Sets a non-default weight for the second edge. //! weights[e] = 5; //! @@ -97,7 +97,8 @@ //! graph.degree(v); //! ``` //! -//! [`ElementMap`] is not required to panic if provided with an invalid vertex or edge. +//! [`ElementMap`] does not check validity of vertices or edges and might or might not panic in case +//! an invalid handle is provided. //! //! Vertices and edges are provided by the graph and not intended to be generated by users. //! diff --git a/src/maps.rs b/src/maps.rs index debddc8..82a693c 100644 --- a/src/maps.rs +++ b/src/maps.rs @@ -1,7 +1,7 @@ //! Provides maps to associate custom data to graph vertices and edges. //! -//! [`ElementMap`] provides a copy-on-write, [`Vec`]-backed map to associate data to either all -//! vertices or all edges in a graph. +//! [`ElementMap`] is a dense, [`Vec`]-backed map to associate data to either all vertices or all +//! edges in a graph. use std::ops::{Index, IndexMut}; @@ -12,15 +12,19 @@ use std::ops::{Index, IndexMut}; /// which means that the provided index conversion function should map elements 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 is -/// written beyond current capacity, but the `default` value can transparently be read. No bound or -/// validity checks are performed by the map on the provided element handles. -/// /// Use [`GraphTopology::vertex_map`] or [`GraphTopology::edge_map`] to obtain an `ElementMap` for /// vertices or edges, respectively. /// +/// The map does not grow on its own when the associated graph grows. Use the graph's +/// [`GraphTopologyAddition::sync_vertex_map`] or [`GraphTopologyAddition::sync_edge_map`] to extend +/// the map explicitly after adding vertices or edges to it. +/// +/// No bound or validity checks are performed by the map on the provided element handles. +/// /// [`GraphTopology::edge_map`]: crate::traits::GraphTopology::edge_map /// [`GraphTopology::vertex_map`]: crate::traits::GraphTopology::vertex_map +/// [`GraphTopologyAddition::sync_vertex_map`]: crate::traits::GraphTopologyAddition::sync_vertex_map +/// [`GraphTopologyAddition::sync_edge_map`]: crate::traits::GraphTopologyAddition::sync_edge_map pub struct ElementMap { data: Vec, default: T, @@ -29,47 +33,59 @@ pub struct ElementMap { impl ElementMap { /// 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 { + /// and an initial `len`, i.e. the number of slots that can be read from and written to. + /// + /// Since `to_index` is usually an implementation detail of the graph model, this is not meant + /// to be called directly. Use [`GraphTopology::vertex_map`] or [`GraphTopology::edge_map`] + /// instead. + /// + /// [`GraphTopology::edge_map`]: crate::traits::GraphTopology::edge_map + /// [`GraphTopology::vertex_map`]: crate::traits::GraphTopology::vertex_map + pub fn new(default: T, to_index: fn(E) -> usize, len: usize) -> Self { Self { - data: vec![default.clone(); capacity], + data: vec![default.clone(); len], default, to_index, } } - /// 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 { - self.data.capacity() - } - - /// Reserves capacity for at least `additional` more data entries. Does nothing if capacity is - /// already sufficient. + /// Grows the map so that it contains at least `new_len` slots for elements, initialising any + /// newly added slots to the `default` value provided to [`new`], without modifying existing + /// data. Does nothing if the map already has at least `new_len` slots. /// - /// Use this before writing data for new graph elements to avoid incremental growth on the first - /// write to each new element. - pub fn reserve(&mut self, additional: usize) { - self.data.reserve(additional); + /// Since the number of elements the map can actually store depend on the index conversion + /// function, which is usually an implementation detail of the graph model, this is not meant to + /// be called directly. Use [`GraphTopologyAddition::sync_vertex_map`] or + /// [`GraphTopologyAddition::sync_edge_map`] instead. + /// + /// [`new`]: ElementMap::new + /// [`GraphTopologyAddition::sync_vertex_map`]: crate::traits::GraphTopologyAddition::sync_vertex_map + /// [`GraphTopologyAddition::sync_edge_map`]: crate::traits::GraphTopologyAddition::sync_edge_map + pub fn grow(&mut self, new_len: usize) { + if new_len > self.data.len() { + self.data.resize(new_len, self.default.clone()); + } } /// Returns a new map over the same elements with values transformed by `f`. /// - /// The default value of the new map is `f` applied to the original default, so reads beyond the - /// current capacity of the new map behave consistently with the transformation. + /// The default value of the new map is `f` applied to the original default, so slots later added + /// to the new map by [`GraphTopologyAddition::sync_vertex_map`] or + /// [`GraphTopologyAddition::sync_edge_map`] are filled consistently with the transformation. + /// + /// [`GraphTopologyAddition::sync_vertex_map`]: crate::traits::GraphTopologyAddition::sync_vertex_map + /// [`GraphTopologyAddition::sync_edge_map`]: crate::traits::GraphTopologyAddition::sync_edge_map /// /// # Examples /// /// ``` /// # use grapherity::maps::ElementMap; - /// let mut map: ElementMap> = ElementMap::new(None, |e| e, 0); + /// let mut map: ElementMap> = ElementMap::new(None, |e| e, 2); /// map[1] = Some(42); /// /// let bools = map.map(|v| v.is_some()); /// assert_eq!(bools[0], false); /// assert_eq!(bools[1], true); - /// // Returns mapped default for reads beyond capacity. - /// assert_eq!(bools[9], false); /// ``` pub fn map(&self, f: impl Fn(&T) -> U) -> ElementMap { ElementMap { @@ -84,22 +100,13 @@ impl Index for ElementMap { type Output = T; fn index(&self, e: E) -> &T { - let i = (self.to_index)(e); - if i < self.data.len() { - &self.data[i] - } else { - &self.default - } + &self.data[(self.to_index)(e)] } } impl IndexMut for ElementMap { fn index_mut(&mut self, e: E) -> &mut T { - let i = (self.to_index)(e); - if i >= self.data.len() { - self.data.resize(i + 1, self.default.clone()); - } - &mut self.data[i] + &mut self.data[(self.to_index)(e)] } } @@ -109,67 +116,97 @@ mod tests { #[test] fn initial_values_are_default() { - let map = ElementMap::new(42, |e| e, 0); + let map = ElementMap::new(42, |e| e, 6); assert_eq!(map[0], 42, "initial read at 0 should be default"); assert_eq!(map[5], 42, "initial read at 5 should be default"); } #[test] - fn reserve_increases_capacity_without_initializing() { - let mut map = ElementMap::new(0, |e| e, 0); - let capacity_before = map.capacity(); - map.reserve(capacity_before + 10); - assert!( - map.capacity() > capacity_before, - "expected sufficient capacity increase after reserve" - ); - assert_eq!(map[5], 0, "expected read beyond length to return default"); + #[should_panic] + fn read_out_of_bounds() { + let map = ElementMap::new(42, |e| e, 3); + let _ = map[3]; } #[test] - fn reserve_prevents_reallocation_on_write() { - let mut map = ElementMap::new(0, |e| e, 0); - map.reserve(10); - let capacity_before = map.capacity(); - for i in 0..10 { - map[i] = i as i32; - } - assert_eq!( - map.capacity(), - capacity_before, - "writes within reserved capacity should not reallocate" - ); - } - - #[test] - fn reserve_does_not_overwrite_existing_values() { - let mut map = ElementMap::new(0, |e| e, 0); - map[0] = 5; - let capacity_before = map.capacity(); - map.reserve(capacity_before + 10); - assert_eq!(map[0], 5, "reserve should not change existing data"); + #[should_panic] + fn write_out_of_bounds() { + let mut map = ElementMap::new(0, |e| e, 3); + map[3] = 1; } #[test] fn write_and_read() { - let mut map = ElementMap::new(0, |e| e, 0); + let mut map = ElementMap::new(0, |e| e, 2); map[1] = 7; assert_eq!(map[1], 7, "unexpected read value after write"); assert_eq!(map[0], 0, "initial read should be default"); } + #[test] + fn grow() { + let mut map = ElementMap::new(0, |e| e, 2); + assert_eq!( + map.data.len(), + 2, + "precondition: unexpected data length before grow" + ); + map.grow(10); + assert_eq!(map.data.len(), 10, "unexpected data length after grow"); + } + + #[test] + fn grow_extends_with_default() { + let mut map = ElementMap::new(17, |e| e, 3); + map.grow(6); + assert_eq!(map[5], 17, "read of newly grown slot should be default"); + map[5] = 9; + assert_eq!( + map[5], 9, + "unexpected value after write in newly grown slot" + ); + } + + #[test] + fn grow_preserves_existing_values() { + let mut map = ElementMap::new(22, |e| e, 3); + map[0] = 5; + map.grow(10); + assert_eq!(map[0], 5, "unexpected data after grow"); + } + + #[test] + fn grow_to_smaller_len() { + let mut map = ElementMap::new(23, |e| e, 6); + map[5] = 9; + map.grow(3); + assert_eq!( + map[5], 9, + "grow to a smaller length should not discard data" + ); + assert_eq!(map.data.len(), 6, "unexpected data length"); + } + #[test] fn map() { - let mut source: ElementMap> = ElementMap::new(None, |e| e, 0); + let mut source: ElementMap> = ElementMap::new(None, |e| e, 3); source[1] = Some(42); source[2] = None; let mapped = source.map(|v| v.is_some()); - assert_eq!(mapped[0], false, "unset entry should map to false"); - assert_eq!(mapped[1], true, "Some entry should map to true"); - assert_eq!(mapped[2], false, "explicit None entry should map to false"); - assert_eq!( - mapped[9], false, - "read beyond capacity should return mapped default" + assert!(!mapped[0], "unset entry should map to false"); + assert!(mapped[1], "Some entry should map to true"); + assert!(!mapped[2], "explicit None entry should map to false"); + } + + #[test] + fn map_grow() { + let mut source: ElementMap> = ElementMap::new(None, |e| e, 2); + source[1] = Some(42); + let mut mapped = source.map(|v| v.is_some()); + mapped.grow(4); + assert!( + !mapped[3], + "slot added by grow should hold the mapped default" ); } } diff --git a/src/models/append_graph.rs b/src/models/append_graph.rs index 1dd03db..96d36d1 100644 --- a/src/models/append_graph.rs +++ b/src/models/append_graph.rs @@ -170,7 +170,6 @@ impl GraphTopology for AppendGraph { self.vertices.len() } - // TODO: Create with capacity 0, maybe offer pre-allocated alternative? fn vertex_map(&self, default: T) -> ElementMap { ElementMap::new(default, |v| v.0, self.vertex_capacity()) } @@ -179,7 +178,6 @@ impl GraphTopology for AppendGraph { self.incidences.len() / 2 } - // TODO: Create with capacity 0, maybe offer pre-allocated alternative? fn edge_map(&self, default: T) -> ElementMap { ElementMap::new(default, |e| e.index() / 2, self.edge_capacity()) } diff --git a/src/models/frozen_graph.rs b/src/models/frozen_graph.rs index eeaa08e..4db2439 100644 --- a/src/models/frozen_graph.rs +++ b/src/models/frozen_graph.rs @@ -144,7 +144,8 @@ impl GraphTopology for FrozenGraph { // TODO: Find a way to reduce the needed capacity for the edge ElementMap. This could be improved by finding a small vertex cover and store those vertices first. See https://en.wikipedia.org/wiki/Vertex_cover#Approximate_evaluation fn edge_map(&self, default: T) -> ElementMap { - ElementMap::new(default, |e| e.0, self.incidences.len() - 1) + // "incidences.len() - 1" would underflow for an edge-free graph. + ElementMap::new(default, |e| e.0, self.incidences.len().saturating_sub(1)) } fn degree(&self, v: Self::Vertex) -> usize { diff --git a/src/models/graph.rs b/src/models/graph.rs index 4b85360..a95e1c9 100644 --- a/src/models/graph.rs +++ b/src/models/graph.rs @@ -262,7 +262,6 @@ impl GraphTopology for Graph { self.vertices.len() } - // TODO: Create with capacity 0, maybe offer pre-allocated alternative? fn vertex_map(&self, default: T) -> ElementMap { ElementMap::new(default, |v| v.arr_idx(), self.vertex_capacity()) } @@ -271,7 +270,6 @@ impl GraphTopology for Graph { self.incidences.len() / 2 } - // TODO: Create with capacity 0, maybe offer pre-allocated alternative? fn edge_map(&self, default: T) -> ElementMap { ElementMap::new(default, |e| e.arr_idx() / 2, self.edge_capacity()) } diff --git a/src/testing/graph_topology_addition_testing.rs b/src/testing/graph_topology_addition_testing.rs index 77f68b1..935d715 100644 --- a/src/testing/graph_topology_addition_testing.rs +++ b/src/testing/graph_topology_addition_testing.rs @@ -15,8 +15,10 @@ macro_rules! graph_topology_addition_tests { reserve_edges_does_not_affect_vertex_capacity, add_vertex, add_edge, - vertex_map_new_vertex, - edge_map_new_edge, + sync_vertex_map_after_add, + sync_edge_map_after_add, + sync_vertex_map_preserves_values, + sync_edge_map_preserves_values, ); }; @@ -122,31 +124,68 @@ where assert_ne!(graph.add_edge(u, v), e, "unexpected duplicate edge"); } -pub fn vertex_map_new_vertex() { +pub fn sync_vertex_map_after_add() { let mut graph = G::default(); let mut map = graph.vertex_map(27); + let u = graph.add_vertex(); let v = graph.add_vertex(); + graph.sync_vertex_map(&mut map); assert_eq!( - map[v], 27, + map[u], 27, "unexpected value from default map read for new vertex" ); - map[v] = 9; + map[u] = 9; assert_eq!( - map[v], 9, + map[u], 9, "unexpected value from map read after write for new vertex" ); + assert_eq!( + map[v], 27, + "unexpected value from default map read for second new vertex" + ); } -pub fn edge_map_new_edge() { +pub fn sync_edge_map_after_add() { let mut graph = G::default(); let u = graph.add_vertex(); let v = graph.add_vertex(); let mut map = graph.edge_map(28); let e = graph.add_edge(u, v); - assert_eq!(map[e], 28); + let f = graph.add_edge(u, v); + graph.sync_edge_map(&mut map); + assert_eq!( + map[e], 28, + "unexpected value from default map read for new edge" + ); map[e] = 8; assert_eq!( map[e], 8, "unexpected value from map read after write for new edge" ); + assert_eq!( + map[f], 28, + "unexpected value from default map read for second new edge" + ); +} + +pub fn sync_vertex_map_preserves_values() { + let mut graph = G::default(); + let v = graph.add_vertex(); + let mut map = graph.vertex_map(0); + map[v] = 5; + graph.reserve_vertices(20); + graph.sync_vertex_map(&mut map); + assert_eq!(map[v], 5, "sync should not change existing map values"); +} + +pub fn sync_edge_map_preserves_values() { + let mut graph = G::default(); + let u = graph.add_vertex(); + let v = graph.add_vertex(); + let e = graph.add_edge(u, v); + let mut map = graph.edge_map(0); + map[e] = 6; + graph.reserve_edges(20); + graph.sync_edge_map(&mut map); + assert_eq!(map[e], 6, "sync should not change existing map values"); } diff --git a/src/testing/graph_topology_testing.rs b/src/testing/graph_topology_testing.rs index 415fc52..d595c2d 100644 --- a/src/testing/graph_topology_testing.rs +++ b/src/testing/graph_topology_testing.rs @@ -72,12 +72,22 @@ pub fn vertex_count() { assert_eq!(graph.vertex_count(), 10, "unexpected vertex count"); } -pub fn vertex_map() { - let (graph, v) = G::single_vertex(); - let mut map = graph.vertex_map(27); - assert_eq!(map[v], 27, "unexpected value from default map read"); - map[v] = 9; - assert_eq!(map[v], 9, "unexpected value from map after write"); +pub fn vertex_map() +where + G::Vertex: Debug, +{ + let (graph, vertices, _, _) = G::standard(); + let mut map = graph.vertex_map(88); + for (i, &v) in vertices.iter().enumerate() { + assert_eq!( + map[v], 88, + "unexpected value from default map read for vertex {v:?}" + ); + map[v] = i; + } + for (i, &v) in vertices.iter().enumerate() { + assert_eq!(map[v], i, "unexpected value after write for vertex {v:?}"); + } } pub fn edge_count_empty() { @@ -90,12 +100,22 @@ pub fn edge_count() { assert_eq!(graph.edge_count(), 18, "unexpected edge count"); } -pub fn edge_map() { - let (graph, _, e) = G::single_edge(); - let mut map = graph.edge_map(28); - assert_eq!(map[e], 28, "unexpected value from default map read"); - map[e] = 8; - assert_eq!(map[e], 8, "unexpected value from map after write"); +pub fn edge_map() +where + G::Edge: Debug, +{ + let (graph, _, edges, _) = G::standard(); + let mut map = graph.edge_map(99); + for (i, &(e, _, _)) in edges.iter().enumerate() { + assert_eq!( + map[e], 99, + "unexpected value from default map read for edge {e:?}" + ); + map[e] = i; + } + for (i, &(e, _, _)) in edges.iter().enumerate() { + assert_eq!(map[e], i, "unexpected value after write for edge {e:?}"); + } } pub fn degree_zero() { diff --git a/src/traits.rs b/src/traits.rs index d491668..06ca661 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -47,6 +47,10 @@ pub trait GraphTopology { /// Creates and returns an [`ElementMap`] for the vertices with every slot initialised to /// `default`. /// + /// The returned map only covers the vertices that exist in the graph at the time of the call. + /// Use [`GraphTopologyAddition::sync_vertex_map`] to extend the map before accessing new + /// vertices. + /// /// # Examples /// /// ``` @@ -54,13 +58,11 @@ pub trait GraphTopology { /// # use grapherity::models::Graph; /// let mut graph = Graph::new(); /// let v1 = graph.add_vertex(); + /// let v2 = 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"); @@ -72,6 +74,9 @@ pub trait GraphTopology { /// Creates and returns an [`ElementMap`] for the edges with every slot initialised to `default`. /// + /// The returned map only covers the edges that exist in the graph at the time of the call. Use + /// [`GraphTopologyAddition::sync_edge_map`] to extend the map before accessing new edges. + /// /// # Examples /// /// ``` @@ -81,13 +86,11 @@ pub trait GraphTopology { /// 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 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); @@ -180,9 +183,21 @@ pub trait GraphTopology { /// undirected graph. pub trait GraphTopologyAddition: GraphTopology + Default { /// Returns the total number of vertices the graph can hold without reallocating. + /// + /// For every valid [`Vertex`] handle, the index produced by the conversion function used in + /// [`GraphTopology::vertex_map`] must be strictly less than this capacity. Implementors of + /// [`GraphTopology::vertex_map`] must size the returned vertex [`ElementMap`] to this capacity. + /// + /// [`Vertex`]: GraphTopology::Vertex fn vertex_capacity(&self) -> usize; /// Returns the total number of edges the graph can hold without reallocating. + /// + /// For every valid [`Edge`] handle, the index produced by the conversion function used in + /// [`GraphTopology::edge_map`] must be strictly less than this capacity. Implementors of + /// [`GraphTopology::edge_map`] must size the returned edge [`ElementMap`] to this capacity. + /// + /// [`Edge`]: GraphTopology::Edge fn edge_capacity(&self) -> usize; /// Reserves capacity for at least `additional` more vertices. Does nothing if capacity is @@ -212,6 +227,65 @@ pub trait GraphTopologyAddition: GraphTopology + Default { /// /// Panics if `u` or `v` is not a valid vertex of this graph. fn add_edge(&mut self, u: Self::Vertex, v: Self::Vertex) -> Self::Edge; + + /// Grows `map` so that it covers a number of vertices up to at least [`vertex_capacity`]. + /// + /// The data for any newly covered vertices is initialised to the map's original default value. + /// Does nothing if `map` already covers enough vertices. + /// + /// An [`ElementMap`] does not grow on its own, so call this after [`reserve_vertices`] (or + /// after vertices have already been added) and before accessing `map` for any newly added + /// vertices. + /// + /// # Examples + /// + /// ``` + /// # use grapherity::prelude::*; + /// # use grapherity::models::Graph; + /// let mut graph = Graph::new(); + /// let mut labels = graph.vertex_map("z"); + /// graph.reserve_vertices(1); + /// graph.sync_vertex_map(&mut labels); + /// let v = graph.add_vertex(); + /// labels[v] = "a"; + /// assert_eq!(labels[v], "a"); + /// ``` + /// + /// [`reserve_vertices`]: Self::reserve_vertices + /// [`vertex_capacity`]: Self::vertex_capacity + fn sync_vertex_map(&self, map: &mut ElementMap) { + map.grow(self.vertex_capacity()); + } + + /// Grows `map` so that it covers a number of edges up to at least [`edge_capacity`]. + /// + /// The data for any newly covered edges is initialised to the map's original default value. + /// Does nothing if `map` already covers enough edges. + /// + /// An [`ElementMap`] does not grow on its own, so call this after [`reserve_edges`] (or after + /// edges have already been added) and before accessing `map` for any newly added edges. + /// + /// # Examples + /// + /// ``` + /// # use grapherity::prelude::*; + /// # use grapherity::models::Graph; + /// let mut graph = Graph::new(); + /// let mut weights = graph.edge_map(1); + /// let v1 = graph.add_vertex(); + /// let v2 = graph.add_vertex(); + /// graph.reserve_edges(1); + /// graph.sync_edge_map(&mut weights); + /// let e = graph.add_edge(v1, v2); + /// weights[e] = 5; + /// assert_eq!(weights[e], 5); + /// ``` + /// + /// [`reserve_edges`]: Self::reserve_edges + /// [`edge_capacity`]: Self::edge_capacity + fn sync_edge_map(&self, map: &mut ElementMap) { + map.grow(self.edge_capacity()); + } } /// A trait that adds deletion operations to an undirected graph topology.