//! Provides maps to associate custom data to graph vertices and edges. //! //! [`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}; /// A map to associate custom data to graph vertices or edges. /// /// This map uses raw element indices to associate homogenous custom data of type `T` to graph /// vertices or edges. The implementation uses a [`Vec`], allocating contiguous slots for the data, /// which means that the provided index conversion function should map elements to contiguous /// indices, or indices with relatively few gaps. /// /// 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, to_index: fn(E) -> usize, } impl ElementMap { /// Creates a new map with the given `default` value, an index conversion function `to_index`, /// 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(); len], default, to_index, } } /// 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. /// /// 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 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, 2); /// map[1] = Some(42); /// /// let bools = map.map(|v| v.is_some()); /// assert_eq!(bools[0], false); /// assert_eq!(bools[1], true); /// ``` pub fn map(&self, f: impl Fn(&T) -> U) -> ElementMap { ElementMap { data: self.data.iter().map(&f).collect(), default: f(&self.default), to_index: self.to_index, } } /// Creates a new `ElementMap` indexed by elements of type `F`, such that for each element *e* /// of this map that `mapping` maps to some *f*, the new map contains at *f* a clone of the /// value this map contains at *e*. /// /// A slot of `mapping` counts as unmapped if it holds the default value of `mapping`, which /// must therefore never be a valid element of `F`. Unmapped slots are skipped. Slots of the new /// map that no element is mapped to, and slots of this map beyond the length of `mapping` or /// vice versa, are left at this map's default value. The `template` is only used to determine /// the length and the index conversion function of the new map. /// /// Iterates the raw slots of both maps, so it neither needs the graph nor the element handles. pub(crate) fn remap( &self, mapping: &ElementMap, template: &ElementMap, ) -> ElementMap { let mut result = ElementMap { data: vec![self.default.clone(); template.data.len()], default: self.default.clone(), to_index: template.to_index, }; for (value, &f) in self.data.iter().zip(&mapping.data) { if f != mapping.default { result.data[(template.to_index)(f)] = value.clone(); } } result } } impl Index for ElementMap { type Output = T; fn index(&self, e: E) -> &T { &self.data[(self.to_index)(e)] } } impl IndexMut for ElementMap { fn index_mut(&mut self, e: E) -> &mut T { &mut self.data[(self.to_index)(e)] } } #[cfg(test)] mod tests { use super::*; use std::rc::Rc; #[test] fn initial_values_are_default() { 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] #[should_panic] fn read_out_of_bounds() { let map = ElementMap::new(42, |e| e, 3); let _ = map[3]; } #[test] #[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, 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, 3); source[1] = Some(42); source[2] = None; let mapped = source.map(|v| v.is_some()); 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" ); } #[test] fn remap() { let mut source = ElementMap::new('-', |e| e, 3); source[0] = 'a'; source[1] = 'b'; source[2] = 'c'; let mut mapping = ElementMap::new(usize::MAX, |e| e, 3); mapping[0] = 2; mapping[1] = 0; mapping[2] = 1; let template = ElementMap::new((), |f| f, 3); let result = source.remap(&mapping, &template); assert_eq!(result[0], 'b', "unexpected value at slot 0"); assert_eq!(result[1], 'c', "unexpected value at slot 1"); assert_eq!(result[2], 'a', "unexpected value at slot 2"); assert_eq!( result.default, '-', "default should be taken from the source map" ); } #[test] fn remap_skips_default() { let mut source = ElementMap::new(0, |e| e, 3); source[0] = 1; source[1] = 2; source[2] = 3; // Slot 1 stays unmapped, which would be out of bounds for the template if it was used. let mut mapping = ElementMap::new(usize::MAX, |e| e, 3); mapping[0] = 1; mapping[2] = 0; let template = ElementMap::new((), |f| f, 2); let result = source.remap(&mapping, &template); assert_eq!(result[0], 3, "unexpected value at slot 0"); assert_eq!(result[1], 1, "unexpected value at slot 1"); } #[test] fn remap_unmapped_slots() { let mut source = ElementMap::new(9, |e| e, 2); source[0] = 1; source[1] = 2; let mut mapping = ElementMap::new(usize::MAX, |e| e, 2); mapping[0] = 0; mapping[1] = 2; let template = ElementMap::new((), |f| f, 4); let result = source.remap(&mapping, &template); assert_eq!( result.data, vec![1, 9, 2, 9], "should have the template's length, with unmapped slots holding the source's default" ); } #[test] fn remap_index_function() { let mut source = ElementMap::::new(0, |e| e, 3); source[0] = 10; source[1] = 20; source[2] = 30; let mut mapping = ElementMap::::new(u32::MAX, |e| e, 3); mapping[0] = 0; mapping[1] = 1; mapping[2] = 2; // Reverses the order of the slots. let template = ElementMap::::new((), |f| 2 - f as usize, 3); let result = source.remap(&mapping, &template); assert_eq!( result.data, vec![30, 20, 10], "values should be written at the slots given by the template's index function" ); assert_eq!( (result.to_index)(0), 2, "index function should be taken from the template" ); } #[test] fn remap_shorter_source() { let mut source = ElementMap::new(9, |e| e, 2); source[0] = 5; source[1] = 6; let mut mapping = ElementMap::new(usize::MAX, |e| e, 4); for i in 0..4 { mapping[i] = i; } let template = ElementMap::new((), |f| f, 4); let result = source.remap(&mapping, &template); assert_eq!(result.data, vec![5, 6, 9, 9], "unexpected data"); } #[test] fn remap_longer_source() { let mut source = ElementMap::new(0, |e| e, 4); for i in 0..4 { source[i] = i + 1; } let mut mapping = ElementMap::new(usize::MAX, |e| e, 2); mapping[0] = 1; mapping[1] = 0; let template = ElementMap::new((), |f| f, 2); let result = source.remap(&mapping, &template); assert_eq!(result.data, vec![2, 1], "unexpected data"); } #[test] fn remap_empty() { let source = ElementMap::new(0, |e| e, 0); let mapping = ElementMap::new(usize::MAX, |e| e, 0); let template = ElementMap::new((), |f| f, 0); let result = source.remap(&mapping, &template); assert_eq!(result.data, Vec::::new(), "should be empty"); } #[test] fn remap_clones_values() { let value = Rc::new(1); let mut source = ElementMap::new(Rc::new(0), |e| e, 1); source[0] = Rc::clone(&value); let mut mapping = ElementMap::new(usize::MAX, |e| e, 1); mapping[0] = 0; let template = ElementMap::new((), |f| f, 1); let _result = source.remap(&mapping, &template); // The local variable, the source slot and the result slot each hold one reference. assert_eq!( Rc::strong_count(&value), 3, "result should hold its own clone of the value" ); drop(source); assert_eq!( Rc::strong_count(&value), 2, "result should keep the value alive on its own" ); } }