usecrate::ule::AsULE; usecrate::ZeroVec; use alloc::borrow::Borrow; use core::cmp::Ordering; use core::convert::TryFrom; use core::fmt; use core::iter::FromIterator; use core::ops::Range;
/// A zero-copy, two-dimensional map datastructure . /// /// This is an extension of [`ZeroMap`] that supports two layers of keys. For example, /// to map a pair of an integer and a string to a buffer, you can write: /// /// ```no_run /// # use zerovec::ZeroMap2d; /// let _: ZeroMap2d<u32, str, [u8]> = unimplemented!(); /// ``` /// /// Internally, `ZeroMap2d` stores four zero-copy vectors, one for each type argument plus /// one more to match between the two vectors of keys. /// /// # Examples /// /// ``` /// use zerovec::ZeroMap2d; /// /// // Example byte buffer representing the map { 1: {2: "three" } } /// let BINCODE_BYTES: &[u8; 51] = &[ /// 2, 0, 0, 0, 0, 0, 0, 0, 1, 0, 4, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, /// 0, 0, 0, 0, 0, 0, 2, 0, 11, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 116, /// 104, 114, 101, 101, /// ]; /// /// // Deserializing to ZeroMap requires no heap allocations. /// let zero_map: ZeroMap2d<u16, u16, str> = /// bincode::deserialize(BINCODE_BYTES) /// .expect("Should deserialize successfully"); /// assert_eq!(zero_map.get_2d(&1, &2), Some("three")); /// ``` /// /// [`VarZeroVec`]: crate::VarZeroVec /// [`ZeroMap`]: crate::ZeroMap // ZeroMap2d contains 4 fields: // // - keys0 = sorted list of all K0 in the map // - joiner = helper vec that maps from a K0 to a range of keys1 // - keys1 = list of all K1 in the map, sorted in ranges for each K0 // - values = list of all values in the map, sorted by (K0, K1) // // For a particular K0 at index i, the range of keys1 corresponding to K0 is // (joiner[i-1]..joiner[i]), where the first range starts at 0. // // Required Invariants: // // 1. len(keys0) == len(joiner) // 2. len(keys1) == len(values) // 3. joiner is sorted // 4. the last element of joiner is the length of keys1 // // Optional Invariants: // // 5. keys0 is sorted (for binary_search) // 6. ranges within keys1 are sorted (for binary_search) // 7. every K0 is associated with at least one K1 (no empty ranges) // // During deserialization, these three invariants are not checked, because they put the // ZeroMap2d in a deterministic state, even though it may have unexpected behavior. pubstruct ZeroMap2d<'a, K0, K1, V> where
K0: ZeroMapKV<'a>,
K1: ZeroMapKV<'a>,
V: ZeroMapKV<'a>,
K0: ?Sized,
K1: ?Sized,
V: ?Sized,
{ pub(crate) keys0: K0::Container, pub(crate) joiner: ZeroVec<'a, u32>, pub(crate) keys1: K1::Container, pub(crate) values: V::Container,
}
/// Construct a new [`ZeroMap2d`] with a given capacity pubfn with_capacity(capacity: usize) -> Self { Self {
keys0: K0::Container::zvl_with_capacity(capacity),
joiner: ZeroVec::with_capacity(capacity),
keys1: K1::Container::zvl_with_capacity(capacity),
values: V::Container::zvl_with_capacity(capacity),
}
}
/// Obtain a borrowed version of this map pubfn as_borrowed(&'a self) -> ZeroMap2dBorrowed<'a, K0, K1, V> {
ZeroMap2dBorrowed {
keys0: self.keys0.zvl_as_borrowed(),
joiner: &self.joiner,
keys1: self.keys1.zvl_as_borrowed(),
values: self.values.zvl_as_borrowed(),
}
}
/// The number of values in the [`ZeroMap2d`] pubfn len(&self) -> usize { self.values.zvl_len()
}
/// Whether the [`ZeroMap2d`] is empty pubfn is_empty(&self) -> bool { self.values.zvl_len() == 0
}
/// Remove all elements from the [`ZeroMap2d`] pubfn clear(&mutself) { self.keys0.zvl_clear(); self.joiner.clear(); self.keys1.zvl_clear(); self.values.zvl_clear();
}
/// Reserve capacity for `additional` more elements to be inserted into /// the [`ZeroMap2d`] to avoid frequent reallocations. /// /// See [`Vec::reserve()`](alloc::vec::Vec::reserve) for more information. pubfn reserve(&mutself, additional: usize) { self.keys0.zvl_reserve(additional); self.joiner.zvl_reserve(additional); self.keys1.zvl_reserve(additional); self.values.zvl_reserve(additional);
}
/// Produce an ordered iterator over keys0, which can then be used to get an iterator /// over keys1 for a particular key0. /// /// # Example /// /// Loop over all elements of a ZeroMap2d: /// /// ``` /// use zerovec::ZeroMap2d; /// /// let mut map: ZeroMap2d<u16, u16, str> = ZeroMap2d::new(); /// map.insert(&1, &1, "foo"); /// map.insert(&2, &3, "bar"); /// map.insert(&2, &4, "baz"); /// /// let mut total_value = 0; /// /// for cursor in map.iter0() { /// for (key1, value) in cursor.iter1() { /// // This code runs for every (key0, key1) pair /// total_value += cursor.key0().as_unsigned_int() as usize; /// total_value += key1.as_unsigned_int() as usize; /// total_value += value.len(); /// } /// } /// /// assert_eq!(total_value, 22); /// ``` pubfn iter0<'l>(&'l self) -> impl Iterator<Item = ZeroMap2dCursor<'l, 'a, K0, K1, V>> + 'l {
(0..self.keys0.zvl_len()).map(move |idx| ZeroMap2dCursor::from_cow(self, idx))
}
// INTERNAL ROUTINES FOLLOW //
/// Given an index into the joiner array, returns the corresponding range of keys1 fn get_range_for_key0_index(&self, key0_index: usize) -> Range<usize> {
ZeroMap2dCursor::from_cow(self, key0_index).get_range()
}
/// Removes key0_index from the keys0 array and the joiner array fn remove_key0_index(&mutself, key0_index: usize) { self.keys0.zvl_remove(key0_index); self.joiner.with_mut(|v| v.remove(key0_index));
}
/// Shifts all joiner ranges from key0_index onward one index up fn joiner_expand(&mutself, key0_index: usize) { #[allow(clippy::expect_used)] // slice overflow self.joiner
.to_mut_slice()
.iter_mut()
.skip(key0_index)
.for_each(|refmut v| { // TODO(#1410): Make this fallible
**v = v
.as_unsigned_int()
.checked_add(1)
.expect("Attempted to add more than 2^32 elements to a ZeroMap2d")
.to_unaligned()
})
}
/// Shifts all joiner ranges from key0_index onward one index down fn joiner_shrink(&mutself, key0_index: usize) { self.joiner
.to_mut_slice()
.iter_mut()
.skip(key0_index)
.for_each(|refmut v| **v = (v.as_unsigned_int() - 1).to_unaligned())
}
}
impl<'a, K0, K1, V> ZeroMap2d<'a, K0, K1, V> where
K0: ZeroMapKV<'a> + Ord,
K1: ZeroMapKV<'a> + Ord,
V: ZeroMapKV<'a>,
K0: ?Sized,
K1: ?Sized,
V: ?Sized,
{ /// Get the value associated with `key0` and `key1`, if it exists. /// /// For more fine-grained error handling, use [`ZeroMap2d::get0`]. /// /// ```rust /// use zerovec::ZeroMap2d; /// /// let mut map = ZeroMap2d::new(); /// map.insert(&1, "one", "foo"); /// map.insert(&2, "one", "bar"); /// map.insert(&2, "two", "baz"); /// assert_eq!(map.get_2d(&1, "one"), Some("foo")); /// assert_eq!(map.get_2d(&1, "two"), None); /// assert_eq!(map.get_2d(&2, "one"), Some("bar")); /// assert_eq!(map.get_2d(&2, "two"), Some("baz")); /// assert_eq!(map.get_2d(&3, "three"), None); /// ``` pubfn get_2d(&self, key0: &K0, key1: &K1) -> Option<&V::GetType> { self.get0(key0)?.get1(key1)
}
/// Insert `value` with `key`, returning the existing value if it exists. /// /// ```rust /// use zerovec::ZeroMap2d; /// /// let mut map = ZeroMap2d::new(); /// assert_eq!(map.insert(&0, "zero", "foo"), None,); /// assert_eq!(map.insert(&1, "one", "bar"), None,); /// assert_eq!(map.insert(&1, "one", "baz").as_deref(), Some("bar"),); /// assert_eq!(map.get_2d(&1, "one").as_deref(), Some("baz")); /// assert_eq!(map.len(), 2); /// ``` pubfn insert(&mutself, key0: &K0, key1: &K1, value: &V) -> Option<V::OwnedType> { let (key0_index, range) = self.get_or_insert_range_for_key0(key0);
debug_assert!(range.start <= range.end); // '<=' because we may have inserted a new key0
debug_assert!(range.end <= self.keys1.zvl_len()); let range_start = range.start; #[allow(clippy::unwrap_used)] // by debug_assert! invariants let index = range_start
+ matchself.keys1.zvl_binary_search_in_range(key1, range).unwrap() {
Ok(index) => return Some(self.values.zvl_replace(range_start + index, value)),
Err(index) => index,
}; self.keys1.zvl_insert(index, key1); self.values.zvl_insert(index, value); self.joiner_expand(key0_index); #[cfg(debug_assertions)] self.check_invariants();
None
}
/// Remove the value at `key`, returning it if it exists. /// /// ```rust /// use zerovec::ZeroMap2d; /// /// let mut map = ZeroMap2d::new(); /// map.insert(&1, "one", "foo"); /// map.insert(&2, "two", "bar"); /// assert_eq!( /// map.remove(&1, "one"), /// Some("foo".to_owned().into_boxed_str()) /// ); /// assert_eq!(map.get_2d(&1, "one"), None); /// assert_eq!(map.remove(&1, "one"), None); /// ``` pubfn remove(&mutself, key0: &K0, key1: &K1) -> Option<V::OwnedType> { let key0_index = self.keys0.zvl_binary_search(key0).ok()?; let range = self.get_range_for_key0_index(key0_index);
debug_assert!(range.start < range.end); // '<' because every key0 should have a key1
debug_assert!(range.end <= self.keys1.zvl_len()); let is_singleton_range = range.start + 1 == range.end; #[allow(clippy::unwrap_used)] // by debug_assert invariants let index = range.start
+ self
.keys1
.zvl_binary_search_in_range(key1, range)
.unwrap()
.ok()?; self.keys1.zvl_remove(index); let removed = self.values.zvl_remove(index); self.joiner_shrink(key0_index); if is_singleton_range { self.remove_key0_index(key0_index);
} #[cfg(debug_assertions)] self.check_invariants();
Some(removed)
}
/// Appends `value` with `key` to the end of the underlying vector, returning /// `key` and `value` _if it failed_. Useful for extending with an existing /// sorted list. /// /// ```rust /// use zerovec::ZeroMap2d; /// /// let mut map = ZeroMap2d::new(); /// assert!(map.try_append(&1, "one", "uno").is_none()); /// assert!(map.try_append(&3, "three", "tres").is_none()); /// /// let unsuccessful = map.try_append(&3, "three", "tres-updated"); /// assert!(unsuccessful.is_some(), "append duplicate of last key"); /// /// let unsuccessful = map.try_append(&2, "two", "dos"); /// assert!(unsuccessful.is_some(), "append out of order"); /// /// assert_eq!(map.get_2d(&1, "one"), Some("uno")); /// /// // contains the original value for the key: 3 /// assert_eq!(map.get_2d(&3, "three"), Some("tres")); /// /// // not appended since it wasn't in order /// assert_eq!(map.get_2d(&2, "two"), None); /// ``` #[must_use] pubfn try_append<'b>(
&mutself,
key0: &'b K0,
key1: &'b K1,
value: &'b V,
) -> Option<(&'b K0, &'b K1, &'b V)> { ifself.is_empty() { self.keys0.zvl_push(key0); self.joiner.with_mut(|v| v.push(1u32.to_unaligned())); self.keys1.zvl_push(key1); self.values.zvl_push(value); return None;
}
// The unwraps are protected by the fact that we are not empty #[allow(clippy::unwrap_used)] let last_key0 = self.keys0.zvl_get(self.keys0.zvl_len() - 1).unwrap(); let key0_cmp = K0::Container::t_cmp_get(key0, last_key0); #[allow(clippy::unwrap_used)] let last_key1 = self.keys1.zvl_get(self.keys1.zvl_len() - 1).unwrap(); let key1_cmp = K1::Container::t_cmp_get(key1, last_key1);
// Check for error case (out of order) match key0_cmp {
Ordering::Less => { // Error case return Some((key0, key1, value));
}
Ordering::Equal => { match key1_cmp {
Ordering::Less | Ordering::Equal => { // Error case return Some((key0, key1, value));
}
_ => {}
}
}
_ => {}
}
#[allow(clippy::expect_used)] // slice overflow let joiner_value = u32::try_from(self.keys1.zvl_len() + 1)
.expect("Attempted to add more than 2^32 elements to a ZeroMap2d");
// All OK to append #[allow(clippy::unwrap_used)] if key0_cmp == Ordering::Greater { self.keys0.zvl_push(key0); self.joiner
.with_mut(|v| v.push(joiner_value.to_unaligned()));
} else { // This unwrap is protected because we are not empty
*self.joiner.to_mut_slice().last_mut().unwrap() = joiner_value.to_unaligned();
} self.keys1.zvl_push(key1); self.values.zvl_push(value);
#[cfg(debug_assertions)] self.check_invariants();
None
}
// INTERNAL ROUTINES FOLLOW //
#[cfg(debug_assertions)] #[allow(clippy::unwrap_used)] // this is an assertion function pub(crate) fn check_invariants(&self) {
debug_assert_eq!(self.keys0.zvl_len(), self.joiner.len());
debug_assert_eq!(self.keys1.zvl_len(), self.values.zvl_len());
debug_assert!(self.keys0.zvl_is_ascending());
debug_assert!(self.joiner.zvl_is_ascending()); iflet Some(last_joiner) = self.joiner.last() {
debug_assert_eq!(last_joiner as usize, self.keys1.zvl_len());
} for i in0..self.joiner.len() { let j0 = if i == 0 { 0
} else { self.joiner.get(i - 1).unwrap() as usize
}; let j1 = self.joiner.get(i).unwrap() as usize;
debug_assert_ne!(j0, j1); for j in (j0 + 1)..j1 { let m0 = self.keys1.zvl_get(j - 1).unwrap(); let m1 = self.keys1.zvl_get(j).unwrap();
debug_assert_eq!(Ordering::Less, K1::Container::get_cmp_get(m0, m1));
}
}
}
}
impl<'a, K0, K1, V> ZeroMap2d<'a, K0, K1, V> where
K0: ZeroMapKV<'a> + Ord,
K1: ZeroMapKV<'a>,
V: ZeroMapKV<'a>,
K0: ?Sized,
K1: ?Sized,
V: ?Sized,
{ /// Gets a cursor for `key0`. If `None`, then `key0` is not in the map. If `Some`, /// then `key0` is in the map, and `key1` can be queried. /// /// ```rust /// use zerovec::ZeroMap2d; /// /// let mut map = ZeroMap2d::new(); /// map.insert(&1u32, "one", "foo"); /// map.insert(&2, "one", "bar"); /// map.insert(&2, "two", "baz"); /// assert_eq!(map.get0(&1).unwrap().get1("one").unwrap(), "foo"); /// assert_eq!(map.get0(&1).unwrap().get1("two"), None); /// assert_eq!(map.get0(&2).unwrap().get1("one").unwrap(), "bar"); /// assert_eq!(map.get0(&2).unwrap().get1("two").unwrap(), "baz"); /// assert_eq!(map.get0(&3), None); /// ``` #[inline] pubfn get0<'l>(&'l self, key0: &K0) -> Option<ZeroMap2dCursor<'l, 'a, K0, K1, V>> { let key0_index = self.keys0.zvl_binary_search(key0).ok()?;
Some(ZeroMap2dCursor::from_cow(self, key0_index))
}
// We can't use the default PartialEq because ZeroMap2d is invariant // so otherwise rustc will not automatically allow you to compare ZeroMaps // with different lifetimes impl<'a, 'b, K0, K1, V> PartialEq<ZeroMap2d<'b, K0, K1, V>> for ZeroMap2d<'a, K0, K1, V> where
K0: for<'c> ZeroMapKV<'c> + ?Sized,
K1: for<'c> ZeroMapKV<'c> + ?Sized,
V: for<'c> ZeroMapKV<'c> + ?Sized,
<K0 as ZeroMapKV<'a>>::Container: PartialEq<<K0 as ZeroMapKV<'b>>::Container>,
<K1 as ZeroMapKV<'a>>::Container: PartialEq<<K1 as ZeroMapKV<'b>>::Container>,
<V as ZeroMapKV<'a>>::Container: PartialEq<<V as ZeroMapKV<'b>>::Container>,
{ fn eq(&self, other: &ZeroMap2d<'b, K0, K1, V>) -> bool { self.keys0.eq(&other.keys0)
&& self.joiner.eq(&other.joiner)
&& self.keys1.eq(&other.keys1)
&& self.values.eq(&other.values)
}
}
// Out of order let result = zm2d.try_append(&3, "ddd", "DD0");
assert!(result.is_some());
// Append a few more elements let result = zm2d.try_append(&5, "ddd", "DD1");
assert!(result.is_none()); let result = zm2d.try_append(&7, "ddd", "DD2");
assert!(result.is_none()); let result = zm2d.try_append(&7, "eee", "EEE");
assert!(result.is_none()); let result = zm2d.try_append(&7, "www", "WWW");
assert!(result.is_none()); let result = zm2d.try_append(&9, "yyy", "YYY");
assert!(result.is_none());
// Remove some elements let result = zm2d.remove(&3, "ccc"); // first element
assert_eq!(result.as_deref(), Some("CCC")); let result = zm2d.remove(&3, "mmm"); // middle element
assert_eq!(result.as_deref(), Some("MM0")); let result = zm2d.remove(&5, "ddd"); // singleton K0
assert_eq!(result.as_deref(), Some("DD1")); let result = zm2d.remove(&9, "yyy"); // last element
assert_eq!(result.as_deref(), Some("YYY"));
let zeromap2d: ZeroMap2d<[u8; 5], i32, Option<[u8; 4]>> =
source_data.iter().copied().collect();
letmut btreemap_iter = btreemap.iter();
for cursor in zeromap2d.iter0() { for (key1, value) in cursor.iter1() { // This code runs for every (key0, key1) pair in order let expected = btreemap_iter.next().unwrap();
assert_eq!(
(expected.0 .0, expected.0 .1, expected.1),
(*cursor.key0(), key1.as_unsigned_int() as i32, &value.get())
);
}
}
assert!(btreemap_iter.next().is_none());
}
}
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.16 Sekunden
(vorverarbeitet am 2026-06-22)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.