use std::collections::hash_map::DefaultHasher;
use std::fmt;
use std::hash::{Hash, Hasher};
const DEFAULT_CAPACITY: usize = 16;
const MAX_KICKS: usize = 500;
const MAX_LOAD_FACTOR: f64 = 0.45;
const SEED1: u64 = 0x9e37_79b9_7f4a_7c15;
const SEED2: u64 = 0x6c62_272e_07bb_0142;
#[derive(Clone, Debug)]
struct Slot<K, V> {
entry: Option<(K, V)>,
}
impl<K, V> Slot<K, V> {
const fn empty() -> Self {
Slot { entry: None }
}
fn is_empty(&self) -> bool {
self.entry.is_none()
}
}
pub struct CuckooHashMap<K: Hash + Eq + Clone, V: Clone> {
table1: Vec<Slot<K, V>>,
table2: Vec<Slot<K, V>>,
cap: usize,
len: usize,
seed1: u64,
seed2: u64,
}
impl<K: Hash + Eq + Clone, V: Clone> CuckooHashMap<K, V> {
pub fn new() -> Self {
Self::with_capacity(DEFAULT_CAPACITY)
}
pub fn with_capacity(capacity: usize) -> Self {
let cap = next_power_of_two(capacity.max(DEFAULT_CAPACITY));
CuckooHashMap {
table1: vec![Slot::empty(); cap],
table2: vec![Slot::empty(); cap],
cap,
len: 0,
seed1: SEED1,
seed2: SEED2,
}
}
pub fn insert(&mut self, key: K, value: V) -> Option<V> {
if let Some(v) = self.get_mut_internal(&key) {
return Some(std::mem::replace(v, value));
}
if self.load_factor() >= MAX_LOAD_FACTOR {
self.rehash(self.cap * 2);
}
self.insert_no_update(key, value);
None
}
pub fn get(&self, key: &K) -> Option<&V> {
let (i1, i2) = self.indices(key);
if let Some((k, v)) = &self.table1[i1].entry {
if k == key {
return Some(v);
}
}
if let Some((k, v)) = &self.table2[i2].entry {
if k == key {
return Some(v);
}
}
None
}
pub fn get_mut(&mut self, key: &K) -> Option<&mut V> {
let (i1, i2) = self.indices(key);
if let Some((k, _)) = &self.table1[i1].entry {
if k == key {
return self.table1[i1].entry.as_mut().map(|(_, v)| v);
}
}
if let Some((k, _)) = &self.table2[i2].entry {
if k == key {
return self.table2[i2].entry.as_mut().map(|(_, v)| v);
}
}
None
}
pub fn remove(&mut self, key: &K) -> Option<V> {
let (i1, i2) = self.indices(key);
if let Some((k, _)) = &self.table1[i1].entry {
if k == key {
self.len -= 1;
return self.table1[i1].entry.take().map(|(_, v)| v);
}
}
if let Some((k, _)) = &self.table2[i2].entry {
if k == key {
self.len -= 1;
return self.table2[i2].entry.take().map(|(_, v)| v);
}
}
None
}
pub fn contains_key(&self, key: &K) -> bool {
self.get(key).is_some()
}
pub fn len(&self) -> usize {
self.len
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
pub fn capacity(&self) -> usize {
self.cap * 2
}
pub fn load_factor(&self) -> f64 {
self.len as f64 / (self.cap * 2) as f64
}
pub fn clear(&mut self) {
for slot in &mut self.table1 {
slot.entry = None;
}
for slot in &mut self.table2 {
slot.entry = None;
}
self.len = 0;
}
pub fn iter(&self) -> impl Iterator<Item = (&K, &V)> {
let iter1 = self.table1.iter().filter_map(|s| {
s.entry.as_ref().map(|(k, v)| (k, v))
});
let iter2 = self.table2.iter().filter_map(|s| {
s.entry.as_ref().map(|(k, v)| (k, v))
});
iter1.chain(iter2)
}
pub fn keys(&self) -> impl Iterator<Item = &K> {
self.iter().map(|(k, _)| k)
}
pub fn values(&self) -> impl Iterator<Item = &V> {
self.iter().map(|(_, v)| v)
}
fn indices(&self, key: &K) -> (usize, usize) {
let h1 = hash_with_seed(key, self.seed1) as usize & (self.cap - 1);
let h2 = hash_with_seed(key, self.seed2) as usize & (self.cap - 1);
(h1, h2)
}
fn get_mut_internal(&mut self, key: &K) -> Option<&mut V> {
let (i1, i2) = self.indices(key);
if let Some((k, _)) = &self.table1[i1].entry {
if k == key {
return self.table1[i1].entry.as_mut().map(|(_, v)| v);
}
}
if let Some((k, _)) = &self.table2[i2].entry {
if k == key {
return self.table2[i2].entry.as_mut().map(|(_, v)| v);
}
}
None
}
fn insert_no_update(&mut self, key: K, value: V) {
let mut current_key = key;
let mut current_value = value;
for kick in 0..MAX_KICKS {
let i1 = hash_with_seed(¤t_key, self.seed1) as usize & (self.cap - 1);
if self.table1[i1].is_empty() {
self.table1[i1].entry = Some((current_key, current_value));
self.len += 1;
return;
}
let evicted = self.table1[i1].entry.take().expect("slot was occupied");
self.table1[i1].entry = Some((current_key, current_value));
current_key = evicted.0;
current_value = evicted.1;
let i2 = hash_with_seed(¤t_key, self.seed2) as usize & (self.cap - 1);
if self.table2[i2].is_empty() {
self.table2[i2].entry = Some((current_key, current_value));
self.len += 1;
return;
}
let evicted2 = self.table2[i2].entry.take().expect("slot was occupied");
self.table2[i2].entry = Some((current_key, current_value));
current_key = evicted2.0;
current_value = evicted2.1;
if kick > 0 && kick % (MAX_KICKS / 5) == 0 {
self.rehash_with_pair(self.cap * 2, current_key, current_value);
return;
}
}
self.rehash_with_pair(self.cap * 2, current_key, current_value);
}
fn rehash_with_pair(&mut self, new_cap: usize, extra_key: K, extra_value: V) {
self.rehash(new_cap);
self.insert_no_update(extra_key, extra_value);
}
fn rehash(&mut self, new_cap: usize) {
let new_cap = next_power_of_two(new_cap.max(DEFAULT_CAPACITY));
let new_seed1 = self.seed1.wrapping_add(0x517c_c1b7_2722_0a95);
let new_seed2 = self.seed2.wrapping_add(0xbea2_25a5_8b20_e1b7);
let mut new_table1: Vec<Slot<K, V>> = vec![Slot::empty(); new_cap];
let mut new_table2: Vec<Slot<K, V>> = vec![Slot::empty(); new_cap];
let entries: Vec<(K, V)> = self
.table1
.iter_mut()
.chain(self.table2.iter_mut())
.filter_map(|s| s.entry.take())
.collect();
let old_len = entries.len();
let old_table1 = std::mem::replace(&mut self.table1, new_table1);
let old_table2 = std::mem::replace(&mut self.table2, new_table2);
let old_cap = std::mem::replace(&mut self.cap, new_cap);
let old_seed1 = std::mem::replace(&mut self.seed1, new_seed1);
let old_seed2 = std::mem::replace(&mut self.seed2, new_seed2);
self.len = 0;
for (k, v) in entries {
self.insert_no_update(k, v);
}
let _ = (old_table1, old_table2, old_cap, old_seed1, old_seed2, old_len);
}
}
impl<K: Hash + Eq + Clone, V: Clone> fmt::Debug for CuckooHashMap<K, V>
where
K: fmt::Debug,
V: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_map().entries(self.iter()).finish()
}
}
impl<K: Hash + Eq + Clone, V: Clone> Clone for CuckooHashMap<K, V> {
fn clone(&self) -> Self {
CuckooHashMap {
table1: self.table1.clone(),
table2: self.table2.clone(),
cap: self.cap,
len: self.len,
seed1: self.seed1,
seed2: self.seed2,
}
}
}
impl<K: Hash + Eq + Clone, V: Clone> Default for CuckooHashMap<K, V> {
fn default() -> Self {
Self::new()
}
}
fn hash_with_seed<T: Hash>(item: &T, seed: u64) -> u64 {
let mut h = DefaultHasher::new();
seed.hash(&mut h);
item.hash(&mut h);
h.finish()
}
fn next_power_of_two(n: usize) -> usize {
if n <= 1 {
return 1;
}
let mut p = 1usize;
while p < n {
p <<= 1;
}
p
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn basic_insert_get() {
let mut m = CuckooHashMap::new();
m.insert("alpha", 1i32);
m.insert("beta", 2i32);
m.insert("gamma", 3i32);
assert_eq!(m.get(&"alpha"), Some(&1));
assert_eq!(m.get(&"beta"), Some(&2));
assert_eq!(m.get(&"gamma"), Some(&3));
assert_eq!(m.get(&"delta"), None);
assert_eq!(m.len(), 3);
}
#[test]
fn update_existing_key() {
let mut m = CuckooHashMap::new();
m.insert("key", 10i32);
let old = m.insert("key", 20i32);
assert_eq!(old, Some(10));
assert_eq!(m.get(&"key"), Some(&20));
assert_eq!(m.len(), 1);
}
#[test]
fn remove_existing_and_absent() {
let mut m = CuckooHashMap::new();
m.insert(1u64, "one");
m.insert(2u64, "two");
assert_eq!(m.remove(&1u64), Some("one"));
assert_eq!(m.len(), 1);
assert_eq!(m.remove(&1u64), None);
assert_eq!(m.remove(&99u64), None);
}
#[test]
fn contains_key() {
let mut m = CuckooHashMap::new();
m.insert(42u32, "forty-two");
assert!(m.contains_key(&42u32));
assert!(!m.contains_key(&0u32));
}
#[test]
fn large_insert_no_data_loss() {
let mut m = CuckooHashMap::new();
for i in 0u64..512 {
m.insert(i, i * i);
}
assert_eq!(m.len(), 512);
for i in 0u64..512 {
assert_eq!(m.get(&i), Some(&(i * i)), "missing entry {i}");
}
}
#[test]
fn clear_empties_map() {
let mut m = CuckooHashMap::new();
for i in 0u64..100 {
m.insert(i, i);
}
m.clear();
assert!(m.is_empty());
assert_eq!(m.len(), 0);
assert_eq!(m.get(&0u64), None);
}
#[test]
fn iter_yields_all_entries() {
let mut m = CuckooHashMap::new();
let entries: Vec<(u64, u64)> = (0u64..50).map(|i| (i, i * 2)).collect();
for (k, v) in &entries {
m.insert(*k, *v);
}
let mut collected: Vec<(u64, u64)> = m.iter().map(|(&k, &v)| (k, v)).collect();
let mut expected = entries;
collected.sort();
expected.sort();
assert_eq!(collected, expected);
}
#[test]
fn get_mut_updates_in_place() {
let mut m = CuckooHashMap::new();
m.insert("x", 0i32);
if let Some(v) = m.get_mut(&"x") {
*v += 99;
}
assert_eq!(m.get(&"x"), Some(&99));
}
}