Files
grapherity/src/maps.rs
T
2026-10-15 20:09:35 +02:00

383 lines
14 KiB
Rust

//! 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<E: Copy, T: Clone> {
data: Vec<T>,
default: T,
to_index: fn(E) -> usize,
}
impl<E: Copy, T: Clone> ElementMap<E, T> {
/// 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<usize, Option<i32>> = 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<U: Clone>(&self, f: impl Fn(&T) -> U) -> ElementMap<E, U> {
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<F: Copy + Eq>(
&self,
mapping: &ElementMap<E, F>,
template: &ElementMap<F, ()>,
) -> ElementMap<F, T> {
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<E: Copy, T: Clone> Index<E> for ElementMap<E, T> {
type Output = T;
fn index(&self, e: E) -> &T {
&self.data[(self.to_index)(e)]
}
}
impl<E: Copy, T: Clone> IndexMut<E> for ElementMap<E, T> {
fn index_mut(&mut self, e: E) -> &mut T {
&mut self.data[(self.to_index)(e)]
}
}
#[cfg(test)]
mod tests {
use std::rc::Rc;
use super::*;
#[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<usize, Option<i32>> = 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<usize, Option<i32>> = 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::<usize, i32>::new(0, |e| e, 3);
source[0] = 10;
source[1] = 20;
source[2] = 30;
let mut mapping = ElementMap::<usize, u32>::new(u32::MAX, |e| e, 3);
mapping[0] = 0;
mapping[1] = 1;
mapping[2] = 2;
// Reverses the order of the slots.
let template = ElementMap::<u32, ()>::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::<i32>::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"
);
}
}