use super::{
ItemIndex,
alloc::{AllocWrapper, Allocator},
item_set::IndexRemap,
map_hash::MapHash,
};
use crate::internal::{TableValidationError, ValidateCompact};
use alloc::{collections::BTreeSet, vec::Vec};
use core::{
fmt,
hash::{BuildHasher, Hash},
};
use equivalent::Equivalent;
use hashbrown::{HashTable, hash_table};
#[derive(Clone, Debug)]
pub(crate) struct HashedIndex {
pub(crate) ix: ItemIndex,
pub(crate) hash: u64,
}
impl HashedIndex {
#[inline]
fn new(ix: ItemIndex, hash: u64) -> Self {
Self { ix, hash }
}
}
#[inline]
fn cached_hasher(stored: &HashedIndex) -> u64 {
stored.hash
}
#[derive(Clone, Default)]
pub(crate) struct MapHashTable<A: Allocator> {
pub(super) items: HashTable<HashedIndex, AllocWrapper<A>>,
}
impl<A: Allocator> fmt::Debug for MapHashTable<A> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("MapHashTable").field("items", &self.items).finish()
}
}
impl<A: Allocator> MapHashTable<A> {
pub(crate) const fn new_in(alloc: A) -> Self {
Self { items: HashTable::new_in(AllocWrapper(alloc)) }
}
pub(crate) fn with_capacity_in(capacity: usize, alloc: A) -> Self {
Self {
items: HashTable::with_capacity_in(capacity, AllocWrapper(alloc)),
}
}
pub(crate) fn len(&self) -> usize {
self.items.len()
}
pub(crate) fn validate(
&self,
expected_len: usize,
compactness: ValidateCompact,
) -> Result<(), TableValidationError> {
if self.len() != expected_len {
return Err(TableValidationError::new(format!(
"expected length {expected_len}, was {}",
self.len()
)));
}
match compactness {
ValidateCompact::Compact => {
let mut values: Vec<_> =
self.items.iter().map(|h| h.ix).collect();
values.sort_unstable();
for (i, value) in values.iter().enumerate() {
if value.as_u32() as usize != i {
return Err(TableValidationError::new(format!(
"expected value at index {i} to be {i}, was {value}"
)));
}
}
}
ValidateCompact::NonCompact => {
let values: Vec<_> = self.items.iter().map(|h| h.ix).collect();
let value_set: BTreeSet<_> = values.iter().copied().collect();
if value_set.len() != values.len() {
return Err(TableValidationError::new(format!(
"expected no duplicates, but found {} duplicates \
(values: {:?})",
values.len() - value_set.len(),
values,
)));
}
}
}
Ok(())
}
pub(crate) fn compute_hash<S: BuildHasher, K: Hash + Eq>(
&self,
state: &S,
key: K,
) -> MapHash {
MapHash { hash: state.hash_one(key) }
}
pub(crate) fn find_index<S: BuildHasher, K, Q, F>(
&self,
state: &S,
key: &Q,
lookup: F,
) -> Option<ItemIndex>
where
F: Fn(ItemIndex) -> K,
Q: ?Sized + Hash + Equivalent<K>,
{
let hash = state.hash_one(key);
self.items
.find(hash, |stored| key.equivalent(&lookup(stored.ix)))
.map(|stored| stored.ix)
}
pub(crate) fn entry<S: BuildHasher, K: Hash + Eq, F>(
&mut self,
state: &S,
key: K,
lookup: F,
) -> Entry<'_, A>
where
F: Fn(ItemIndex) -> K,
{
let hash = state.hash_one(&key);
match self.items.entry(
hash,
|stored| lookup(stored.ix) == key,
cached_hasher,
) {
hash_table::Entry::Occupied(inner) => {
Entry::Occupied(OccupiedEntry { inner })
}
hash_table::Entry::Vacant(inner) => {
Entry::Vacant(VacantEntry { inner, hash })
}
}
}
pub(crate) fn find_entry_by_hash<F>(
&mut self,
hash: u64,
mut f: F,
) -> Result<OccupiedEntry<'_, A>, ()>
where
F: FnMut(ItemIndex) -> bool,
{
match self.items.find_entry(hash, |stored| f(stored.ix)) {
Ok(inner) => Ok(OccupiedEntry { inner }),
Err(_) => Err(()),
}
}
pub(crate) fn retain<F>(&mut self, mut f: F)
where
F: FnMut(ItemIndex) -> bool,
{
self.items.retain(|stored| f(stored.ix));
}
pub(crate) fn remove_by_index(&mut self, ix: ItemIndex) {
let mut found = false;
self.items.retain(|stored| {
if !found && stored.ix == ix {
found = true;
false
} else {
true
}
});
assert!(
found,
"linear scan should locate the index that find_entry_by_hash missed"
);
}
#[inline]
pub(crate) fn clear(&mut self) {
self.items.clear();
}
#[inline]
pub(crate) fn reserve(&mut self, additional: usize) {
self.items.reserve(additional, cached_hasher);
}
pub(crate) fn remap_indexes(&mut self, remap: &IndexRemap) {
for stored in self.items.iter_mut() {
stored.ix = remap.remap(stored.ix);
}
}
#[inline]
pub(crate) fn shrink_to_fit(&mut self) {
self.items.shrink_to_fit(cached_hasher);
}
#[inline]
pub(crate) fn shrink_to(&mut self, min_capacity: usize) {
self.items.shrink_to(min_capacity, cached_hasher);
}
#[inline]
pub(crate) fn try_reserve(
&mut self,
additional: usize,
) -> Result<(), hashbrown::TryReserveError> {
self.items.try_reserve(additional, cached_hasher)
}
#[cfg(all(test, feature = "std"))]
pub(crate) fn reserve_counting_rehash(
&mut self,
additional: usize,
) -> usize {
let count = core::cell::Cell::new(0usize);
self.items.reserve(additional, |stored| {
count.set(count.get() + 1);
cached_hasher(stored)
});
count.into_inner()
}
}
pub(crate) enum Entry<'a, A: Allocator> {
Occupied(OccupiedEntry<'a, A>),
Vacant(VacantEntry<'a, A>),
}
pub(crate) struct OccupiedEntry<'a, A: Allocator> {
inner: hash_table::OccupiedEntry<'a, HashedIndex, AllocWrapper<A>>,
}
impl<'a, A: Allocator> OccupiedEntry<'a, A> {
#[inline]
pub(crate) fn get(&self) -> ItemIndex {
self.inner.get().ix
}
#[inline]
pub(crate) fn remove(self) {
let _ = self.inner.remove();
}
}
pub(crate) struct VacantEntry<'a, A: Allocator> {
inner: hash_table::VacantEntry<'a, HashedIndex, AllocWrapper<A>>,
hash: u64,
}
impl<'a, A: Allocator> VacantEntry<'a, A> {
#[inline]
pub(crate) fn insert(self, ix: ItemIndex) {
let _ = self.inner.insert(HashedIndex::new(ix, self.hash));
}
}