From 428ccc2def4ce0e14a669320ef0a676d3c79afac Mon Sep 17 00:00:00 2001 From: cavivie Date: Mon, 24 Jun 2024 00:27:05 +0800 Subject: [PATCH] feat: add slotvec, slotbkvec and slotdeque three containers chore: add Debug trait impl for slotvec, slotbkvec and slotdeque chore: make lifetime for iterators bound more clarity fix: calculation method both for bucket bits in std/nod_std --- Cargo.toml | 3 + src/lib.rs | 6 + src/slotbkvec.rs | 462 +++++++++++++++++++++++++++++++++++++++++++++++ src/slotdeque.rs | 281 ++++++++++++++++++++++++++++ src/slotvec.rs | 263 +++++++++++++++++++++++++++ 5 files changed, 1015 insertions(+) create mode 100644 src/slotbkvec.rs create mode 100644 src/slotdeque.rs create mode 100644 src/slotvec.rs diff --git a/Cargo.toml b/Cargo.toml index f649a2f..dd26a7e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,3 +21,6 @@ map = [] [profile.test] opt-level = 1 codegen-units = 1 + +[dependencies] +auto_enums = "0.8.5" diff --git a/src/lib.rs b/src/lib.rs index 9f75f8c..08593ef 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,12 +10,18 @@ extern crate alloc; mod object; mod slice; +mod slotvec; +mod slotbkvec; +mod slotdeque; mod slotmap; #[cfg(feature = "map")] mod map; pub use object::Managed; pub use slice::ManagedSlice; +pub use slotvec::SlotVec; +pub use slotbkvec::SlotBkVec; +pub use slotdeque::SlotDeque; pub use slotmap::{ Key as SlotKey, Slot as SlotIndex, diff --git a/src/slotbkvec.rs b/src/slotbkvec.rs new file mode 100644 index 0000000..df26160 --- /dev/null +++ b/src/slotbkvec.rs @@ -0,0 +1,462 @@ +//! A slotbkvec, a vector-like container with unique indices, but +//! distribute the elements in the inner vector of the outer buckets. +//! +//! See the documentation of [`SlotBkVec`] for details. +//! +//! [`SlotBkVec`]: struct.SlotBkVec.html +use core::ops::{Index, IndexMut}; + +use super::{ManagedSlice as Slice, SlotVec}; + +/// Provides a slotbkvec based on external memory. +/// +/// `N` buckets are provided, each bucket is a slice of element `T`, +/// and each indice carries bucket and slice index information, so that +/// elements in the slice can be reversely located through the buckets. +/// +/// By default its bucket number `N` is 1, which looks like a [`SlotVec`]. +/// +/// The bucket parameter of the public API function refers to the bucket +/// number of buckets minus one, the range is 0..=N-1, that is, the index. +/// +/// # Panics +/// If `N` is provided is not in the range of 1 to 16, a compilation error +/// will occur. The range of 1 to 16 is only an empirical magic number. +#[derive(Debug)] +pub struct SlotBkVec<'a, T, const N: usize = 1> { + /// Buckets for each slotvec. + buckets: [SlotVec<'a, T>; N], + /// Bucket status bits. + bucket_bits: usize, + /// Bucket bit mask. + bucket_mask: usize, +} + +impl<'a, T, const N: usize> SlotBkVec<'a, T, N> { + const MIN_BUCKET_COUNT: usize = 1; + const MAX_BUCKET_COUNT: usize = 16; + /// Compiler will check const N must be in range [1..16]. + const __COMPILER_CHECK__: () = + assert!(N >= Self::MIN_BUCKET_COUNT && N <= Self::MAX_BUCKET_COUNT); + + /// Returns the bucket bit numbers. + fn bucket_bits() -> usize { + // N <= 2: last bit mask: 0b1 , buckets: (0,1) + // N <= 4: last tow bits mask: 0b11 , buckets: (0,1,2,3) + // N <= 8: last three bits mask: 0b111 , buckets: (0,1,2,3,4,5,6,7) + // N <= 16: last four bits mask: 0b1111 , buckets: (0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15) + // this calculation method only works in std: `(N as f64).log2().ceil() as usize` + match N { + 1..=2 => 1, + 3..=4 => 2, + 5..=8 => 3, + 9..=16 => 4, + _ => unreachable!(), + } + } + + /// Returns the bucket mask from bucket bit numbers. + fn bucket_mask() -> usize { + let bucket_bits = Self::bucket_bits(); + (1 << bucket_bits) - 1 + } + + /// Returns a unmasked index and buket from a masked index. + fn index_unmask(&self, index: usize) -> (usize, usize) { + Self::calculate_index_unmask(self.bucket_bits, self.bucket_mask, index) + } + + /// Returns a masked index with a unmasked index and buket. + fn masked_index(&self, index: usize, buket: usize) -> usize { + Self::calculate_masked_index(self.bucket_bits, self.bucket_mask, index, buket) + } + + /// Returns a unmasked index and buket from a masked index. + #[inline] + fn calculate_index_unmask( + bucket_bits: usize, + bucket_mask: usize, + index: usize, + ) -> (usize, usize) { + (index >> bucket_bits, index & bucket_mask) + } + + /// Returns a masked index with a unmasked index and buket. + #[inline] + fn calculate_masked_index( + bucket_bits: usize, + bucket_mask: usize, + index: usize, + buket: usize, + ) -> usize { + (index << bucket_bits) | (buket & bucket_mask) + } + + /// Iterates every element of all slices of the buckets, as mutable. + #[auto_enums::auto_enum(DoubleEndedIterator)] + fn iter_mut_impl<'s>(&'s mut self) -> impl DoubleEndedIterator { + // This implementation does not affect performance, because N is determined + // at compile time, and a specific N will only take a specific branch. + // If there is a const-match syntax, we can save the runtime pattern-matching, + // and we don't need auto_enums to solve multi-branch impl opaque types. + macro_rules! iters_chain { + ($n:literal, $first:ident $(,$rest:ident)*) => {{ + // SAFETY: Type casting is safe after the const generic parameter N is asserted. + let _self_: &mut SlotBkVec<'_, T, $n> = unsafe { ::core::mem::transmute(self) }; + let [$first$(,$rest)*] = _self_.each_mut(); + $first$(.chain($rest))* + }}; + } + // buckets[0].iter_mut().chain(b1.iter_mut()) ... .chain(buckets[N-1].iter_mut()) + match N { + 01 => iters_chain!(01, a), + 02 => iters_chain!(02, a, b), + 03 => iters_chain!(03, a, b, c), + 04 => iters_chain!(04, a, b, c, d), + 05 => iters_chain!(05, a, b, c, d, e), + 06 => iters_chain!(06, a, b, c, d, e, f), + 07 => iters_chain!(07, a, b, c, d, e, f, g), + 08 => iters_chain!(08, a, b, c, d, e, f, g, h), + 09 => iters_chain!(09, a, b, c, d, e, f, g, h, i), + 10 => iters_chain!(10, a, b, c, d, e, f, g, h, i, j), + 11 => iters_chain!(11, a, b, c, d, e, f, g, h, i, j, k), + 12 => iters_chain!(12, a, b, c, d, e, f, g, h, i, j, k, l), + 13 => iters_chain!(13, a, b, c, d, e, f, g, h, i, j, k, l, m), + 14 => iters_chain!(14, a, b, c, d, e, f, g, h, i, j, k, l, m, n), + 15 => iters_chain!(15, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o), + 16 => iters_chain!(16, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p), + _ => ::core::iter::empty(), // unreachable: compile-checking 1 <= N <= 16 + } + } +} + +impl<'a, T, const N: usize> SlotBkVec<'a, T, N> { + /// Creates a slot bucket vec, `Option` is used to mark whether the slot has been used. + pub fn new(slices: [Slice<'a, Option>; N]) -> Self { + #[allow(clippy::let_unit_value)] + let _ = Self::__COMPILER_CHECK__; + Self { + buckets: slices.map(SlotVec::new), + bucket_bits: Self::bucket_bits(), + bucket_mask: Self::bucket_mask(), + } + } + + /// Pushes an element to the back of the bucket slice in the buckets, + /// an element should be generated by the function `elem_fn` calling. + /// + /// Returns None if the bucket number is overflow ( > `N` ), + /// or if the slice is fixed-size (not a `Vec`) and is full. + pub fn push_with(&mut self, bucket: usize, elem_fn: impl FnOnce(usize) -> T) -> Option { + if bucket >= N { + None + } else { + let (bucket_bits, bucket_mask) = (self.bucket_bits, self.bucket_mask); + let mask_fn = + |index| Self::calculate_masked_index(bucket_bits, bucket_mask, index, bucket); + let index = self.buckets[bucket].push_with(|index| { + let index = mask_fn(index); + elem_fn(index) + })?; + Some(self.masked_index(index, bucket)) + } + } + + /// Pushes an element to the back of the bucket slice in the buckets. + /// + /// Returns None if the bucket number is overflow ( > `N` ), + /// or if the slice is fixed-size (not a `Vec`) and is full. + pub fn push(&mut self, bucket: usize, elem: T) -> Option { + if bucket >= N { + None + } else { + let index = self.buckets[bucket].push(elem)?; + Some(self.masked_index(index, bucket)) + } + } + + /// Gets an element from the bucket slice by its index, as immutable. + /// + /// Returns `None` if the index did not refer to a valid element. + pub fn get(&self, index: usize) -> Option<&T> { + let (index, bucket) = self.index_unmask(index); + self.buckets[bucket].get(index) + } + + /// Gets an element from the bucket slice by its index, as mutable. + /// + /// Returns `None` if the index did not refer to a valid element. + pub fn get_mut(&mut self, index: usize) -> Option<&mut T> { + let (index, buket) = self.index_unmask(index); + self.buckets[buket].get_mut(index) + } + + /// Removes an element from the bucket slice, without changing it. + /// + /// Returns the removed element that could be freed if successful, + /// returns `None` if the index did not refer to a valid element. + pub fn remove(&mut self, index: usize) -> Option { + let (index, buket) = self.index_unmask(index); + self.buckets[buket].remove(index) + } + + /// Returns the number of valid elements of all slices of the buckets. + pub fn len(&self) -> usize { + self.iter().count() + } + + /// Returns true if all slices of the buckets contains no valid elements. + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Iterates every element of the bucket slice, as immutable. + /// + /// # Panics + /// This function panics if the bucket number is overflow (>N). + pub fn bucket_iter<'s>(&'s self, bucket: usize) -> impl DoubleEndedIterator { + if bucket >= N { + None + } else { + Some(self.buckets[bucket].iter()) + } + .into_iter() + .flatten() + } + + /// Iterates every element of the bucket slice, as mutable. + /// + /// # Panics + /// This function panics if the bucket number is overflow (>N). + pub fn bucket_iter_mut<'s>( + &'s mut self, + bucket: usize, + ) -> impl DoubleEndedIterator { + if bucket >= N { + None + } else { + Some(self.buckets[bucket].iter_mut()) + } + .into_iter() + .flatten() + } + + /// Iterates every element of all slices of the buckets, as immutable. + pub fn iter<'s>(&'s self) -> impl DoubleEndedIterator { + self.buckets.iter().flat_map(|slice| slice.iter()) + } + + /// Iterates every element of all slices of the buckets, as mutable. + pub fn iter_mut<'s>(&'s mut self) -> impl DoubleEndedIterator { + // NB: In the future we may use a more concise Rust API like this. + // Although it is possible to use the Captures trick here, + // the Captures trick signature will infect the upper APIs, + // so we use array pattern-matching to avoid the Captures trick. + // + // See: https://github.com/rust-lang/rust/issues/34511#issuecomment-373423999 + // pub trait Captures<'a> {} + // impl<'a, T: ?Sized> Captures<'a> for T {} + // + // ``` + // pub fn iter_mut<'s>(&'s mut self) -> impl DoubleEndedIterator + Captures<'a> { + // self.buckets.iter_mut().flat_map(|slice| slice.iter_mut()) + // } + // ``` + + // This won't compile due to opaque type lifetime capture 'a: + // self.buckets.iter_mut().flat_map(|slice| slice.iter_mut()) + self.iter_mut_impl() // using array pattern-matching to solve + } + + /// Borrows each bucket element mutably and returns an array of immutable references. + pub fn each_ref<'s>(&'s self) -> [impl DoubleEndedIterator; N] { + self.buckets.each_ref().map(|x| x.iter()) + } + + /// Borrows each bucket element mutably and returns an array of mutable references. + pub fn each_mut<'s>(&'s mut self) -> [impl DoubleEndedIterator; N] { + self.buckets.each_mut().map(|x| x.iter_mut()) + } +} + +impl<'a, T, const N: usize> Index for SlotBkVec<'a, T, N> { + type Output = T; + + /// Returns a immutable reference to the value corresponding to the supplied index. + /// + /// # Panics + /// + /// Panics if the index is not present in the `SlotBkVec`. + fn index(&self, index: usize) -> &Self::Output { + self.get(index).unwrap() + } +} + +impl<'a, T, const N: usize> IndexMut for SlotBkVec<'a, T, N> { + /// Returns a mutable reference to the value corresponding to the supplied index. + /// + /// # Panics + /// + /// Panics if the index is not present in the `SlotBkVec`. + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + self.get_mut(index).unwrap() + } +} + +#[cfg(test)] +mod tests { + use super::{Slice, SlotBkVec}; + use std::{ + string::{String, ToString}, + vec, + vec::Vec, + }; + + #[test] + fn test_compile_error() { + macro_rules! repeat_owned { + ($n:literal) => { + [0; $n].map(|_| Slice::Owned(vec![])) + }; + } + macro_rules! repeat_borrowed { + ($n:literal) => { + [0; $n].map(|_| Slice::Borrowed(&mut [])) + }; + } + // compile error + // let _ = SlotBkVec::::new([]); + // compile ok + let _ = SlotBkVec::::new(repeat_borrowed!(1)); + let _ = SlotBkVec::::new(repeat_borrowed!(15)); + let _ = SlotBkVec::::new(repeat_owned!(16)); + // compile error + // let _ = SlotBkVec::::new(repeat_owned!(17)); + } + + #[test] + fn simple() { + let (slice1, slice1_bucket) = (vec![], 0); + let (slice2, slice2_bucket) = (vec![], 1); + let (slice3, slice3_bucket) = (vec![], 2); + let mut slot_bkvec: SlotBkVec<&str, 3> = SlotBkVec::new([ + Slice::Owned(slice1), + Slice::Owned(slice2), + Slice::Owned(slice3), + ]); + + // nonexistent buckets, existent buckets: 0,1,2 < 3 + assert_eq!(slot_bkvec.push(3, "hello"), None); + assert_eq!(slot_bkvec.push(4, "world"), None); + + let index = slot_bkvec.push(slice1_bucket, "hello").unwrap(); + assert_eq!(slot_bkvec.get(index), Some("hello").as_ref()); + assert_eq!(slot_bkvec[index], "hello"); + + let elem = slot_bkvec.get_mut(index).unwrap(); + *elem = "world"; + assert_eq!(slot_bkvec.get(index), Some("world").as_ref()); + assert_eq!(slot_bkvec[index], "world"); + + let index = slot_bkvec.push(slice2_bucket, "hello").unwrap(); + assert_eq!(slot_bkvec.get(index), Some("hello").as_ref()); + assert_eq!(slot_bkvec[index], "hello"); + + let index = slot_bkvec.push(slice2_bucket, "jacky").unwrap(); + assert_eq!(slot_bkvec.get(index), Some("jacky").as_ref()); + assert_eq!(slot_bkvec[index], "jacky"); + + let index = slot_bkvec.push(slice3_bucket, "noise").unwrap(); + assert_eq!(slot_bkvec.get(index), Some("noise").as_ref()); + assert_eq!(slot_bkvec[index], "noise"); + + assert_eq!( + slot_bkvec.iter().collect::>(), + [&"world", &"hello", &"jacky", &"noise"] + ); + } + + #[test] + fn retained() { + let (slice1, slice1_bucket) = (vec![], 0); + let (slice2, slice2_bucket) = (vec![], 1); + let mut slot_bkvec: SlotBkVec<&str, 2> = + SlotBkVec::new([Slice::Owned(slice1), Slice::Owned(slice2)]); + + let idx1 = slot_bkvec.push(slice1_bucket, "hello").unwrap(); + let idx2 = slot_bkvec.push(slice2_bucket, "world").unwrap(); + let idx3 = slot_bkvec.push(slice2_bucket, "jacky").unwrap(); + + assert_eq!(slot_bkvec.remove(idx1), Some("hello")); + assert_eq!(slot_bkvec.get(idx1), None); + // nonexistent index, panics + // slot_bkvec[idx1]; + + assert_eq!(slot_bkvec.remove(idx3), Some("jacky")); + assert_eq!(slot_bkvec.get(idx3), None); + + assert_eq!(slot_bkvec.iter().count(), 1); + + assert_eq!(slot_bkvec.remove(idx2), Some("world")); + assert_eq!(slot_bkvec.get(idx2), None); + assert_eq!(slot_bkvec.iter().count(), 0); + + let idx4 = slot_bkvec.push(slice1_bucket, "small").unwrap(); + assert_eq!(slot_bkvec.get(idx4), Some(&"small")); + assert_eq!(slot_bkvec.iter().count(), 1); + } + + #[test] + fn complex() { + let (slice1, slice1_bucket) = (&mut [Default::default(); 1], 0); + let (slice2, slice2_bucket) = (vec![Default::default(); 2], 1); + let mut slot_bkvec: SlotBkVec = + SlotBkVec::new([Slice::Borrowed(slice1), Slice::Owned(slice2)]); + + let index = slot_bkvec.push(slice1_bucket, "hello".to_string()).unwrap(); + assert_eq!(slot_bkvec.get(index), Some("hello".to_string()).as_ref()); + + let elem = slot_bkvec.get_mut(index).unwrap(); + *elem = "world".to_string(); + assert_eq!(slot_bkvec.get(index), Some("world".to_string()).as_ref()); + + // the slice1 is full, the length of the slice1 borrowed is 1. + assert_eq!(slot_bkvec.push(slice1_bucket, "messy".to_string()), None); + + let index = slot_bkvec.push(slice2_bucket, "hello".to_string()).unwrap(); + assert_eq!(slot_bkvec.get(index), Some("hello".to_string()).as_ref()); + + let index = slot_bkvec.push(slice2_bucket, "jacky".to_string()).unwrap(); + assert_eq!(slot_bkvec.get(index), Some("jacky".to_string()).as_ref()); + + // Even though the slice2 is 2 in length, + // it can still be pushed because it is an owned vector. + assert!(slot_bkvec + .push(slice2_bucket, "noise".to_string()) + .is_some()); + + let index = slot_bkvec + .push_with(slice2_bucket, |index| index.to_string()) + .unwrap(); + let rusty = index.to_string(); + + assert_eq!( + slot_bkvec + .bucket_iter(slice1_bucket) + .collect::>(), + [&"world"] + ); + + assert_eq!( + slot_bkvec + .bucket_iter(slice2_bucket) + .collect::>(), + [&"hello", &"jacky", &"noise", &rusty.as_str()] + ); + + assert_eq!( + slot_bkvec.iter().collect::>(), + [&"world", &"hello", &"jacky", &"noise", &rusty.as_str()] + ); + } +} diff --git a/src/slotdeque.rs b/src/slotdeque.rs new file mode 100644 index 0000000..9d1e494 --- /dev/null +++ b/src/slotdeque.rs @@ -0,0 +1,281 @@ +//! A slotdeque, a vector-deque-like container with unique indices. +//! +//! See the documentation of [`SlotDeque`] for details. +//! +//! [`SlotDeque`]: struct.SlotDeque.html +use core::ops::{Index, IndexMut}; + +use super::{ManagedSlice as Slice, SlotBkVec}; + +/// Provides a slotdeque based on external memory. +/// +/// A slotdeque provides a `VecDeque`-like interface where each entry is +/// associated with a stable index. Lookup with the index will detect if +/// an entry has been removed but does not require a lifetime relation. +/// It will allocate memory internally when adding elements to slotdeque +/// if the [`Slice`] argument to the constructor is a [`Slice::Owned`] Vec. +/// It only replaces the Option element value with the value that actually +/// if the [`Slice`] argument to the constructor is a [`Slice::Borrowed`] slice. +/// +/// [`push_front`] is logically like [`VecDeque::push_front`], +/// [`push_back`] is logically like [`VecDeque::push_back`]. +/// +/// [`VecDeque::push_front`]: std::collections::VecDeque::push_front +/// [`VecDeque::push_back`]: std::collections::VecDeque::push_back +#[derive(Debug)] +pub struct SlotDeque<'a, T> { + /// The first bucket slice is logically similar to the front slice of + /// the [`VecDeque`] after being reversed, and the second bucket slice + /// directly is like the back slice of the [`VecDeque`]. + /// + /// Pushes on the first bucket slice is equivalent to [`VecDeque::push_front`], + /// and pushes on the second bucket slice is equivalent to [`VecDeque::push_back`]. + /// + /// [`VecDeque`]: std::collections::VecDeque + /// [`VecDeque::push_front`]: std::collections::VecDeque::push_front + /// [`VecDeque::push_back`]: std::collections::VecDeque::push_back + slices: SlotBkVec<'a, T, 2>, +} + +impl<'a, T> SlotDeque<'a, T> { + const BUCKET_FRONT: usize = 0; + const BUCKET_BACK: usize = 1; + + /// Creates a slot deque, `Option` is used to mark whether the slot has been used. + pub fn new(front: Slice<'a, Option>, back: Slice<'a, Option>) -> Self { + Self { + slices: SlotBkVec::new([front, back]), + } + } + + /// Prepends an element to the front of deque. + /// + /// Returns None if the front slice is fixed-size (not a `Vec`) and is full. + pub fn push_front_with(&mut self, f: impl FnOnce(usize) -> T) -> Option { + self.slices.push_with(Self::BUCKET_FRONT, f) + } + + /// Prepends an element to the front of deque. + /// + /// Returns None if the front slice is fixed-size (not a `Vec`) and is full. + pub fn push_front(&mut self, elem: T) -> Option { + self.slices.push(Self::BUCKET_FRONT, elem) + } + + /// Appends an element to the back of deque. + /// + /// Returns None if the back slice is fixed-size (not a `Vec`) and is full. + pub fn push_back_with(&mut self, f: impl FnOnce(usize) -> T) -> Option { + self.slices.push_with(Self::BUCKET_BACK, f) + } + + /// Appends an element to the back of the deque. + /// + /// Returns None if the back slice is fixed-size (not a `Vec`) and is full. + pub fn push_back(&mut self, elem: T) -> Option { + self.slices.push(Self::BUCKET_BACK, elem) + } + + /// Gets an element from the deque by its index, as immutable. + /// + /// Returns `None` if the index did not refer to a valid element. + pub fn get(&self, index: usize) -> Option<&T> { + self.slices.get(index) + } + + /// Gets an element from the deque by its index, as mutable. + /// + /// Returns `None` if the index did not refer to a valid element. + pub fn get_mut(&mut self, index: usize) -> Option<&mut T> { + self.slices.get_mut(index) + } + + /// Removes an element from the deque, without changing it. + /// + /// Returns the removed element that could be freed if successful, + /// returns `None` if the index did not refer to a valid element. + pub fn remove(&mut self, index: usize) -> Option { + self.slices.remove(index) + } + + /// Returns the number of valid elements in the deque. + pub fn len(&self) -> usize { + self.iter().count() + } + + /// Returns true if the deque contains no valid elements. + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Iterates every element in the front of this deque, as immutable. + pub fn front_iter<'s>(&'s self) -> impl DoubleEndedIterator { + self.slices.bucket_iter(Self::BUCKET_FRONT).rev() + } + + /// Iterates every element in the front of this deque, as mutable. + pub fn front_iter_mut<'s>(&'s mut self) -> impl DoubleEndedIterator { + self.slices.bucket_iter_mut(Self::BUCKET_FRONT).rev() + } + + /// Iterates every element in the back of this deque, as immutable. + pub fn back_iter<'s>(&'s self) -> impl DoubleEndedIterator { + self.slices.bucket_iter(Self::BUCKET_BACK) + } + + /// Iterates every element in the back of this deque, as mutable. + pub fn back_iter_mut<'s>(&'s mut self) -> impl DoubleEndedIterator { + self.slices.bucket_iter_mut(Self::BUCKET_BACK) + } + + /// Iterates every element of this deque, as immutable. + pub fn iter<'s>(&'s self) -> impl DoubleEndedIterator { + // Compile ok because that borrow immutable on self. + // self.front_iter().chain(self.back_iter()) + let [front, back] = self.slices.each_ref(); + front.rev().chain(back) + } + + /// Iterates every element of this deque, as mutable. + pub fn iter_mut<'s>(&'s mut self) -> impl DoubleEndedIterator { + // Compile error because that borrow twice mutable on self. + // self.front_iter_mut().chain(self.back_iter_mut()) + let [front, back] = self.slices.each_mut(); + front.rev().chain(back) + } +} + +impl<'a, T> Index for SlotDeque<'a, T> { + type Output = T; + + /// Returns a immutable reference to the value corresponding to the supplied index. + /// + /// # Panics + /// + /// Panics if the index is not present in the `SlotDeque`. + fn index(&self, index: usize) -> &Self::Output { + self.get(index).unwrap() + } +} + +impl<'a, T> IndexMut for SlotDeque<'a, T> { + /// Returns a mutable reference to the value corresponding to the supplied index. + /// + /// # Panics + /// + /// Panics if the index is not present in the `SlotDeque`. + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + self.get_mut(index).unwrap() + } +} + +#[cfg(test)] +mod tests { + use super::{Slice, SlotDeque}; + use std::{ + string::{String, ToString}, + vec, + vec::Vec, + }; + + #[test] + fn simple() { + let front = Slice::Owned(vec![]); + let back = Slice::Owned(vec![]); + let mut slot_deque: SlotDeque<&str> = SlotDeque::new(front, back); + + let index = slot_deque.push_front("hello").unwrap(); + assert_eq!(slot_deque.get(index), Some(&"hello")); + assert_eq!(slot_deque[index], "hello"); + let index = slot_deque.push_front("world").unwrap(); + assert_eq!(slot_deque.get(index), Some(&"world")); + assert_eq!(slot_deque[index], "world"); + + let index = slot_deque.push_back("jacky").unwrap(); + assert_eq!(slot_deque.get(index), Some(&"jacky")); + assert_eq!(slot_deque[index], "jacky"); + let index = slot_deque.push_back("messy").unwrap(); + assert_eq!(slot_deque.get(index), Some(&"messy")); + assert_eq!(slot_deque[index], "messy"); + + assert_eq!( + slot_deque.front_iter().collect::>(), + [&"world", &"hello"] + ); + + assert_eq!( + slot_deque.back_iter().collect::>(), + [&"jacky", &"messy"] + ); + + assert_eq!( + slot_deque.iter().collect::>(), + [&"world", &"hello", &"jacky", &"messy"] + ); + } + + #[test] + fn retained() { + let front = Slice::Owned(vec![]); + let back = Slice::Owned(vec![]); + let mut slot_deque: SlotDeque<&str> = SlotDeque::new(front, back); + + let idx1 = slot_deque.push_front("hello").unwrap(); + let idx2 = slot_deque.push_front("world").unwrap(); + let idx3 = slot_deque.push_front("jacky").unwrap(); + + assert_eq!(slot_deque.remove(idx1), Some("hello")); + assert_eq!(slot_deque.get(idx1), None); + // nonexistent index, panics + // slot_deque[idx1]; + + assert_eq!(slot_deque.remove(idx3), Some("jacky")); + assert_eq!(slot_deque.get(idx3), None); + + assert_eq!(slot_deque.iter().count(), 1); + + assert_eq!(slot_deque.remove(idx2), Some("world")); + assert_eq!(slot_deque.get(idx2), None); + assert_eq!(slot_deque.iter().count(), 0); + + let idx4 = slot_deque.push_back("small").unwrap(); + assert_eq!(slot_deque.get(idx4), Some(&"small")); + assert_eq!(slot_deque.iter().count(), 1); + } + + #[test] + fn complex() { + let front = &mut [Default::default(); 1]; + let back = vec![Default::default(); 2]; + let mut slot_deque: SlotDeque = + SlotDeque::new(Slice::Borrowed(front), Slice::Owned(back)); + + let index = slot_deque.push_front("hello".to_string()).unwrap(); + assert_eq!(slot_deque.get(index).map(|x| x.as_str()), Some("hello")); + + let elem = slot_deque.get_mut(index).unwrap(); + *elem = "world".to_string(); + assert_eq!(slot_deque.get(index).map(|x| x.as_str()), Some("world")); + + // the front slice is full, the length of the front slice borrowed is 1. + assert_eq!(slot_deque.push_front("messy".to_string()), None); + + let index = slot_deque.push_back("hello".to_string()).unwrap(); + assert_eq!(slot_deque.get(index).map(|x| x.as_str()), Some("hello")); + + let index = slot_deque.push_back("jacky".to_string()).unwrap(); + assert_eq!(slot_deque.get(index).map(|x| x.as_str()), Some("jacky")); + + // Even though the back slice is 2 in length, + // it can still be pushed because it is an owned vector. + let noise_index = slot_deque + .push_back_with(|index| index.to_string()) + .unwrap(); + let noise = noise_index.to_string(); + + assert_eq!( + slot_deque.iter().collect::>(), + [&"world", &"hello", &"jacky", &noise.as_str()] + ); + } +} diff --git a/src/slotvec.rs b/src/slotvec.rs new file mode 100644 index 0000000..cb9d1d3 --- /dev/null +++ b/src/slotvec.rs @@ -0,0 +1,263 @@ +//! A slotvec, a vector-like container with unique indices. +//! +//! See the documentation of [`SlotVec`] for details. +//! +//! [`SlotVec`]: struct.SlotVec.html +use core::ops::{Index, IndexMut}; + +use super::ManagedSlice as Slice; + +/// Provides a slotvec based on external memory. +/// +/// Provides mappings between slots and elements. +/// +/// A slotvec provides a vector(`Vec`)-like interface where each entry is +/// associated with a stable index. Lookup with the index will detect if +/// an entry has been removed but does not require a lifetime relation. +/// It will allocate memory internally when adding elements to slotvec +/// if the [`Slice`] argument to the constructor is a [`Slice::Owned`] Vec. +/// It only replaces the Option element value with the value that actually +/// if the [`Slice`] argument to the constructor is a [`Slice::Borrowed`] slice. +#[derive(Debug)] +pub struct SlotVec<'a, T> { + /// An element: Option\ represents: + /// - index -> Some(T) => There is a valid value at index. + /// - index -> None => There is no or a freed value at index. + slice: Slice<'a, Option>, + /// The forward valid free slice index. + free_top_index: Option, +} + +impl<'a, T> SlotVec<'a, T> { + fn next_free_top_index(&mut self, skip_count: usize) { + for (index, slot) in self.slice.iter().enumerate().skip(skip_count) { + if slot.is_none() { + self.free_top_index.replace(index); + return; // break loop if next found + } + } + } +} + +impl<'a, T> SlotVec<'a, T> { + /// Creates a slot vec, `Option` is used to mark whether the slot has been used. + pub fn new(slice: Slice<'a, Option>) -> Self { + let mut slot_vec = Self { + slice, + free_top_index: None, + }; + slot_vec.next_free_top_index(0); + slot_vec + } + + /// Pushes an element to the back of the slice in the vec, + /// an element should be generated by the function `elem_fn` calling. + /// + /// Returns None if the slice is fixed-size (not a owned `Vec`) and is full. + #[inline] + pub fn push_with(&mut self, elem_fn: impl FnOnce(usize) -> T) -> Option { + // The removed elements will always be queried by this logic, + // because free_top_index is always set in the remove function. + if let Some(index) = self.free_top_index.take() { + self.slice[index].replace(elem_fn(index)); + // Since none of the element slots before free_top_index are available, + // we simply think the next free top start from the free_top_index next. + self.next_free_top_index(index + 1); + return Some(index); + } + // If no element has been removed, the following code logic + // will always be executed, possibly new allocating on owned. + match &mut self.slice { + Slice::Borrowed(_) => None, + #[cfg(any(feature = "std", feature = "alloc"))] + Slice::Owned(slice) => { + slice.push(None); // placeholder + let index = slice.len() - 1; + // don't do this set because we can't know + // for sure it will be indexed successfully. + // self.free_top_index.replace(slice.len()); + self.slice[index].replace(elem_fn(index)); + Some(index) + } + } + } + + /// Pushes an element to the back in the vec. + /// + /// Returns None if the slice is fixed-size (not a `Vec`) and is full. + pub fn push(&mut self, elem: T) -> Option { + self.push_with(|_| elem) + } + + /// Gets an element from the vec by its index, as immutable. + /// + /// Returns `None` if the index did not refer to a valid element. + pub fn get(&self, index: usize) -> Option<&T> { + self.slice.get(index)?.as_ref() + } + + /// Gets an element from the vec by its index, as mutable. + /// + /// Returns `None` if the index did not refer to a valid element. + pub fn get_mut(&mut self, index: usize) -> Option<&mut T> { + self.slice.get_mut(index)?.as_mut() + } + + /// Removes an element from the vec, without changing it. + /// + /// Returns the removed element that could be freed if successful, + /// returns `None` if the index did not refer to a valid element. + pub fn remove(&mut self, index: usize) -> Option { + let removed = self.slice.get_mut(index)?.take()?; + self.free_top_index = match self.free_top_index { + Some(old_index) if index < old_index => Some(index), + Some(old_index) => Some(old_index), + None => Some(index), + }; + Some(removed) + } + + /// Returns the number of valid elements in the vec. + pub fn len(&self) -> usize { + self.iter().count() + } + + /// Returns true if the vec contains no valid elements. + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Iterates every element of this vec, as immutable. + pub fn iter<'s>(&'s self) -> impl DoubleEndedIterator { + self.slice.iter().filter_map(|slot| slot.as_ref()) + } + + /// Iterates every element of this vec, as mutable. + pub fn iter_mut<'s>(&'s mut self) -> impl DoubleEndedIterator { + self.slice.iter_mut().filter_map(|slot| slot.as_mut()) + } +} + +impl<'a, T> Index for SlotVec<'a, T> { + type Output = T; + + /// Returns a immutable reference to the value corresponding to the supplied index. + /// + /// # Panics + /// + /// Panics if the index is not present in the `SlotVec`. + fn index(&self, index: usize) -> &Self::Output { + self.get(index).unwrap() + } +} + +impl<'a, T> IndexMut for SlotVec<'a, T> { + /// Returns a mutable reference to the value corresponding to the supplied index. + /// + /// # Panics + /// + /// Panics if the index is not present in the `SlotVec`. + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + self.get_mut(index).unwrap() + } +} + +#[cfg(test)] +mod tests { + use super::{Slice, SlotVec}; + use std::{ + format, + string::{String, ToString}, + vec, + vec::Vec, + }; + + #[test] + fn simple() { + let mut slot_vec: SlotVec<&str> = SlotVec::new(Slice::Owned(vec![])); + + let index = slot_vec.push("hello").unwrap(); + assert_eq!(slot_vec.get(index), Some(&"hello")); + assert_eq!(slot_vec[index], "hello"); + let index = slot_vec.push("world").unwrap(); + assert_eq!(slot_vec.get(index), Some(&"world")); + assert_eq!(slot_vec[index], "world"); + + assert_eq!( + slot_vec.iter().collect::>(), + [&"hello", &"world"] + ); + } + + #[test] + fn retained() { + let mut slot_vec: SlotVec<&str> = SlotVec::new(Slice::Owned(vec![])); + + let idx1 = slot_vec.push("hello").unwrap(); + let idx2 = slot_vec.push("world").unwrap(); + let idx3 = slot_vec.push("jacky").unwrap(); + + assert_eq!(slot_vec.remove(idx1), Some("hello")); + assert_eq!(slot_vec.get(idx1), None); + // nonexistent index, panics + // slot_vec[idx1]; + + assert_eq!(slot_vec.remove(idx3), Some("jacky")); + assert_eq!(slot_vec.get(idx3), None); + + assert_eq!(slot_vec.iter().count(), 1); + + assert_eq!(slot_vec.remove(idx2), Some("world")); + assert_eq!(slot_vec.get(idx2), None); + assert_eq!(slot_vec.iter().count(), 0); + + // The free top index is the one with the smallest index in the deletion. + assert_eq!(slot_vec.free_top_index, Some(idx1)); + + let idx4 = slot_vec.push("small").unwrap(); + assert_eq!(slot_vec.get(idx4), Some(&"small")); + assert_eq!(slot_vec.iter().count(), 1); + + // The last push will be placed at idx1 in the slotvec, because idx1 is removed, + // and the free top becomes the one with the smallest deleted index, which is idx2. + assert_eq!(idx1, idx4); + assert_eq!(slot_vec.free_top_index, Some(idx2)); + } + + #[test] + fn complex() { + let slice = &mut [Default::default(); 1]; + let mut slot_vec: SlotVec = SlotVec::new(Slice::Borrowed(slice)); + + let index = slot_vec.push("hello".to_string()).unwrap(); + assert_eq!(slot_vec.get(index).map(|x| x.as_str()), Some("hello")); + + let elem = slot_vec.get_mut(index).unwrap(); + *elem = "world".to_string(); + assert_eq!(slot_vec.get(index).map(|x| x.as_str()), Some("world")); + + // the front slice is full, the length of the front slice borrowed is 1. + assert_eq!(slot_vec.push("messy".to_string()), None); + + let slice = vec![Default::default(); 2]; + let mut slot_vec: SlotVec = SlotVec::new(Slice::Owned(slice)); + + let index = slot_vec.push("hello".to_string()).unwrap(); + assert_eq!(slot_vec.get(index).map(|x| x.as_str()), Some("hello")); + + let index = slot_vec.push("jacky".to_string()).unwrap(); + assert_eq!(slot_vec.get(index).map(|x| x.as_str()), Some("jacky")); + + // Even though the back slice is 2 in length, + // it can still be pushed because it is an owned vector. + let index = slot_vec + .push_with(|index| format!("custom element with index {}", index)) + .unwrap(); + let custom = format!("custom element with index {}", index); + + assert_eq!( + slot_vec.iter().collect::>(), + [&"hello", &"jacky", &custom.as_str()] + ); + } +}