#![allow(non_snake_case)]
#![allow(non_upper_case_globals)]
use crate::ported::object::Object;
const OEISprimes: [usize; 35] = [
7,
13,
31,
61,
127,
251,
509,
1021,
2039,
4093,
8191,
16381,
32749,
65521,
131071,
262139,
524287,
1048573,
2097143,
4194301,
8388593,
16777213,
33554393,
67108859,
134217689,
268435399,
536870909,
1073741789,
2147483647,
4294967291,
8589934583,
17179869143,
34359738337,
68719476731,
137438953447,
];
pub fn nextPrime(n: usize) -> usize {
for &prime in &OEISprimes {
if n <= prime {
return prime;
}
}
panic!("Hashtable: no prime found");
}
struct HashtableItem {
key: u32,
probe: usize,
value: Option<Box<dyn Object>>,
}
pub struct Hashtable {
size: usize,
buckets: Vec<HashtableItem>,
items: usize,
owner: bool,
}
#[allow(dead_code)]
fn Hashtable_dump(this: &Hashtable) {
eprintln!(
"Hashtable {:p}: size={} items={} owner={}",
this as *const Hashtable,
this.size,
this.items,
if this.owner { "yes" } else { "no" }
);
let mut items = 0;
for i in 0..this.size {
let value_ptr: *const () = this.buckets[i]
.value
.as_deref()
.map_or(std::ptr::null(), |v| v as *const dyn Object as *const ());
eprintln!(
" item {:5}: key = {:5} probe = {:2} value = {:p}",
i, this.buckets[i].key, this.buckets[i].probe, value_ptr
);
if this.buckets[i].value.is_some() {
items += 1;
}
}
eprintln!(
"Hashtable {:p}: items={} counted={}",
this as *const Hashtable, this.items, items
);
}
#[allow(dead_code)]
fn Hashtable_isConsistent(this: &Hashtable) -> bool {
let mut items = 0;
for i in 0..this.size {
if this.buckets[i].value.is_some() {
items += 1;
}
}
let res = items == this.items;
if !res {
Hashtable_dump(this);
}
res
}
pub fn Hashtable_count(this: &Hashtable) -> usize {
let mut items = 0;
for bucket in this.buckets.iter() {
if bucket.value.is_some() {
items += 1;
}
}
assert!(items == this.items);
items
}
pub fn Hashtable_new(size: usize, owner: bool) -> Hashtable {
let size = if size != 0 { nextPrime(size) } else { 13 };
let buckets = (0..size)
.map(|_| HashtableItem {
key: 0,
probe: 0,
value: None,
})
.collect();
Hashtable {
items: 0,
size,
buckets,
owner,
}
}
pub fn Hashtable_delete(mut this: Hashtable) {
Hashtable_clear(&mut this);
}
pub fn Hashtable_clear(this: &mut Hashtable) {
for bucket in this.buckets.iter_mut() {
bucket.key = 0;
bucket.probe = 0;
bucket.value = None;
}
this.items = 0;
}
fn insert(this: &mut Hashtable, mut key: u32, mut value: Box<dyn Object>) {
let mut index = (key as usize) % this.size;
let mut probe: usize = 0;
loop {
if this.buckets[index].value.is_none() {
this.items += 1;
this.buckets[index].key = key;
this.buckets[index].probe = probe;
this.buckets[index].value = Some(value);
return;
}
if this.buckets[index].key == key {
this.buckets[index].value = Some(value);
return;
}
if probe > this.buckets[index].probe {
let tmp_key = this.buckets[index].key;
let tmp_probe = this.buckets[index].probe;
let tmp_value = this.buckets[index].value.take().unwrap();
this.buckets[index].key = key;
this.buckets[index].probe = probe;
this.buckets[index].value = Some(value);
key = tmp_key;
probe = tmp_probe;
value = tmp_value;
}
index = (index + 1) % this.size;
probe += 1;
}
}
pub fn Hashtable_setSize(this: &mut Hashtable, size: usize) {
if size <= this.items {
return;
}
let newSize = nextPrime(size);
if newSize == this.size {
return;
}
let newBuckets = (0..newSize)
.map(|_| HashtableItem {
key: 0,
probe: 0,
value: None,
})
.collect();
let oldBuckets = std::mem::replace(&mut this.buckets, newBuckets);
this.size = newSize;
this.items = 0;
for mut bucket in oldBuckets {
if let Some(value) = bucket.value.take() {
insert(this, bucket.key, value);
}
}
}
pub fn Hashtable_put(this: &mut Hashtable, key: u32, value: Box<dyn Object>) {
if 10 * this.items > 7 * this.size {
if usize::MAX / 2 < this.size {
panic!("Hashtable: size overflow");
}
Hashtable_setSize(this, 2 * this.size);
}
insert(this, key, value);
}
pub fn Hashtable_remove(this: &mut Hashtable, key: u32) -> Option<Box<dyn Object>> {
let mut index = (key as usize) % this.size;
let mut probe: usize = 0;
let mut res: Option<Box<dyn Object>> = None;
while this.buckets[index].value.is_some() {
if this.buckets[index].key == key {
let removed = this.buckets[index].value.take();
if this.owner {
drop(removed);
} else {
res = removed;
}
let mut next = (index + 1) % this.size;
while this.buckets[next].value.is_some() && this.buckets[next].probe > 0 {
let key_n = this.buckets[next].key;
let probe_n = this.buckets[next].probe;
let value_n = this.buckets[next].value.take();
this.buckets[index].key = key_n;
this.buckets[index].probe = probe_n - 1;
this.buckets[index].value = value_n;
index = next;
next = (index + 1) % this.size;
}
this.buckets[index].value = None;
this.items -= 1;
break;
}
if this.buckets[index].probe < probe {
break;
}
index = (index + 1) % this.size;
probe += 1;
}
if 8 * this.items < this.size {
Hashtable_setSize(this, this.size / 3);
}
res
}
pub fn Hashtable_get(this: &Hashtable, key: u32) -> Option<&dyn Object> {
let mut index = (key as usize) % this.size;
let mut probe: usize = 0;
let mut res: Option<&dyn Object> = None;
while this.buckets[index].value.is_some() {
if this.buckets[index].key == key {
res = this.buckets[index].value.as_deref();
break;
}
if this.buckets[index].probe < probe {
break;
}
index = if index + 1 != this.size { index + 1 } else { 0 };
probe += 1;
}
res
}
pub fn Hashtable_foreach(this: &Hashtable, f: &mut dyn FnMut(u32, &dyn Object)) {
for walk in this.buckets.iter() {
if let Some(value) = walk.value.as_deref() {
f(walk.key, value);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ported::object::{Object, ObjectClass};
#[test]
fn next_prime_matches_table_entries() {
assert_eq!(nextPrime(0), 7);
assert_eq!(nextPrime(7), 7);
assert_eq!(nextPrime(8), 13);
assert_eq!(nextPrime(13), 13);
assert_eq!(nextPrime(14), 31);
assert_eq!(nextPrime(1048573), 1048573);
assert_eq!(nextPrime(1048574), 2097143);
assert_eq!(nextPrime(137438953447), 137438953447);
}
#[test]
fn next_prime_is_identity_on_every_table_entry() {
for &prime in &OEISprimes {
assert_eq!(nextPrime(prime), prime, "prime={prime}");
}
}
#[test]
#[should_panic(expected = "Hashtable: no prime found")]
fn next_prime_panics_when_no_prime_large_enough() {
nextPrime(usize::MAX);
}
static VAL_class: ObjectClass = ObjectClass { extends: None };
struct Val {
n: u32,
}
impl Object for Val {
fn klass(&self) -> &'static ObjectClass {
&VAL_class
}
}
fn val_of(o: &dyn Object) -> u32 {
let any: &dyn core::any::Any = o;
any.downcast_ref::<Val>().expect("value is a Val").n
}
#[test]
fn new_rounds_size_to_prime_and_defaults_to_13() {
let ht = Hashtable_new(0, true);
assert_eq!(ht.size, 13);
assert_eq!(ht.items, 0);
assert_eq!(Hashtable_count(&ht), 0);
assert_eq!(Hashtable_new(10, false).size, 13);
assert_eq!(Hashtable_new(20, false).size, 31);
assert_eq!(Hashtable_new(31, false).size, 31);
}
#[test]
fn put_get_roundtrip() {
let mut ht = Hashtable_new(0, true);
Hashtable_put(&mut ht, 1, Box::new(Val { n: 1 }));
Hashtable_put(&mut ht, 2, Box::new(Val { n: 2 }));
Hashtable_put(&mut ht, 100, Box::new(Val { n: 100 }));
assert_eq!(val_of(Hashtable_get(&ht, 1).unwrap()), 1);
assert_eq!(val_of(Hashtable_get(&ht, 2).unwrap()), 2);
assert_eq!(val_of(Hashtable_get(&ht, 100).unwrap()), 100);
assert!(Hashtable_get(&ht, 999).is_none());
assert_eq!(ht.items, 3);
assert_eq!(Hashtable_count(&ht), 3);
}
#[test]
fn put_same_key_overwrites_without_growing_items() {
let mut ht = Hashtable_new(0, true);
Hashtable_put(&mut ht, 7, Box::new(Val { n: 70 }));
assert_eq!(val_of(Hashtable_get(&ht, 7).unwrap()), 70);
assert_eq!(ht.items, 1);
Hashtable_put(&mut ht, 7, Box::new(Val { n: 71 }));
assert_eq!(val_of(Hashtable_get(&ht, 7).unwrap()), 71);
assert_eq!(ht.items, 1);
assert_eq!(Hashtable_count(&ht), 1);
}
#[test]
fn remove_then_get_returns_none() {
let mut ht = Hashtable_new(0, false);
Hashtable_put(&mut ht, 3, Box::new(Val { n: 33 }));
Hashtable_put(&mut ht, 4, Box::new(Val { n: 44 }));
let removed = Hashtable_remove(&mut ht, 3);
assert_eq!(val_of(removed.as_deref().unwrap()), 33);
assert!(Hashtable_get(&ht, 3).is_none());
assert_eq!(val_of(Hashtable_get(&ht, 4).unwrap()), 44);
assert_eq!(ht.items, 1);
assert_eq!(Hashtable_count(&ht), 1);
assert!(Hashtable_remove(&mut ht, 999).is_none());
assert_eq!(ht.items, 1);
let mut owned = Hashtable_new(0, true);
Hashtable_put(&mut owned, 5, Box::new(Val { n: 55 }));
assert!(Hashtable_remove(&mut owned, 5).is_none());
assert!(Hashtable_get(&owned, 5).is_none());
assert_eq!(owned.items, 0);
}
#[test]
fn colliding_keys_probe_and_all_resolve() {
let mut ht = Hashtable_new(0, true);
assert_eq!(ht.size, 13);
let keys = [5u32, 18, 31, 44];
for &k in &keys {
assert_eq!((k as usize) % ht.size, 5, "key {k} must collide at 5");
Hashtable_put(&mut ht, k, Box::new(Val { n: k }));
}
for &k in &keys {
assert_eq!(val_of(Hashtable_get(&ht, k).unwrap()), k);
}
assert_eq!(ht.items, 4);
assert_eq!(Hashtable_count(&ht), 4);
let mut ht2 = Hashtable_new(0, false);
for &k in &keys {
Hashtable_put(&mut ht2, k, Box::new(Val { n: k }));
}
let removed = Hashtable_remove(&mut ht2, 18);
assert_eq!(val_of(removed.as_deref().unwrap()), 18);
assert!(Hashtable_get(&ht2, 18).is_none());
for &k in &[5u32, 31, 44] {
assert_eq!(
val_of(Hashtable_get(&ht2, k).unwrap()),
k,
"key {k} after shift"
);
}
assert_eq!(Hashtable_count(&ht2), 3);
}
#[test]
fn foreach_visits_every_filled_bucket() {
let mut ht = Hashtable_new(0, true);
let keys = [1u32, 14, 27, 100, 250];
for &k in &keys {
Hashtable_put(&mut ht, k, Box::new(Val { n: k }));
}
let mut seen: Vec<(u32, u32)> = Vec::new();
Hashtable_foreach(&ht, &mut |k, v| seen.push((k, val_of(v))));
assert_eq!(seen.len(), keys.len());
seen.sort_unstable();
let mut expected: Vec<(u32, u32)> = keys.iter().map(|&k| (k, k)).collect();
expected.sort_unstable();
assert_eq!(seen, expected);
}
#[test]
fn many_puts_trigger_resize_and_preserve_all_entries() {
let mut ht = Hashtable_new(0, true);
assert_eq!(ht.size, 13);
let count: u32 = 50;
for k in 0..count {
Hashtable_put(&mut ht, k, Box::new(Val { n: k }));
}
assert!(ht.size > 13, "expected a resize, size still {}", ht.size);
assert_eq!(ht.items, count as usize);
assert_eq!(Hashtable_count(&ht), count as usize);
for k in 0..count {
assert_eq!(
val_of(Hashtable_get(&ht, k).unwrap()),
k,
"key {k} lost in resize"
);
}
assert!(ht.size > ht.items);
}
#[test]
fn clear_empties_the_table() {
let mut ht = Hashtable_new(0, true);
for k in 0..10u32 {
Hashtable_put(&mut ht, k, Box::new(Val { n: k }));
}
assert_eq!(ht.items, 10);
Hashtable_clear(&mut ht);
assert_eq!(ht.items, 0);
assert_eq!(Hashtable_count(&ht), 0);
for k in 0..10u32 {
assert!(Hashtable_get(&ht, k).is_none());
}
}
#[test]
fn remove_shrinks_on_low_load_factor() {
let mut ht = Hashtable_new(0, false);
for k in 0..40u32 {
Hashtable_put(&mut ht, k, Box::new(Val { n: k }));
}
let grown = ht.size;
assert!(grown > 13);
for k in 0..38u32 {
Hashtable_remove(&mut ht, k);
}
assert!(
ht.size < grown,
"expected shrink from {grown}, still {}",
ht.size
);
assert_eq!(ht.items, 2);
assert_eq!(Hashtable_count(&ht), 2);
assert_eq!(val_of(Hashtable_get(&ht, 38).unwrap()), 38);
assert_eq!(val_of(Hashtable_get(&ht, 39).unwrap()), 39);
}
}