382 lines
14 KiB
Rust
382 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 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<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"
|
|
);
|
|
}
|
|
}
|