From d79f6696491863cefe5684cbb7900576c3e5a923 Mon Sep 17 00:00:00 2001 From: Rain Date: Sat, 31 May 2025 21:08:20 +0000 Subject: [PATCH] [WIP] IdIndexMap --- Cargo.lock | 10 + Cargo.toml | 1 + crates/iddqd/Cargo.toml | 1 + crates/iddqd/src/id_index_map/daft_impls.rs | 237 +++++++++ crates/iddqd/src/id_index_map/entry.rs | 256 +++++++++ crates/iddqd/src/id_index_map/imp.rs | 525 +++++++++++++++++++ crates/iddqd/src/id_index_map/iter.rs | 375 +++++++++++++ crates/iddqd/src/id_index_map/mod.rs | 21 + crates/iddqd/src/id_index_map/ref_mut.rs | 129 +++++ crates/iddqd/src/id_index_map/serde_impls.rs | 194 +++++++ crates/iddqd/src/id_index_map/tables.rs | 55 ++ crates/iddqd/src/lib.rs | 2 + crates/iddqd/src/macros.rs | 3 +- crates/iddqd/src/support/mod.rs | 1 + crates/iddqd/src/support/ordered_set.rs | 71 +++ 15 files changed, 1880 insertions(+), 1 deletion(-) create mode 100644 crates/iddqd/src/id_index_map/daft_impls.rs create mode 100644 crates/iddqd/src/id_index_map/entry.rs create mode 100644 crates/iddqd/src/id_index_map/imp.rs create mode 100644 crates/iddqd/src/id_index_map/iter.rs create mode 100644 crates/iddqd/src/id_index_map/mod.rs create mode 100644 crates/iddqd/src/id_index_map/ref_mut.rs create mode 100644 crates/iddqd/src/id_index_map/serde_impls.rs create mode 100644 crates/iddqd/src/id_index_map/tables.rs create mode 100644 crates/iddqd/src/support/ordered_set.rs diff --git a/Cargo.lock b/Cargo.lock index 0a9fda6..8b7e24c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -81,6 +81,15 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "flex_array" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94efd9404da148e1ab14db7a674a97da8ab964638a25056f32fed36f2064ff92" +dependencies = [ + "allocator-api2", +] + [[package]] name = "fnv" version = "1.0.7" @@ -143,6 +152,7 @@ dependencies = [ "allocator-api2", "daft", "equivalent", + "flex_array", "foldhash", "hashbrown", "iddqd-test-utils", diff --git a/Cargo.toml b/Cargo.toml index 14d8ee6..a382902 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ allocator-api2 = { version = "0.2.21", default-features = false, features = ["al bumpalo = { version = "3.17.0", features = ["allocator-api2", "collections"] } daft = { version = "0.1.3", default-features = false } equivalent = "1.0.2" +flex_array = { version = "0.2.5", features = ["alloc_api2"] } foldhash = "0.1.5" # We have to turn on hashbrown's allocator-api2 feature even if we don't expose # it in our public API. There's no way to refer to the hashbrown Allocator trait diff --git a/crates/iddqd/Cargo.toml b/crates/iddqd/Cargo.toml index 440e821..c52f474 100644 --- a/crates/iddqd/Cargo.toml +++ b/crates/iddqd/Cargo.toml @@ -26,6 +26,7 @@ workspace = true allocator-api2 = { workspace = true } daft = { workspace = true, optional = true } equivalent.workspace = true +flex_array.workspace = true foldhash = { workspace = true, optional = true } hashbrown.workspace = true ref-cast = { workspace = true, optional = true } diff --git a/crates/iddqd/src/id_index_map/daft_impls.rs b/crates/iddqd/src/id_index_map/daft_impls.rs new file mode 100644 index 0000000..ac51c81 --- /dev/null +++ b/crates/iddqd/src/id_index_map/daft_impls.rs @@ -0,0 +1,237 @@ +//! `Diffable` implementation. + +use super::{IdHashItem, IdIndexMap}; +use crate::{ + DefaultHashBuilder, + support::{ + alloc::{Allocator, Global}, + daft_utils::IdLeaf, + }, +}; +use core::hash::{BuildHasher, Hash}; +use daft::Diffable; +use equivalent::Equivalent; + +impl Diffable + for IdIndexMap +{ + type Diff<'a> + = Diff<'a, T, S, A> + where + T: 'a, + S: 'a, + A: 'a; + + fn diff<'daft>(&'daft self, other: &'daft Self) -> Self::Diff<'daft> { + // TODO: Implement + todo!() + } +} + +/// A diff of two [`IdIndexMap`]s. +/// +/// Generated by the [`Diffable`] implementation for [`IdIndexMap`]. +/// +/// # Examples +/// +/// ``` +/// # #[cfg(feature = "default-hasher")] { +/// use daft::Diffable; +/// use iddqd::{IdHashItem, IdIndexMap, id_upcast}; +/// +/// #[derive(Eq, PartialEq)] +/// struct Item { +/// id: String, +/// value: u32, +/// } +/// +/// impl IdHashItem for Item { +/// type Key<'a> = &'a str; +/// fn key(&self) -> Self::Key<'_> { +/// &self.id +/// } +/// id_upcast!(); +/// } +/// +/// // Create two IdIndexMaps with overlapping items. +/// let mut map1 = IdIndexMap::new(); +/// map1.insert_unique(Item { id: "a".to_string(), value: 1 }); +/// map1.insert_unique(Item { id: "b".to_string(), value: 2 }); +/// +/// let mut map2 = IdIndexMap::new(); +/// map2.insert_unique(Item { id: "b".to_string(), value: 3 }); +/// map2.insert_unique(Item { id: "c".to_string(), value: 4 }); +/// +/// // Compute the diff between the two maps. +/// let diff = map1.diff(&map2); +/// +/// // "a" is removed. +/// assert!(diff.removed.contains_key("a")); +/// // "b" is modified (value changed from 2 to 3). +/// assert!(diff.is_modified("b")); +/// // "c" is added. +/// assert!(diff.added.contains_key("c")); +/// # } +/// ``` +/// +/// [`Diffable`]: daft::Diffable +pub struct Diff< + 'daft, + T: ?Sized + IdHashItem, + S = DefaultHashBuilder, + A: Allocator = Global, +> { + /// Entries common to both maps. + /// + /// Items are stored as [`IdLeaf`]s to references. + pub common: IdIndexMap, S, A>, + + /// Added entries. + pub added: IdIndexMap<&'daft T, S, A>, + + /// Removed entries. + pub removed: IdIndexMap<&'daft T, S, A>, +} + +impl<'daft, T: ?Sized + IdHashItem, S: Default, A: Allocator + Default> Default + for Diff<'daft, T, S, A> +{ + fn default() -> Self { + Self { + common: IdIndexMap::default(), + added: IdIndexMap::default(), + removed: IdIndexMap::default(), + } + } +} + +#[cfg(all(feature = "default-hasher", feature = "allocator-api2"))] +impl<'daft, T: ?Sized + IdHashItem> Diff<'daft, T> { + /// Creates a new, empty `IdIndexMapDiff` + pub fn new() -> Self { + Self { + common: IdIndexMap::new(), + added: IdIndexMap::new(), + removed: IdIndexMap::new(), + } + } +} + +#[cfg(feature = "allocator-api2")] +impl<'daft, T: ?Sized + IdHashItem, S: Clone + BuildHasher> Diff<'daft, T, S> { + /// Creates a new `IdIndexMapDiff` with the given hasher. + pub fn with_hasher(hasher: S) -> Self { + Self { + common: IdIndexMap::with_hasher(hasher.clone()), + added: IdIndexMap::with_hasher(hasher.clone()), + removed: IdIndexMap::with_hasher(hasher), + } + } +} + +impl< + 'daft, + T: ?Sized + IdHashItem, + S: Clone + BuildHasher, + A: Clone + Allocator, +> Diff<'daft, T, S, A> +{ + /// Creates a new `IdIndexMapDiff` with the given hasher and allocator. + pub fn with_hasher_in(hasher: S, alloc: A) -> Self { + Self { + common: IdIndexMap::with_hasher_in(hasher.clone(), alloc.clone()), + added: IdIndexMap::with_hasher_in(hasher.clone(), alloc.clone()), + removed: IdIndexMap::with_hasher_in(hasher, alloc), + } + } +} + +impl<'daft, T: ?Sized + IdHashItem + Eq, S: Clone + BuildHasher, A: Allocator> + Diff<'daft, T, S, A> +{ + /// Returns an iterator over unchanged keys and values. + pub fn unchanged(&self) -> impl Iterator + '_ { + // TODO: Implement + todo!() + } + + /// Returns true if the item corresponding to the key is unchanged. + pub fn is_unchanged<'a, Q>(&'a self, key: &Q) -> bool + where + Q: ?Sized + Hash + Equivalent>, + { + // TODO: Implement + todo!() + } + + /// Returns the value associated with the key if it is unchanged, + /// otherwise `None`. + pub fn get_unchanged<'a, Q>(&'a self, key: &Q) -> Option<&'daft T> + where + Q: ?Sized + Hash + Equivalent>, + { + // TODO: Implement + todo!() + } + + /// Returns an iterator over modified keys and values. + pub fn modified(&self) -> impl Iterator> + '_ { + // TODO: Implement + todo!() + } + + /// Returns true if the value corresponding to the key is + /// modified. + pub fn is_modified<'a, Q>(&'a self, key: &Q) -> bool + where + Q: ?Sized + Hash + Equivalent>, + { + // TODO: Implement + todo!() + } + + /// Returns the [`IdLeaf`] associated with the key if it is modified, + /// otherwise `None`. + pub fn get_modified<'a, Q>(&'a self, key: &Q) -> Option> + where + Q: ?Sized + Hash + Equivalent>, + { + // TODO: Implement + todo!() + } + + /// Returns an iterator over modified keys and values, performing + /// a diff on the values. + /// + /// This is useful when `T::Diff` is a complex type, not just a + /// [`daft::Leaf`]. + pub fn modified_diff(&self) -> impl Iterator> + '_ + where + T: Diffable, + { + // TODO: Implement + todo!() + } +} + +impl IdHashItem for IdLeaf { + type Key<'a> + = T::Key<'a> + where + T: 'a; + + fn key(&self) -> Self::Key<'_> { + let before_key = self.before().key(); + if before_key != self.after().key() { + panic!("key is different between before and after"); + } + self.before().key() + } + + #[inline] + fn upcast_key<'short, 'long: 'short>( + long: Self::Key<'long>, + ) -> Self::Key<'short> { + T::upcast_key(long) + } +} diff --git a/crates/iddqd/src/id_index_map/entry.rs b/crates/iddqd/src/id_index_map/entry.rs new file mode 100644 index 0000000..c6f1e5d --- /dev/null +++ b/crates/iddqd/src/id_index_map/entry.rs @@ -0,0 +1,256 @@ +use super::{IdHashItem, IdIndexMap, RefMut}; +use crate::{ + DefaultHashBuilder, + support::{ + alloc::{Allocator, Global}, + borrow::DormantMutRef, + map_hash::MapHash, + }, +}; +use core::{fmt, hash::BuildHasher}; + +/// An implementation of the Entry API for [`IdIndexMap`]. +pub enum Entry<'a, T: IdHashItem, S = DefaultHashBuilder, A: Allocator = Global> +{ + /// A vacant entry. + Vacant(VacantEntry<'a, T, S, A>), + /// An occupied entry. + Occupied(OccupiedEntry<'a, T, S, A>), +} + +impl<'a, T: IdHashItem, S, A: Allocator> fmt::Debug for Entry<'a, T, S, A> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Entry::Vacant(entry) => { + f.debug_tuple("Vacant").field(entry).finish() + } + Entry::Occupied(entry) => { + f.debug_tuple("Occupied").field(entry).finish() + } + } + } +} + +impl<'a, T: IdHashItem, S: Clone + BuildHasher, A: Allocator> + Entry<'a, T, S, A> +{ + /// Ensures a value is in the entry by inserting the default if empty, and + /// returns a mutable reference to the value in the entry. + /// + /// # Panics + /// + /// Panics if the key hashes to a different value than the one passed + /// into [`IdIndexMap::entry`]. + #[inline] + pub fn or_insert(self, default: T) -> RefMut<'a, T, S> { + // TODO: Implement + todo!() + } + + /// Ensures a value is in the entry by inserting the result of the default + /// function if empty, and returns a mutable reference to the value in the + /// entry. + /// + /// # Panics + /// + /// Panics if the key hashes to a different value than the one passed + /// into [`IdIndexMap::entry`]. + #[inline] + pub fn or_insert_with T>( + self, + default: F, + ) -> RefMut<'a, T, S> { + // TODO: Implement + todo!() + } + + /// Provides in-place mutable access to an occupied entry before any + /// potential inserts into the map. + #[inline] + pub fn and_modify(self, f: F) -> Self + where + F: FnOnce(RefMut<'_, T, S>), + { + // TODO: Implement + todo!() + } + + /// Returns the index of this entry in the map. + /// + /// For vacant entries, this returns the index where the entry would be inserted. + pub fn index(&self) -> usize { + // TODO: Implement + todo!() + } +} + +/// A vacant entry. +pub struct VacantEntry< + 'a, + T: IdHashItem, + S = DefaultHashBuilder, + A: Allocator = Global, +> { + map: DormantMutRef<'a, IdIndexMap>, + hash: MapHash, +} + +impl<'a, T: IdHashItem, S, A: Allocator> fmt::Debug + for VacantEntry<'a, T, S, A> +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("VacantEntry") + .field("hash", &self.hash) + .finish_non_exhaustive() + } +} + +impl<'a, T: IdHashItem, S: Clone + BuildHasher, A: Allocator> + VacantEntry<'a, T, S, A> +{ + pub(super) unsafe fn new( + map: DormantMutRef<'a, IdIndexMap>, + hash: MapHash, + ) -> Self { + VacantEntry { map, hash } + } + + /// Sets the entry to a new value, returning a mutable reference to the + /// value. + pub fn insert(self, value: T) -> RefMut<'a, T, S> { + // TODO: Implement + todo!() + } + + /// Sets the value of the entry, and returns an `OccupiedEntry`. + #[inline] + pub fn insert_entry(mut self, value: T) -> OccupiedEntry<'a, T, S, A> { + // TODO: Implement + todo!() + } + + /// Returns the index where this entry would be inserted. + pub fn index(&self) -> usize { + // TODO: Implement + todo!() + } +} + +/// A view into an occupied entry in an [`IdIndexMap`]. Part of the [`Entry`] +/// enum. +pub struct OccupiedEntry< + 'a, + T: IdHashItem, + S = DefaultHashBuilder, + A: Allocator = Global, +> { + map: DormantMutRef<'a, IdIndexMap>, + // index is a valid index into the map's internal ordered storage. + index: usize, +} + +impl<'a, T: IdHashItem, S, A: Allocator> fmt::Debug + for OccupiedEntry<'a, T, S, A> +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("OccupiedEntry") + .field("index", &self.index) + .finish_non_exhaustive() + } +} + +impl<'a, T: IdHashItem, S: Clone + BuildHasher, A: Allocator> + OccupiedEntry<'a, T, S, A> +{ + /// # Safety + /// + /// After self is created, the original reference created by + /// `DormantMutRef::new` must not be used. + pub(super) unsafe fn new( + map: DormantMutRef<'a, IdIndexMap>, + index: usize, + ) -> Self { + OccupiedEntry { map, index } + } + + /// Gets a reference to the value. + /// + /// If you need a reference to `T` that may outlive the destruction of the + /// `Entry` value, see [`into_ref`](Self::into_ref). + pub fn get(&self) -> &T { + // TODO: Implement + todo!() + } + + /// Gets a mutable reference to the value. + /// + /// If you need a reference to `T` that may outlive the destruction of the + /// `Entry` value, see [`into_mut`](Self::into_mut). + pub fn get_mut(&mut self) -> RefMut<'_, T, S> { + // TODO: Implement + todo!() + } + + /// Converts self into a reference to the value. + /// + /// If you need multiple references to the `OccupiedEntry`, see + /// [`get`](Self::get). + pub fn into_ref(self) -> &'a T { + // TODO: Implement + todo!() + } + + /// Converts self into a mutable reference to the value. + /// + /// If you need multiple references to the `OccupiedEntry`, see + /// [`get_mut`](Self::get_mut). + pub fn into_mut(self) -> RefMut<'a, T, S> { + // TODO: Implement + todo!() + } + + /// Sets the entry to a new value, returning the old value. + /// + /// # Panics + /// + /// Panics if `value.key()` is different from the key of the entry. + pub fn insert(&mut self, value: T) -> T { + // TODO: Implement + todo!() + } + + /// Takes ownership of the value from the map. + pub fn remove(mut self) -> T { + // TODO: Implement + todo!() + } + + /// Takes ownership of the value from the map, shifting all elements after it. + pub fn shift_remove(self) -> T { + // TODO: Implement + todo!() + } + + /// Takes ownership of the value from the map, swapping it with the last element. + pub fn swap_remove(self) -> T { + // TODO: Implement + todo!() + } + + /// Returns the index of this entry in the map. + pub fn index(&self) -> usize { + self.index + } + + /// Moves this entry to a new index. + pub fn move_to(&mut self, new_index: usize) { + // TODO: Implement + todo!() + } + + /// Swaps this entry with another entry at the given index. + pub fn swap_with(&mut self, other_index: usize) { + // TODO: Implement + todo!() + } +} diff --git a/crates/iddqd/src/id_index_map/imp.rs b/crates/iddqd/src/id_index_map/imp.rs new file mode 100644 index 0000000..8e7e318 --- /dev/null +++ b/crates/iddqd/src/id_index_map/imp.rs @@ -0,0 +1,525 @@ +use super::{ + Entry, IdHashItem, IntoIter, Iter, IterMut, OccupiedEntry, RefMut, + VacantEntry, tables::IdIndexMapTables, +}; +use crate::{ + DefaultHashBuilder, + errors::DuplicateItem, + internal::{ValidateCompact, ValidationError}, + support::{ + alloc::{Allocator, Global, global_alloc}, + borrow::DormantMutRef, + item_set::ItemSet, + map_hash::MapHash, + ordered_set::OrderedSet, + }, +}; +use alloc::collections::BTreeSet; +use core::{ + fmt, + hash::{BuildHasher, Hash}, +}; +use equivalent::Equivalent; +use hashbrown::hash_table; + +/// An index map where the key is part of the value, preserving insertion order. +/// +/// Similar to [`IdHashMap`], but maintains the order in which items were inserted, +/// like [`IndexMap`]. +/// +/// The storage mechanism uses an ordered vector of items with a hash table of +/// integer indexes for efficient lookups by key while preserving insertion order. +/// +/// # Examples +/// +/// ``` +/// # #[cfg(feature = "default-hasher")] { +/// use iddqd::{IdHashItem, IdIndexMap, id_upcast}; +/// +/// // Define a struct with a key. +/// #[derive(Debug, PartialEq, Eq, Hash)] +/// struct MyItem { +/// id: String, +/// value: u32, +/// } +/// +/// // Implement IdHashItem for the struct. +/// impl IdHashItem for MyItem { +/// // Keys can borrow from the item. +/// type Key<'a> = &'a str; +/// +/// fn key(&self) -> Self::Key<'_> { +/// &self.id +/// } +/// +/// id_upcast!(); +/// } +/// +/// // Create an IdIndexMap and insert items. +/// let mut map = IdIndexMap::new(); +/// map.insert_unique(MyItem { id: "foo".to_string(), value: 42 }).unwrap(); +/// map.insert_unique(MyItem { id: "bar".to_string(), value: 20 }).unwrap(); +/// +/// // Look up items by their keys. +/// assert_eq!(map.get("foo").unwrap().value, 42); +/// assert_eq!(map.get("bar").unwrap().value, 20); +/// assert!(map.get("baz").is_none()); +/// +/// // Iteration preserves insertion order +/// let items: Vec<_> = map.iter().collect(); +/// assert_eq!(items[0].id, "foo"); +/// assert_eq!(items[1].id, "bar"); +/// # } +/// ``` +/// +/// [`IdHashMap`]: crate::IdHashMap +/// [`IndexMap`]: https://docs.rs/indexmap +#[derive(Clone)] +pub struct IdIndexMap< + T: IdHashItem, + S = DefaultHashBuilder, + A: Allocator = Global, +> { + items: OrderedSet, + tables: IdIndexMapTables, +} + +impl Default + for IdIndexMap +{ + fn default() -> Self { + Self { + items: OrderedSet::with_capacity_in(0, A::default()), + tables: IdIndexMapTables::default(), + } + } +} + +#[cfg(feature = "default-hasher")] +impl IdIndexMap { + /// Creates a new, empty `IdIndexMap`. + #[inline] + pub fn new() -> Self { + Self { + items: OrderedSet::default(), + tables: IdIndexMapTables::default(), + } + } + + /// Creates a new `IdIndexMap` with the given capacity. + pub fn with_capacity(capacity: usize) -> Self { + Self { + items: OrderedSet::with_capacity_in(capacity, global_alloc()), + tables: IdIndexMapTables::with_capacity_and_hasher_in( + capacity, + DefaultHashBuilder::default(), + global_alloc(), + ), + } + } +} + +impl IdIndexMap { + /// Creates a new, empty `IdIndexMap` with the given hasher. + pub fn with_hasher(hasher: S) -> Self { + Self { + items: OrderedSet::default(), + tables: IdIndexMapTables::with_capacity_and_hasher_in( + 0, + hasher, + global_alloc(), + ), + } + } + + /// Creates a new `IdIndexMap` with the given capacity and hasher. + pub fn with_capacity_and_hasher(capacity: usize, hasher: S) -> Self { + Self { + items: OrderedSet::with_capacity_in(capacity, global_alloc()), + tables: IdIndexMapTables::with_capacity_and_hasher_in( + capacity, + hasher, + global_alloc(), + ), + } + } +} + +#[cfg(feature = "default-hasher")] +impl IdIndexMap { + /// Creates a new empty `IdIndexMap` using the given allocator. + pub fn new_in(alloc: A) -> Self { + Self { + items: OrderedSet::with_capacity_in(0, alloc.clone()), + tables: IdIndexMapTables::with_capacity_and_hasher_in( + 0, + DefaultHashBuilder::default(), + alloc, + ), + } + } + + /// Creates an empty `IdIndexMap` with the specified capacity using the given allocator. + pub fn with_capacity_in(capacity: usize, alloc: A) -> Self { + Self { + items: OrderedSet::with_capacity_in(capacity, alloc.clone()), + tables: IdIndexMapTables::with_capacity_and_hasher_in( + capacity, + DefaultHashBuilder::default(), + alloc, + ), + } + } +} + +impl + IdIndexMap +{ + /// Creates a new, empty `IdIndexMap` with the given hasher and allocator. + pub fn with_hasher_in(hasher: S, alloc: A) -> Self { + Self { + items: OrderedSet::with_capacity_in(0, alloc.clone()), + tables: IdIndexMapTables::with_capacity_and_hasher_in( + 0, hasher, alloc, + ), + } + } + + /// Creates a new, empty `IdIndexMap` with the given capacity, hasher, and allocator. + pub fn with_capacity_and_hasher_in( + capacity: usize, + hasher: S, + alloc: A, + ) -> Self { + Self { + items: OrderedSet::with_capacity_in(capacity, alloc.clone()), + tables: IdIndexMapTables::with_capacity_and_hasher_in( + capacity, hasher, alloc, + ), + } + } +} + +impl IdIndexMap { + #[cfg(feature = "daft")] + pub(crate) fn hasher(&self) -> &S { + self.tables.hasher() + } + + /// Returns the allocator. + pub fn allocator(&self) -> &A { + self.items.allocator() + } + + /// Returns the currently allocated capacity of the map. + pub fn capacity(&self) -> usize { + // items and tables.capacity might theoretically diverge: use + // items.capacity. + self.items.capacity() + } + + /// Returns true if the map is empty. + #[inline] + pub fn is_empty(&self) -> bool { + self.items.is_empty() + } + + /// Returns the number of items in the map. + #[inline] + pub fn len(&self) -> usize { + self.items.len() + } + + /// Iterates over the items in the map in insertion order. + #[inline] + pub fn iter(&self) -> Iter<'_, T> { + Iter::new(&self.items) + } + + /// Iterates over the items in the map, allowing for mutation, in insertion order. + #[inline] + pub fn iter_mut(&mut self) -> IterMut<'_, T, S, A> { + IterMut::new(&self.tables, &mut self.items) + } + + /// Checks general invariants of the map. + #[doc(hidden)] + pub fn validate( + &self, + compactness: ValidateCompact, + ) -> Result<(), ValidationError> + where + T: fmt::Debug, + { + self.items.validate(compactness)?; + self.tables.validate(self.len(), compactness)?; + + // Check that the indexes are all correct. + for (&ix, item) in self.items.iter() { + let key = item.key(); + let Some(ix1) = self.find_index(&key) else { + return Err(ValidationError::general(format!( + "item at index {ix} has no key1 index" + ))); + }; + + if ix1 != ix { + return Err(ValidationError::General(format!( + "item at index {} has mismatched indexes: ix1: {}", + ix, ix1, + ))); + } + } + + Ok(()) + } + + /// Inserts a value into the map, removing and returning the conflicting item, if any. + #[doc(alias = "insert")] + pub fn insert_overwrite(&mut self, value: T) -> Option { + // TODO: use swap_remove + } + + /// Inserts a value into the map, returning an error if any duplicates were added. + pub fn insert_unique( + &mut self, + value: T, + ) -> Result<(), DuplicateItem> { + let _ = self.insert_unique_impl(value)?; + Ok(()) + } + + /// Returns true if the map contains the given key. + pub fn contains_key<'a, Q>(&'a self, key1: &Q) -> bool + where + Q: ?Sized + Hash + Equivalent>, + { + self.find_index(key1).is_some() + } + + /// Gets a reference to the value associated with the given key. + pub fn get<'a, Q>(&'a self, key: &Q) -> Option<&'a T> + where + Q: ?Sized + Hash + Equivalent>, + { + self.find_index(key).map(|ix| &self.items[ix]) + } + + /// Gets a mutable reference to the value associated with the given key. + pub fn get_mut<'a, Q>(&'a mut self, key: &Q) -> Option> + where + Q: ?Sized + Hash + Equivalent>, + { + let (dormant_map, index) = { + let (map, dormant_map) = DormantMutRef::new(self); + let index = map.find_index(key)?; + (dormant_map, index) + }; + + // SAFETY: `map` is not used after this point. + let awakened_map = unsafe { dormant_map.awaken() }; + let item = &mut awakened_map.items[index]; + let hashes = awakened_map.tables.make_hash(item); + Some(RefMut::new(hashes, item)) + } + + /// Gets the item at the given index. + pub fn get_index(&self, index: usize) -> Option<&T> { + self.items.get(index) + } + + /// Gets a mutable reference to the item at the given index. + pub fn get_index_mut(&mut self, index: usize) -> Option> { + let item = self.items.get_mut(index)?; + let hashes = self.tables.make_hash(item); + Some(RefMut::new(hashes, item)) + } + + /// Gets the index of the item with the given key. + pub fn get_index_of<'a, Q>(&'a self, key: &Q) -> Option + where + Q: ?Sized + Hash + Equivalent>, + { + self.find_index(key) + } + + /// Removes and returns the item at the given index. + pub fn shift_remove_index(&mut self, index: usize) -> Option { + let index = self.items.shift_remove(index); + // Change the index of all items in the hash table greater than the + // removed index + self.tables.shift_remove_index(index); + Some(item) + } + + /// Removes and returns the item at the given index, swapping it with the last item. + pub fn swap_remove_index(&mut self, index: usize) -> Option { + // TODO: Implement + todo!() + } + + /// Retrieves an entry by its key. + pub fn entry<'a>(&'a mut self, key: T::Key<'_>) -> Entry<'a, T, S, A> { + // TODO: Implement + todo!() + } + + /// Moves an item from one index to another. + pub fn move_index(&mut self, from: usize, to: usize) { + // TODO: Implement + todo!() + } + + /// Swaps two items by their indices. + pub fn swap_indices(&mut self, a: usize, b: usize) { + // TODO: Implement + todo!() + } + + /// Reverses the order of items in the map. + pub fn reverse(&mut self) { + // TODO: Implement + todo!() + } + + /// Sorts the items in the map by the given comparison function. + pub fn sort_by(&mut self, compare: F) + where + F: FnMut(&T, &T) -> core::cmp::Ordering, + { + // TODO: Implement + todo!() + } + + /// Sorts the items in the map by their keys. + pub fn sort_by_key(&mut self, f: F) + where + F: FnMut(&T) -> K, + K: Ord, + { + // TODO: Implement + todo!() + } + + /// Sorts the items in the map by their keys using a cached key function. + pub fn sort_by_cached_key(&mut self, f: F) + where + F: FnMut(&T) -> K, + K: Ord, + { + // TODO: Implement + todo!() + } + + // Internal helper methods + pub(super) fn get_by_index(&self, index: usize) -> Option<&T> { + // TODO: Implement + todo!() + } + + pub(super) fn get_by_index_mut( + &mut self, + index: usize, + ) -> Option> { + // TODO: Implement + todo!() + } + + pub(super) fn insert_unique_impl( + &mut self, + value: T, + ) -> Result> { + // TODO: Implement + todo!() + } + + pub(super) fn remove_by_index(&mut self, remove_index: usize) -> Option { + // TODO: Implement + todo!() + } + + pub(super) fn replace_at_index(&mut self, index: usize, value: T) -> T { + // TODO: Implement + todo!() + } +} + +impl fmt::Debug for IdIndexMap +where + T: IdHashItem + fmt::Debug, + for<'k> T::Key<'k>: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // TODO: Implement + todo!() + } +} + +impl PartialEq + for IdIndexMap +{ + fn eq(&self, other: &Self) -> bool { + // TODO: Implement + todo!() + } +} + +impl Eq + for IdIndexMap +{ +} + +impl Extend + for IdIndexMap +{ + fn extend>(&mut self, iter: I) { + // TODO: Implement + todo!() + } +} + +impl<'a, T: IdHashItem, S: Clone + BuildHasher, A: Allocator> IntoIterator + for &'a IdIndexMap +{ + type Item = &'a T; + type IntoIter = Iter<'a, T>; + + #[inline] + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +impl<'a, T: IdHashItem, S: Clone + BuildHasher, A: Allocator> IntoIterator + for &'a mut IdIndexMap +{ + type Item = RefMut<'a, T, S>; + type IntoIter = IterMut<'a, T, S, A>; + + #[inline] + fn into_iter(self) -> Self::IntoIter { + self.iter_mut() + } +} + +impl IntoIterator + for IdIndexMap +{ + type Item = T; + type IntoIter = IntoIter; + + #[inline] + fn into_iter(self) -> Self::IntoIter { + // TODO: Implement + todo!() + } +} + +impl + FromIterator for IdIndexMap +{ + fn from_iter>(iter: I) -> Self { + // TODO: Implement + todo!() + } +} diff --git a/crates/iddqd/src/id_index_map/iter.rs b/crates/iddqd/src/id_index_map/iter.rs new file mode 100644 index 0000000..ee012e6 --- /dev/null +++ b/crates/iddqd/src/id_index_map/iter.rs @@ -0,0 +1,375 @@ +use super::{RefMut, tables::IdIndexMapTables}; +use crate::{ + DefaultHashBuilder, IdHashItem, + support::{ + alloc::{AllocWrapper, Allocator, Global}, + item_set::ItemSet, + ordered_set::OrderedSet, + }, +}; +use core::{hash::BuildHasher, iter::FusedIterator}; + +/// An iterator over the elements of a [`IdIndexMap`] by shared reference. +/// Created by [`IdIndexMap::iter`]. +/// +/// Items are yielded in insertion order. +/// +/// [`IdIndexMap`]: crate::IdIndexMap +/// [`IdIndexMap::iter`]: crate::IdIndexMap::iter +#[derive(Clone, Debug, Default)] +pub struct Iter<'a, T: IdHashItem> { + // TODO: Implement internal iterator structure + _phantom: core::marker::PhantomData<&'a T>, +} + +impl<'a, T: IdHashItem> Iter<'a, T> { + pub(crate) fn new(items: &'a OrderedSet) -> Self { + // TODO: Implement + todo!() + } +} + +impl<'a, T: IdHashItem> Iterator for Iter<'a, T> { + type Item = &'a T; + + #[inline] + fn next(&mut self) -> Option { + // TODO: Implement + todo!() + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + // TODO: Implement + todo!() + } +} + +impl<'a, T: IdHashItem> DoubleEndedIterator for Iter<'a, T> { + #[inline] + fn next_back(&mut self) -> Option { + // TODO: Implement + todo!() + } +} + +impl ExactSizeIterator for Iter<'_, T> { + #[inline] + fn len(&self) -> usize { + // TODO: Implement + todo!() + } +} + +impl FusedIterator for Iter<'_, T> {} + +/// An iterator over the elements of a [`IdIndexMap`] by mutable reference. +/// Created by [`IdIndexMap::iter_mut`]. +/// +/// This iterator returns [`RefMut`] instances. +/// +/// Items are yielded in insertion order. +/// +/// [`IdIndexMap`]: crate::IdIndexMap +/// [`IdIndexMap::iter_mut`]: crate::IdIndexMap::iter_mut +#[derive(Debug)] +pub struct IterMut< + 'a, + T: IdHashItem, + S = DefaultHashBuilder, + A: Allocator = Global, +> { + // TODO: Implement internal iterator structure + _phantom: core::marker::PhantomData<(&'a mut T, S, A)>, +} + +impl<'a, T: IdHashItem, S: Clone + BuildHasher, A: Allocator> + IterMut<'a, T, S, A> +{ + pub(super) fn new( + tables: &'a IdIndexMapTables, + items: &'a mut ItemSet, + ) -> Self { + // TODO: Implement + todo!() + } +} + +impl<'a, T: IdHashItem, S: Clone + BuildHasher, A: Allocator> Iterator + for IterMut<'a, T, S, A> +{ + type Item = RefMut<'a, T, S>; + + #[inline] + fn next(&mut self) -> Option { + // TODO: Implement + todo!() + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + // TODO: Implement + todo!() + } +} + +impl<'a, T: IdHashItem, S: Clone + BuildHasher, A: Allocator> + DoubleEndedIterator for IterMut<'a, T, S, A> +{ + #[inline] + fn next_back(&mut self) -> Option { + // TODO: Implement + todo!() + } +} + +impl ExactSizeIterator + for IterMut<'_, T, S, A> +{ + #[inline] + fn len(&self) -> usize { + // TODO: Implement + todo!() + } +} + +impl FusedIterator + for IterMut<'_, T, S, A> +{ +} + +/// An iterator over the elements of a [`IdIndexMap`] by ownership. Created by +/// [`IdIndexMap::into_iter`]. +/// +/// Items are yielded in insertion order. +/// +/// [`IdIndexMap`]: crate::IdIndexMap +/// [`IdIndexMap::into_iter`]: crate::IdIndexMap::into_iter +#[derive(Debug)] +pub struct IntoIter { + // TODO: Implement internal iterator structure + _phantom: core::marker::PhantomData<(T, A)>, +} + +impl IntoIter { + pub(crate) fn new(items: ItemSet) -> Self { + // TODO: Implement + todo!() + } +} + +impl Iterator for IntoIter { + type Item = T; + + #[inline] + fn next(&mut self) -> Option { + // TODO: Implement + todo!() + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + // TODO: Implement + todo!() + } +} + +impl DoubleEndedIterator for IntoIter { + #[inline] + fn next_back(&mut self) -> Option { + // TODO: Implement + todo!() + } +} + +impl ExactSizeIterator for IntoIter { + #[inline] + fn len(&self) -> usize { + // TODO: Implement + todo!() + } +} + +impl FusedIterator for IntoIter {} + +/// An iterator over the keys of a [`IdIndexMap`] by shared reference. +#[derive(Clone, Debug)] +pub struct Keys<'a, T: IdHashItem> { + inner: Iter<'a, T>, +} + +impl<'a, T: IdHashItem> Keys<'a, T> { + pub(crate) fn new(iter: Iter<'a, T>) -> Self { + Self { inner: iter } + } +} + +impl<'a, T: IdHashItem> Iterator for Keys<'a, T> { + type Item = T::Key<'a>; + + #[inline] + fn next(&mut self) -> Option { + self.inner.next().map(|item| item.key()) + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } +} + +impl<'a, T: IdHashItem> DoubleEndedIterator for Keys<'a, T> { + #[inline] + fn next_back(&mut self) -> Option { + self.inner.next_back().map(|item| item.key()) + } +} + +impl ExactSizeIterator for Keys<'_, T> { + #[inline] + fn len(&self) -> usize { + self.inner.len() + } +} + +impl FusedIterator for Keys<'_, T> {} + +/// An iterator over the values of a [`IdIndexMap`] by shared reference. +#[derive(Clone, Debug)] +pub struct Values<'a, T: IdHashItem> { + inner: Iter<'a, T>, +} + +impl<'a, T: IdHashItem> Values<'a, T> { + pub(crate) fn new(iter: Iter<'a, T>) -> Self { + Self { inner: iter } + } +} + +impl<'a, T: IdHashItem> Iterator for Values<'a, T> { + type Item = &'a T; + + #[inline] + fn next(&mut self) -> Option { + self.inner.next() + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } +} + +impl<'a, T: IdHashItem> DoubleEndedIterator for Values<'a, T> { + #[inline] + fn next_back(&mut self) -> Option { + self.inner.next_back() + } +} + +impl ExactSizeIterator for Values<'_, T> { + #[inline] + fn len(&self) -> usize { + self.inner.len() + } +} + +impl FusedIterator for Values<'_, T> {} + +/// An iterator over the values of a [`IdIndexMap`] by mutable reference. +#[derive(Debug)] +pub struct ValuesMut< + 'a, + T: IdHashItem, + S = DefaultHashBuilder, + A: Allocator = Global, +> { + inner: IterMut<'a, T, S, A>, +} + +impl<'a, T: IdHashItem, S: Clone + BuildHasher, A: Allocator> + ValuesMut<'a, T, S, A> +{ + pub(crate) fn new(iter: IterMut<'a, T, S, A>) -> Self { + Self { inner: iter } + } +} + +impl<'a, T: IdHashItem, S: Clone + BuildHasher, A: Allocator> Iterator + for ValuesMut<'a, T, S, A> +{ + type Item = RefMut<'a, T, S>; + + #[inline] + fn next(&mut self) -> Option { + self.inner.next() + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } +} + +impl<'a, T: IdHashItem, S: Clone + BuildHasher, A: Allocator> + DoubleEndedIterator for ValuesMut<'a, T, S, A> +{ + #[inline] + fn next_back(&mut self) -> Option { + self.inner.next_back() + } +} + +impl ExactSizeIterator + for ValuesMut<'_, T, S, A> +{ + #[inline] + fn len(&self) -> usize { + self.inner.len() + } +} + +impl FusedIterator + for ValuesMut<'_, T, S, A> +{ +} + +/// An iterator over the indices and values of a [`IdIndexMap`] by shared reference. +#[derive(Clone, Debug)] +pub struct Enumerate<'a, T: IdHashItem> { + inner: Iter<'a, T>, + index: usize, +} + +impl<'a, T: IdHashItem> Enumerate<'a, T> { + pub(crate) fn new(iter: Iter<'a, T>) -> Self { + Self { inner: iter, index: 0 } + } +} + +impl<'a, T: IdHashItem> Iterator for Enumerate<'a, T> { + type Item = (usize, &'a T); + + #[inline] + fn next(&mut self) -> Option { + self.inner.next().map(|item| { + let index = self.index; + self.index += 1; + (index, item) + }) + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } +} + +impl ExactSizeIterator for Enumerate<'_, T> { + #[inline] + fn len(&self) -> usize { + self.inner.len() + } +} + +impl FusedIterator for Enumerate<'_, T> {} diff --git a/crates/iddqd/src/id_index_map/mod.rs b/crates/iddqd/src/id_index_map/mod.rs new file mode 100644 index 0000000..b0997f6 --- /dev/null +++ b/crates/iddqd/src/id_index_map/mod.rs @@ -0,0 +1,21 @@ +//! An index map where keys are part of the values and insertion order is preserved. +//! +//! For more information, see [`IdIndexMap`]. + +#[cfg(feature = "daft")] +mod daft_impls; +mod entry; +pub(crate) mod imp; +mod iter; +mod ref_mut; +#[cfg(feature = "serde")] +mod serde_impls; +mod tables; + +pub use super::id_hash_map::IdHashItem; +#[cfg(feature = "daft")] +pub use daft_impls::Diff; +pub use entry::{Entry, OccupiedEntry, VacantEntry}; +pub use imp::IdIndexMap; +pub use iter::{IntoIter, Iter, IterMut}; +pub use ref_mut::RefMut; diff --git a/crates/iddqd/src/id_index_map/ref_mut.rs b/crates/iddqd/src/id_index_map/ref_mut.rs new file mode 100644 index 0000000..ac8f9ec --- /dev/null +++ b/crates/iddqd/src/id_index_map/ref_mut.rs @@ -0,0 +1,129 @@ +use crate::{DefaultHashBuilder, IdHashItem, support::map_hash::MapHash}; +use core::{ + fmt, + hash::BuildHasher, + ops::{Deref, DerefMut}, +}; + +/// A mutable reference to an [`IdIndexMap`] item. +/// +/// This is a wrapper around a `&mut T` that panics when dropped, if the +/// borrowed value's keys have changed since the wrapper was created. +/// +/// # Change detection +/// +/// It is illegal to change the key of a borrowed `&mut T`. `RefMut` attempts to +/// enforce this invariant. +/// +/// `RefMut` stores the `Hash` output of the key at creation time, and +/// recomputes this hash when it is dropped or when [`Self::into_ref`] is +/// called. If a key changes, there's a small but non-negligible chance that its +/// hash value stays the same[^collision-chance]. In that case, as long as the +/// new key is not the same as another existing one, internal invariants are not +/// violated and the [`IdIndexMap`] will continue to work correctly. (But don't +/// rely on this!) +/// +/// It is also possible to deliberately write pathological `Hash` +/// implementations that collide more often. (Don't do this either.) +/// +/// Also, `RefMut`'s hash detection will not function if [`mem::forget`] is +/// called on it. If the key is changed and `mem::forget` is then called on the +/// `RefMut`, the [`IdIndexMap`] will stop functioning correctly. This will not +/// introduce memory safety issues, however. +/// +/// The issues here are similar to using interior mutability (e.g. `RefCell` or +/// `Mutex`) to mutate keys in a regular `HashMap`. +/// +/// [`mem::forget`]: std::mem::forget +/// +/// [^collision-chance]: The output of `Hash` is a [`u64`], so the probability +/// of an individual hash colliding by chance is 1/2⁶⁴. Due to the [birthday +/// problem], the probability of a collision by chance reaches 10⁻⁶ within +/// around 6 × 10⁶ elements. +/// +/// [`IdIndexMap`]: crate::IdIndexMap +/// [birthday problem]: https://en.wikipedia.org/wiki/Birthday_problem#Probability_table +pub struct RefMut< + 'a, + T: IdHashItem, + S: Clone + BuildHasher = DefaultHashBuilder, +> { + inner: Option>, +} + +impl<'a, T: IdHashItem, S: Clone + BuildHasher> RefMut<'a, T, S> { + pub(super) fn new(hash: MapHash, borrowed: &'a mut T) -> Self { + Self { inner: Some(RefMutInner { hash, borrowed }) } + } + + /// Borrows self into a shorter-lived `RefMut`. + /// + /// This `RefMut` will also check hash equality on drop. + pub fn reborrow(&mut self) -> RefMut<'_, T, S> { + let inner = self.inner.as_mut().unwrap(); + let borrowed = &mut *inner.borrowed; + RefMut::new(inner.hash.clone(), borrowed) + } + + /// Converts this `RefMut` into a `&'a T`. + pub fn into_ref(mut self) -> &'a T { + let inner = self.inner.take().unwrap(); + inner.into_ref() + } +} + +impl Drop for RefMut<'_, T, S> { + fn drop(&mut self) { + if let Some(inner) = self.inner.take() { + inner.into_ref(); + } + } +} + +impl Deref for RefMut<'_, T, S> { + type Target = T; + + fn deref(&self) -> &Self::Target { + self.inner.as_ref().unwrap().borrowed + } +} + +impl DerefMut for RefMut<'_, T, S> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.inner.as_mut().unwrap().borrowed + } +} + +impl fmt::Debug + for RefMut<'_, T, S> +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.inner { + Some(ref inner) => inner.fmt(f), + None => { + f.debug_struct("RefMut").field("borrowed", &"missing").finish() + } + } + } +} + +struct RefMutInner<'a, T: IdHashItem, S> { + hash: MapHash, + borrowed: &'a mut T, +} + +impl<'a, T: IdHashItem, S: BuildHasher> RefMutInner<'a, T, S> { + fn into_ref(self) -> &'a T { + if !self.hash.is_same_hash(self.borrowed.key()) { + panic!("key changed during RefMut borrow"); + } + + self.borrowed + } +} + +impl fmt::Debug for RefMutInner<'_, T, S> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.borrowed.fmt(f) + } +} diff --git a/crates/iddqd/src/id_index_map/serde_impls.rs b/crates/iddqd/src/id_index_map/serde_impls.rs new file mode 100644 index 0000000..a2eca4a --- /dev/null +++ b/crates/iddqd/src/id_index_map/serde_impls.rs @@ -0,0 +1,194 @@ +use crate::{IdHashItem, IdIndexMap, support::alloc::Allocator}; +use core::{fmt, hash::BuildHasher, marker::PhantomData}; +use serde::{ + Deserialize, Serialize, Serializer, + de::{SeqAccess, Visitor}, +}; + +/// An `IdIndexMap` serializes to the list of items in insertion order. +/// +/// Serializing as a list of items rather than as a map works around the lack of +/// non-string keys in formats like JSON, while preserving insertion order. +/// +/// # Examples +/// +/// ``` +/// # #[cfg(feature = "default-hasher")] { +/// use iddqd::{IdHashItem, IdIndexMap, id_upcast}; +/// # use iddqd_test_utils::serde_json; +/// use serde::{Deserialize, Serialize}; +/// +/// #[derive(Debug, Serialize)] +/// struct Item { +/// id: u32, +/// name: String, +/// email: String, +/// } +/// +/// // This is a complex key, so it can't be a JSON map key. +/// #[derive(Eq, Hash, PartialEq)] +/// struct ComplexKey<'a> { +/// id: u32, +/// email: &'a str, +/// } +/// +/// impl IdHashItem for Item { +/// type Key<'a> = ComplexKey<'a>; +/// fn key(&self) -> Self::Key<'_> { +/// ComplexKey { id: self.id, email: &self.email } +/// } +/// id_upcast!(); +/// } +/// +/// let mut map = IdIndexMap::::new(); +/// map.insert_unique(Item { +/// id: 1, +/// name: "Alice".to_string(), +/// email: "alice@example.com".to_string(), +/// }) +/// .unwrap(); +/// +/// // The map is serialized as a list of items in insertion order. +/// let serialized = serde_json::to_string(&map).unwrap(); +/// assert_eq!( +/// serialized, +/// r#"[{"id":1,"name":"Alice","email":"alice@example.com"}]"#, +/// ); +/// # } +/// ``` +impl Serialize + for IdIndexMap +where + T: Serialize, +{ + fn serialize( + &self, + serializer: Ser, + ) -> Result { + // TODO: Implement + // Serialize just the items in insertion order -- don't serialize the indexes. + // We'll rebuild the indexes on deserialization. + todo!() + } +} + +/// The `Deserialize` impl for `IdIndexMap` deserializes the list of items and +/// then rebuilds the indexes, producing an error if there are any duplicates. +/// +/// The `fmt::Debug` bound on `T` ensures better error reporting. +impl< + 'de, + T: IdHashItem + fmt::Debug, + S: Clone + BuildHasher + Default, + A: Default + Clone + Allocator, +> Deserialize<'de> for IdIndexMap +where + T: Deserialize<'de>, +{ + fn deserialize>( + deserializer: D, + ) -> Result { + deserializer.deserialize_seq(SeqVisitor { + _marker: PhantomData, + hasher: S::default(), + alloc: A::default(), + }) + } +} + +impl< + 'de, + T: IdHashItem + fmt::Debug + Deserialize<'de>, + S: Clone + BuildHasher, + A: Clone + Allocator, +> IdIndexMap +{ + /// Deserializes from a list of items, allocating new storage within the + /// provided allocator. + pub fn deserialize_in>( + deserializer: D, + alloc: A, + ) -> Result + where + S: Default, + { + deserializer.deserialize_seq(SeqVisitor { + _marker: PhantomData, + hasher: S::default(), + alloc, + }) + } + + /// Deserializes from a list of items, with the given hasher, using the + /// default allocator. + pub fn deserialize_with_hasher>( + deserializer: D, + hasher: S, + ) -> Result + where + A: Default, + { + deserializer.deserialize_seq(SeqVisitor { + _marker: PhantomData, + hasher, + alloc: A::default(), + }) + } + + /// Deserializes from a list of items, with the given hasher, and allocating + /// new storage within the provided allocator. + pub fn deserialize_with_hasher_in>( + deserializer: D, + hasher: S, + alloc: A, + ) -> Result { + // First, deserialize the items. + deserializer.deserialize_seq(SeqVisitor { + _marker: PhantomData, + hasher, + alloc, + }) + } +} + +struct SeqVisitor { + _marker: PhantomData T>, + hasher: S, + alloc: A, +} + +impl<'de, T, S, A> Visitor<'de> for SeqVisitor +where + T: IdHashItem + Deserialize<'de> + fmt::Debug, + S: Clone + BuildHasher, + A: Clone + Allocator, +{ + type Value = IdIndexMap; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("a sequence of items representing an IdIndexMap") + } + + fn visit_seq( + self, + mut seq: Access, + ) -> Result + where + Access: SeqAccess<'de>, + { + let mut map = match seq.size_hint() { + Some(size) => IdIndexMap::with_capacity_and_hasher_in( + size, + self.hasher, + self.alloc, + ), + None => IdIndexMap::with_hasher_in(self.hasher, self.alloc), + }; + + while let Some(element) = seq.next_element()? { + map.insert_unique(element).map_err(serde::de::Error::custom)?; + } + + Ok(map) + } +} diff --git a/crates/iddqd/src/id_index_map/tables.rs b/crates/iddqd/src/id_index_map/tables.rs new file mode 100644 index 0000000..a9b8b1a --- /dev/null +++ b/crates/iddqd/src/id_index_map/tables.rs @@ -0,0 +1,55 @@ +use crate::{ + IdHashItem, + internal::{ValidateCompact, ValidationError}, + support::{alloc::Allocator, hash_table::MapHashTable, map_hash::MapHash}, +}; +use core::hash::BuildHasher; + +#[derive(Clone, Debug, Default)] +pub(super) struct IdIndexMapTables { + pub(super) key_to_index: MapHashTable, +} + +impl IdIndexMapTables { + #[cfg(feature = "daft")] + pub(crate) fn hasher(&self) -> &S { + // TODO: store hasher here + self.key_to_index.state() + } + + pub(super) fn with_capacity_and_hasher_in( + capacity: usize, + hasher: S, + alloc: A, + ) -> Self { + Self { + key_to_index: MapHashTable::with_capacity_and_hasher_in( + capacity, hasher, alloc, + ), + } + } + + pub(super) fn validate( + &self, + expected_len: usize, + compactness: ValidateCompact, + ) -> Result<(), ValidationError> { + self.key_to_index.validate(expected_len, compactness).map_err( + |error| ValidationError::Table { name: "key_to_index", error }, + )?; + + Ok(()) + } + + pub(super) fn make_hash(&self, item: &T) -> MapHash { + let k1 = item.key(); + self.key_to_index.compute_hash(k1) + } + + pub(super) fn make_key_hash( + &self, + key: &T::Key<'_>, + ) -> MapHash { + self.key_to_index.compute_hash(key) + } +} diff --git a/crates/iddqd/src/lib.rs b/crates/iddqd/src/lib.rs index 240bb15..0178b9d 100644 --- a/crates/iddqd/src/lib.rs +++ b/crates/iddqd/src/lib.rs @@ -366,6 +366,7 @@ mod macros; pub mod bi_hash_map; pub mod errors; pub mod id_hash_map; +pub mod id_index_map; #[cfg(feature = "std")] pub mod id_ord_map; #[doc(hidden)] @@ -382,6 +383,7 @@ pub use equivalent::Comparable; #[doc(no_inline)] pub use equivalent::Equivalent; pub use id_hash_map::{imp::IdHashMap, trait_defs::IdHashItem}; +pub use id_index_map::imp::IdIndexMap; #[cfg(feature = "std")] pub use id_ord_map::{imp::IdOrdMap, trait_defs::IdOrdItem}; #[cfg(feature = "daft")] diff --git a/crates/iddqd/src/macros.rs b/crates/iddqd/src/macros.rs index 6e3edb5..5dabc05 100644 --- a/crates/iddqd/src/macros.rs +++ b/crates/iddqd/src/macros.rs @@ -1,6 +1,6 @@ //! Macros for this crate. -/// Implement upcasts for [`IdOrdMap`] or [`IdHashMap`]. +/// Implement upcasts for [`IdOrdMap`], [`IdHashMap`], or [`IdIndexMap`]. /// /// The maps in this crate require that the key types' lifetimes are covariant. /// This macro assists with implementing this requirement. @@ -10,6 +10,7 @@ /// /// [`IdOrdMap`]: crate::IdOrdMap /// [`IdHashMap`]: crate::IdHashMap +/// [`IdIndexMap`]: crate::IdIndexMap #[macro_export] macro_rules! id_upcast { () => { diff --git a/crates/iddqd/src/support/mod.rs b/crates/iddqd/src/support/mod.rs index 830debc..46ac72c 100644 --- a/crates/iddqd/src/support/mod.rs +++ b/crates/iddqd/src/support/mod.rs @@ -9,3 +9,4 @@ pub(crate) mod hash_builder; pub(crate) mod hash_table; pub(crate) mod item_set; pub(crate) mod map_hash; +pub(crate) mod ordered_set; diff --git a/crates/iddqd/src/support/ordered_set.rs b/crates/iddqd/src/support/ordered_set.rs new file mode 100644 index 0000000..0efa8fa --- /dev/null +++ b/crates/iddqd/src/support/ordered_set.rs @@ -0,0 +1,71 @@ +use super::alloc::Allocator; +use flex_array::FlexArr; + +/// An ordered map of items stored by integer index. +pub(crate) struct OrderedSet { + // A dense vector for storage. + items: FlexArr, +} + +impl Clone for OrderedSet { + fn clone(&self) -> Self { + // TODO: upstream this into flex_array + let mut items = FlexArr::with_capacity_in( + self.allocator().clone(), + self.items.capacity(), + ) + .expect("allocation succeeded"); + items.clone_from_slice(&self.items); + Self { items } + } +} + +impl Default for OrderedSet { + fn default() -> Self { + Self::with_capacity_in(0, A::default()) + } +} + +impl OrderedSet { + pub(crate) fn with_capacity_in(capacity: usize, alloc: A) -> Self { + Self { + items: FlexArr::with_capacity_in(alloc, capacity) + .expect("allocation succeeded"), + } + } + + #[inline] + pub(crate) fn allocator(&self) -> &A { + FlexArr::allocator(&self.items) + } + + #[inline] + pub(crate) fn capacity(&self) -> usize { + self.items.capacity() + } + + #[inline] + pub(crate) fn is_empty(&self) -> bool { + self.items.is_empty() + } + + #[inline] + pub(crate) fn len(&self) -> usize { + self.items.len() + } + + #[inline] + pub(crate) fn get(&self, index: usize) -> Option<&T> { + self.items.get(index) + } + + #[inline] + pub(crate) fn get_mut(&mut self, index: usize) -> Option<&mut T> { + self.items.get_mut(index) + } + + #[inline] + pub(crate) fn shift_remove(&mut self, index: usize) -> Option { + self.items.remove(index) + } +}