document sparse set #18

Manually merged
bitsyndicate merged 4 commits from sparse-set-docs into main 2025-05-06 18:29:36 +00:00
2 changed files with 242 additions and 6 deletions

View file

@ -1,3 +1,6 @@
/// Collections types for Zenyx
///
/// - [`SparseSet`]
mod sparse_set;
pub use sparse_set::SparseSet;

View file

@ -10,17 +10,36 @@ use bytemuck::Contiguous;
const SPARSE_PAGESIZE: usize = (1 << 10) * 4;
type SparsePage<A> = Option<(Box<[Option<NonZeroUsize>; SPARSE_PAGESIZE], A>, usize)>;
/// A sparse set for fast lookup of large indices.
bitsyndicate marked this conversation as resolved Outdated

Nit, missing period.

Nit, missing period.
///
/// The sparse allocator is mainly used for bulk allocations in the system's page size
/// for the lookup array. It will also be used for the array of pointers into those
/// bulk allocations. Additionally it will be used for the reverse map that generates keys
/// from the value in the internal packed array.
bitsyndicate marked this conversation as resolved Outdated

Nit, missing period.

Nit, missing period.
///
/// The packed allocator will exclusively be used to store the values of type `T`.
///
/// All operations on this datastructure, meaning insertion, lookup, and deletion, are `O(1)`.
///
/// This data structure does not in any way guarantee ordering of the values on
bitsyndicate marked this conversation as resolved Outdated

"datastructure" is two words not one (i.e. data structure).

"datastructure" is two words not one (i.e. data structure).
/// its own.
#[derive(Hash)]
pub struct SparseSet<T, PackedAlloc = Global, SparseAlloc = Global>
where
PackedAlloc: Allocator,
SparseAlloc: Allocator,
{
/// The paginated array of keys. The value at the key is an index into the dense array minus
/// one where the value corresponding to that key is stored.
sparse: Vec<SparsePage<SparseAlloc>, SparseAlloc>,
/// The dense array where the values corresponding to the keys are stored.
bitsyndicate marked this conversation as resolved Outdated

Nit, missing period.

Nit, missing period.
dense: Vec<T, PackedAlloc>,
/// The reverse map to get the index in the sparse array from the index in the dense array.
bitsyndicate marked this conversation as resolved Outdated

Nit, missing period.

Nit, missing period.
dense_to_id: Vec<usize, SparseAlloc>,
}
impl<T> SparseSet<T> {
/// Creates a new [`SparseSet`] with the global allocator.
pub const fn new() -> Self {
Self {
sparse: Vec::new(),
@ -30,11 +49,45 @@ impl<T> SparseSet<T> {
}
}
impl<T> Default for SparseSet<T> {
fn default() -> Self {
Self::new()
}
}
impl<T, PackedAlloc, SparseAlloc> core::fmt::Debug for SparseSet<T, PackedAlloc, SparseAlloc>
where
T: core::fmt::Debug,
PackedAlloc: Allocator,
SparseAlloc: Allocator,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_map()
.entries(self.dense_to_id.iter().zip(self.dense.iter()))
.finish()
}
}
impl<T, PackedAlloc, SparseAlloc> SparseSet<T, PackedAlloc, SparseAlloc>
where
PackedAlloc: Allocator,
SparseAlloc: Allocator + Clone,
{
/// Inserts an element into the sparse set with the key `id`. This will
/// return the previous value if it already exists.
///
/// ```
/// use zenyx::collections::SparseSet;
///
/// let mut sparse_set: SparseSet<u32> = SparseSet::new();
///
/// sparse_set.insert(10, 123);
/// assert_eq!(sparse_set.get(10), Some(&123));
///
/// let prev = sparse_set.insert(10, 9);
/// assert_eq!(prev, Some(123));
/// assert_eq!(sparse_set.get(10), Some(&9));
/// ```
pub fn insert(&mut self, id: usize, value: T) -> Option<T> {
match self.get_dense_idx(id) {
Some(idx) => {
@ -52,15 +105,39 @@ where
}
}
/// Gets the value with the key `id`.
bitsyndicate marked this conversation as resolved Outdated

Nit, missing period.

Nit, missing period.
///
/// ```
/// use zenyx::collections::SparseSet;
///
/// let mut sparse_set: SparseSet<u32> = SparseSet::new();
///
/// sparse_set.insert(10, 123);
/// assert_eq!(sparse_set.get(10), Some(&123));
/// ```
pub fn get(&self, id: usize) -> Option<&T> {
self.dense.get(self.get_dense_idx(id)?)
}
/// Gets the value with the key `id` mutably.
///
/// ```
/// use zenyx::collections::SparseSet;
///
/// let mut sparse_set: SparseSet<u32> = SparseSet::new();
///
/// sparse_set.insert(10, 123);
/// let value = sparse_set.get_mut(10).unwrap();
/// *value = 0;
/// assert_eq!(sparse_set.get(10), Some(&0));
/// ```
pub fn get_mut(&mut self, id: usize) -> Option<&mut T> {
let idx = self.get_dense_idx(id)?;
self.dense.get_mut(idx)
}
/// Sets the dense index of an `key` to `idx`. This will remove said index
/// if it is [`None`].
fn set_dense_idx(&mut self, id: usize, idx: Option<usize>) {
let page = id / SPARSE_PAGESIZE;
let sparse_index = id % SPARSE_PAGESIZE;
@ -84,6 +161,15 @@ where
}
}
/// Gets the index in the dense array for a key `id`.

Nit, missing period.

Nit, missing period.
///
/// ```
/// use zenyx::collections::SparseSet;
///
/// let mut sparse_set: SparseSet<u32> = SparseSet::new();
/// sparse_set.insert(10, 123);
/// assert_eq!(sparse_set.values()[sparse_set.get_dense_idx(10).unwrap()], 123);
/// ```
pub fn get_dense_idx(&self, id: usize) -> Option<usize> {
let page = id / SPARSE_PAGESIZE;
let sparse_index = id % SPARSE_PAGESIZE;
@ -91,6 +177,8 @@ where
page.0[sparse_index].map(|idx| idx.into_integer() - 1)
}
/// This reduces the usage count for a page in the sparse array, deallocating
/// it if it is not used anymore.
bitsyndicate marked this conversation as resolved Outdated

Nit, missing period.

Nit, missing period.
fn reduce_page_usage_count(&mut self, id: usize) {
let page = id / SPARSE_PAGESIZE;
let Some(usage) = &mut self.sparse[page] else {
@ -103,6 +191,7 @@ where
}
}
/// Increase the page usage count for a page in the sparse array.
bitsyndicate marked this conversation as resolved Outdated

Nit, missing period.

Nit, missing period.
fn increase_page_usage_count(&mut self, id: usize) {
let page = id / SPARSE_PAGESIZE;
if page >= self.sparse.len() {
@ -114,6 +203,18 @@ where
usage.1 += 1;
}
/// Removes the value with the key `id` from the sparse set, returning the
/// value if it existed.
///
/// ```
/// use zenyx::collections::SparseSet;
///
/// let mut sparse_set: SparseSet<u32> = SparseSet::new();
///
/// sparse_set.insert(10, 123);
/// assert_eq!(sparse_set.remove(10), Some(123));
/// assert_eq!(sparse_set.remove(10), None);
/// ```
pub fn remove(&mut self, id: usize) -> Option<T> {
let index = self.get_dense_idx(id)?;
if self.dense.is_empty() {
@ -129,26 +230,126 @@ where
Some(previous)
}
/// Returns if there are values in this sparse set.
/// ```
/// use zenyx::collections::SparseSet;
///
/// let mut sparse_set: SparseSet<u32> = SparseSet::new();
/// assert!(sparse_set.is_empty());
/// sparse_set.insert(10, 123);
/// assert!(!sparse_set.is_empty());
/// ```
pub fn is_empty(&self) -> bool {
self.len() == 0
}
/// Returns the number of values in this sparse set.
bitsyndicate marked this conversation as resolved Outdated

Nit, missing period.

Nit, missing period.
///
/// ```
/// use zenyx::collections::SparseSet;
///
/// let mut sparse_set: SparseSet<u32> = SparseSet::new();
/// sparse_set.insert(10, 123);
/// sparse_set.insert(10, 9);
/// sparse_set.insert(11, 10);
/// assert_eq!(sparse_set.len(), 2);
/// ```
pub fn len(&self) -> usize {
self.dense.len()
}
/// Checks if the sparse set contains a value with key `id`.
bitsyndicate marked this conversation as resolved Outdated

Nit, missing period.

Nit, missing period.
///
/// ```
/// use zenyx::collections::SparseSet;
///
/// let mut sparse_set: SparseSet<u32> = SparseSet::new();
/// assert!(!sparse_set.contains(10));
/// sparse_set.insert(10, 123);
/// assert!(sparse_set.contains(10));
/// ```
pub fn contains(&self, id: usize) -> bool {
self.get_dense_idx(id).is_some()
}
/// Gets the keys of all values in the sparse set. This method does not provide
/// any ordering guarantees other than the keys contained corresponding to
/// the values with the same index returned by [`Self::values`].
///
/// ```
/// use zenyx::collections::SparseSet;
///
/// let mut sparse_set: SparseSet<u32> = SparseSet::new();
/// assert!(sparse_set.keys().is_empty());
/// sparse_set.insert(10, 10);
/// sparse_set.insert(9, 10);
/// sparse_set.insert(11, 10);
///
/// assert_eq!(sparse_set.keys(), &[10, 9, 11]);
/// sparse_set.remove(10);
/// assert_eq!(sparse_set.keys(), &[11, 9]);
/// ```
pub fn keys(&self) -> &[usize] {
&self.dense_to_id
}
/// Gets all values in the sparse set, the corresponding `key` is at the same
/// position in the slice returned by [`Self::keys`].
///
/// Otherwise there are no ordering guarantees.
///
/// ```
/// use zenyx::collections::SparseSet;
///
/// let mut sparse_set: SparseSet<u32> = SparseSet::new();
/// assert!(sparse_set.values().is_empty());
/// sparse_set.insert(10, 10);
/// sparse_set.insert(9, 9);
/// sparse_set.insert(11, 11);
///
/// assert_eq!(sparse_set.values(), &[10, 9, 11]);
/// sparse_set.remove(10);
/// assert_eq!(sparse_set.values(), &[11, 9]);
/// ```
pub fn values(&self) -> &[T] {
&self.dense
}
/// Mutable version of [`Self::keys`].
bitsyndicate marked this conversation as resolved Outdated

Nit, missing period.

Nit, missing period.
///
/// ```
/// use zenyx::collections::SparseSet;
///
/// let mut sparse_set: SparseSet<u32> = SparseSet::new();
/// assert!(sparse_set.values().is_empty());
/// sparse_set.insert(10, 10);
/// sparse_set.insert(9, 9);
/// sparse_set.insert(11, 11);
///
/// let dense_of_9 = sparse_set.get_dense_idx(9).unwrap();
/// let dense_of_10 = sparse_set.get_dense_idx(10).unwrap();
///
/// let values = sparse_set.values_mut();
/// values[dense_of_10] = 9;
/// values[dense_of_9] = 10;
///
/// assert_eq!(sparse_set.get(10), Some(&9));
/// assert_eq!(sparse_set.get(9), Some(&10));
/// ```
pub fn values_mut(&mut self) -> &mut [T] {
&mut self.dense
}
/// Creates a new [`SparseSet`] with the values allocated by the `packed_alloc`
/// and everything else, as described in the top level documentation for [`SparseSet`]
/// in the `sparse_alloc`.
///
/// ```
/// use zenyx::collections::SparseSet;
/// use allocator_api2::alloc::Global;
///
/// let sparse_set = SparseSet::<u32>::new_in(Global, Global);
/// ```
pub fn new_in(packed_alloc: PackedAlloc, sparse_alloc: SparseAlloc) -> Self {
Self {
dense: Vec::new_in(packed_alloc),
@ -162,6 +363,16 @@ impl<T, PackedAlloc> SparseSet<T, PackedAlloc>
where
PackedAlloc: Allocator,
{
/// Creates a new [`SparseSet`] with the values allocated by the `packed_alloc`
/// Everything else, as described in the top level documentation for [`SparseSet`]
/// is allocated using the global allocator.
///
/// ```
/// use zenyx::collections::SparseSet;
/// use allocator_api2::alloc::Global;
///
/// let sparse_set = SparseSet::<u32>::new_in_packed(Global);
/// ```
pub const fn new_in_packed(packed_alloc: PackedAlloc) -> Self {
Self {
sparse: Vec::new(),
@ -236,7 +447,10 @@ mod tests {
assert_eq!(sparse_set.remove(SPARSE_PAGESIZE + 2).unwrap(), 3);
assert_eq!(sparse_set.sparse[1].as_ref().unwrap().1, 2);
assert_eq!(sparse_set.keys(), [10, 11, 12, SPARSE_PAGESIZE, SPARSE_PAGESIZE + 1]);
assert_eq!(
sparse_set.keys(),
[10, 11, 12, SPARSE_PAGESIZE, SPARSE_PAGESIZE + 1]
);
assert_eq!(sparse_set.values(), [1, 2, 2, 1, 2]);
assert_eq!(sparse_set.remove(SPARSE_PAGESIZE + 1).unwrap(), 2);
@ -249,25 +463,44 @@ mod tests {
assert_eq!(sparse_set.keys(), [10, 11, 12]);
assert_eq!(sparse_set.values(), [1, 2, 2]);
sparse_set.insert(SPARSE_PAGESIZE, 1);
sparse_set.insert(SPARSE_PAGESIZE + 1, 2);
sparse_set.insert(SPARSE_PAGESIZE + 2, 3);
assert_eq!(sparse_set.remove(10).unwrap(), 1);
assert_eq!(sparse_set.sparse[0].as_ref().unwrap().1, 2);
// swap-remove
assert_eq!(sparse_set.keys(), [SPARSE_PAGESIZE + 2, 11, 12, SPARSE_PAGESIZE, SPARSE_PAGESIZE + 1]);
// swap-remove
assert_eq!(
sparse_set.keys(),
[
SPARSE_PAGESIZE + 2,
11,
12,
SPARSE_PAGESIZE,
SPARSE_PAGESIZE + 1
]
);
assert_eq!(sparse_set.values(), [3, 2, 2, 1, 2]);
assert_eq!(sparse_set.remove(11).unwrap(), 2);
assert_eq!(sparse_set.sparse[0].as_ref().unwrap().1, 1);
assert_eq!(sparse_set.keys(), [SPARSE_PAGESIZE + 2, SPARSE_PAGESIZE + 1, 12, SPARSE_PAGESIZE]);
assert_eq!(
sparse_set.keys(),
[
SPARSE_PAGESIZE + 2,
SPARSE_PAGESIZE + 1,
12,
SPARSE_PAGESIZE
]
);
assert_eq!(sparse_set.values(), [3, 2, 2, 1]);
assert_eq!(sparse_set.remove(12).unwrap(), 2);
assert!(sparse_set.sparse[0].is_none());
assert_eq!(sparse_set.keys(), [SPARSE_PAGESIZE + 2, SPARSE_PAGESIZE + 1, SPARSE_PAGESIZE]);
assert_eq!(
sparse_set.keys(),
[SPARSE_PAGESIZE + 2, SPARSE_PAGESIZE + 1, SPARSE_PAGESIZE]
);
assert_eq!(sparse_set.values(), [3, 2, 1]);
}
}