use alloc::vec::Vec;
use core::hash::{BuildHasher, Hash};
use hashbrown::DefaultHashBuilder;
use hashbrown::HashMap;
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
#[repr(transparent)]
pub struct InternId(u32);
impl InternId {
#[inline]
#[must_use]
pub fn as_usize(self) -> usize {
self.0 as usize
}
#[inline]
#[must_use]
pub fn as_u32(self) -> u32 {
self.0
}
}
impl crate::DenseKey for InternId {
#[inline]
fn index(self) -> usize {
self.as_usize()
}
}
#[derive(Debug, Clone)]
pub struct Interner<K> {
keys: Vec<K>,
buckets: HashMap<u64, Vec<InternId>>,
build_hasher: DefaultHashBuilder,
}
impl<K> Default for Interner<K>
where
K: Eq + Hash,
{
fn default() -> Self {
Self::new()
}
}
impl<K> Interner<K>
where
K: Eq + Hash,
{
#[must_use]
pub fn new() -> Self {
Self {
keys: Vec::new(),
buckets: HashMap::new(),
build_hasher: DefaultHashBuilder::default(),
}
}
#[must_use]
pub fn len(&self) -> usize {
self.keys.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.keys.is_empty()
}
#[must_use]
pub fn get(&self, id: InternId) -> Option<&K> {
self.keys.get(id.as_usize())
}
pub fn intern(&mut self, key: K) -> InternId {
let hash = self.hash(&key);
if let Some(ids) = self.buckets.get(&hash) {
for &id in ids {
if self.keys[id.as_usize()] == key {
return id;
}
}
}
let id = InternId(
u32::try_from(self.keys.len()).expect("too many interned keys for InternId (u32)"),
);
self.keys.push(key);
self.buckets.entry(hash).or_default().push(id);
id
}
pub fn clear(&mut self) {
self.keys.clear();
self.buckets.clear();
}
fn hash<Q>(&self, key: &Q) -> u64
where
Q: Hash + ?Sized,
{
self.build_hasher.hash_one(key)
}
}
#[cfg(test)]
mod tests {
extern crate std;
use super::*;
#[derive(Debug, Eq, PartialEq, Hash)]
struct Key(&'static str);
#[test]
fn interns_duplicates_to_same_id() {
let mut i = Interner::<Key>::new();
let a0 = i.intern(Key("a"));
let a1 = i.intern(Key("a"));
let b = i.intern(Key("b"));
assert_eq!(a0, a1);
assert_ne!(a0, b);
assert_eq!(i.get(a0).unwrap(), &Key("a"));
assert_eq!(i.get(b).unwrap(), &Key("b"));
}
}