pub const HT_INIT_SIZE: usize = 16;
const PERTURB_SHIFT: u32 = 5;
#[derive(Clone, Debug, Default)]
pub(crate) enum Slot<V> {
#[default]
Empty,
Removed,
Used { hash: u64, key: String, value: V },
}
#[derive(Clone, Debug)]
pub struct hashtab_T<V> {
pub(crate) ht_array: Vec<Slot<V>>,
pub(crate) ht_mask: usize,
pub(crate) ht_used: usize,
pub(crate) ht_filled: usize,
}
impl<V> Default for hashtab_T<V> {
fn default() -> Self {
Self {
ht_array: (0..HT_INIT_SIZE).map(|_| Slot::Empty).collect(),
ht_mask: HT_INIT_SIZE - 1,
ht_used: 0,
ht_filled: 0,
}
}
}
pub fn hash_hash(key: &str) -> u64 {
let b = key.as_bytes();
let Some(&first) = b.first() else {
return 0;
};
let mut hash = first as u64;
for &c in &b[1..] {
hash = hash.wrapping_mul(101).wrapping_add(c as u64);
}
hash
}
impl<V> hashtab_T<V> {
pub(crate) fn hash_lookup(&self, key: &str, hash: u64) -> (usize, bool) {
let mut idx = (hash as usize) & self.ht_mask;
let mut freeitem: Option<usize> = None;
match &self.ht_array[idx] {
Slot::Empty => return (idx, false),
Slot::Removed => freeitem = Some(idx),
Slot::Used {
hash: h, key: k, ..
} => {
if *h == hash && k == key {
return (idx, true);
}
}
}
let mut perturb = hash;
loop {
idx = idx
.wrapping_mul(5)
.wrapping_add(perturb as usize)
.wrapping_add(1);
let i = idx & self.ht_mask;
match &self.ht_array[i] {
Slot::Empty => return (freeitem.unwrap_or(i), false),
Slot::Removed => {
if freeitem.is_none() {
freeitem = Some(i);
}
}
Slot::Used {
hash: h, key: k, ..
} => {
if *h == hash && k == key {
return (i, true);
}
}
}
perturb >>= PERTURB_SHIFT;
}
}
pub(crate) fn hash_add_item(&mut self, idx: usize, key: String, hash: u64, value: V) {
self.ht_used += 1;
if matches!(self.ht_array[idx], Slot::Empty) {
self.ht_filled += 1;
}
self.ht_array[idx] = Slot::Used { hash, key, value };
self.hash_may_resize(0);
}
pub(crate) fn hash_remove(&mut self, idx: usize) -> Option<V> {
let old = std::mem::replace(&mut self.ht_array[idx], Slot::Removed);
let Slot::Used { value, .. } = old else {
self.ht_array[idx] = old;
return None;
};
self.ht_used -= 1;
self.hash_may_resize(0);
Some(value)
}
pub(crate) fn hash_may_resize(&mut self, minitems: usize) {
let oldsize = self.ht_mask + 1;
let minsize = if minitems == 0 {
if self.ht_filled < HT_INIT_SIZE - 1 && oldsize == HT_INIT_SIZE {
return;
}
if self.ht_filled * 3 < oldsize * 2 && self.ht_used > oldsize / 5 {
return;
}
if self.ht_used > 1000 {
self.ht_used * 2
} else {
self.ht_used * 4
}
} else {
(minitems.max(self.ht_used) * 3 + 1) / 2
};
let mut newsize = HT_INIT_SIZE;
while newsize < minsize {
newsize <<= 1;
}
if newsize != HT_INIT_SIZE && newsize == oldsize && self.ht_filled * 3 < oldsize * 2 {
return;
}
let newmask = newsize - 1;
let mut newarray: Vec<Slot<V>> = (0..newsize).map(|_| Slot::Empty).collect();
for item in std::mem::take(&mut self.ht_array) {
let Slot::Used { hash, .. } = &item else {
continue;
};
let hash = *hash;
let mut newi = (hash as usize) & newmask;
if !matches!(newarray[newi], Slot::Empty) {
let mut perturb = hash;
loop {
newi = newi
.wrapping_mul(5)
.wrapping_add(perturb as usize)
.wrapping_add(1);
if matches!(newarray[newi & newmask], Slot::Empty) {
break;
}
perturb >>= PERTURB_SHIFT;
}
newi &= newmask;
}
newarray[newi] = item;
}
self.ht_array = newarray;
self.ht_mask = newmask;
self.ht_filled = self.ht_used;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn iterates_in_vim_bucket_order() {
let mut ht: hashtab_T<i64> = hashtab_T::default();
for (k, v) in [("x", 1), ("b", 2), ("q", 3), ("a", 4)] {
ht.insert(k.to_string(), v);
}
let got: Vec<&str> = ht.keys().map(String::as_str).collect();
assert_eq!(got, ["q", "b", "a", "x"]);
}
#[test]
fn iterates_in_vim_bucket_order_3keys() {
let mut ht: hashtab_T<i64> = hashtab_T::default();
for (k, v) in [("z", 1), ("a", 2), ("m", 3)] {
ht.insert(k.to_string(), v);
}
let got: Vec<&str> = ht.keys().map(String::as_str).collect();
assert_eq!(got, ["a", "z", "m"]);
}
#[test]
fn removed_slot_does_not_break_lookup() {
let mut ht: hashtab_T<i64> = hashtab_T::default();
for i in 0..12 {
ht.insert(format!("key{i}"), i);
}
for i in 0..12 {
if i % 2 == 0 {
assert_eq!(ht.shift_remove(&format!("key{i}")), Some(i));
}
}
for i in 0..12 {
let want = (i % 2 != 0).then_some(i);
assert_eq!(ht.get(&format!("key{i}")).copied(), want, "key{i}");
}
assert_eq!(ht.len(), 6);
}
#[test]
fn overwrite_keeps_position() {
let mut ht: hashtab_T<i64> = hashtab_T::default();
for (k, v) in [("x", 1), ("b", 2), ("q", 3)] {
ht.insert(k.to_string(), v);
}
let before: Vec<String> = ht.keys().cloned().collect();
assert_eq!(ht.insert("b".into(), 99), Some(2));
let after: Vec<String> = ht.keys().cloned().collect();
assert_eq!(before, after);
assert_eq!(ht.get("b").copied(), Some(99));
}
#[test]
fn grows_and_keeps_every_key() {
let mut ht: hashtab_T<i64> = hashtab_T::default();
for i in 0..500 {
ht.insert(format!("k{i}"), i);
}
assert_eq!(ht.len(), 500);
for i in 0..500 {
assert_eq!(ht.get(&format!("k{i}")).copied(), Some(i));
}
assert_eq!(ht.iter().count(), 500);
}
}