use std::hash::{BuildHasher, Hasher};
const SEED: u64 = 0x51_7c_c1_b7_27_22_0a_95;
const WORD: u64 = 0x9E37_79B9_7F4A_7C15;
const ROTATE: u32 = 5;
#[derive(Clone, Copy, Debug)]
pub struct TableHasher {
state: u64,
}
impl Default for TableHasher {
fn default() -> TableHasher {
TableHasher { state: SEED }
}
}
impl TableHasher {
#[inline]
fn fold(&mut self, word: u64) {
self.state = self.state.rotate_left(ROTATE) ^ word.wrapping_mul(WORD);
self.state = self.state.wrapping_mul(SEED);
}
}
impl Hasher for TableHasher {
#[inline]
fn write(&mut self, bytes: &[u8]) {
let mut rest = bytes;
while let Some(head) = rest.get(..8) {
let mut word = [0u8; 8];
word.copy_from_slice(head);
self.fold(u64::from_le_bytes(word));
rest = rest.get(8..).unwrap_or(&[]);
}
if !rest.is_empty() {
let mut word = [0u8; 8];
if let Some(slot) = word.get_mut(..rest.len()) {
slot.copy_from_slice(rest);
}
self.fold(u64::from_le_bytes(word));
}
self.fold(bytes.len() as u64);
}
#[inline]
fn write_u8(&mut self, value: u8) {
self.fold(u64::from(value));
}
#[inline]
fn write_u32(&mut self, value: u32) {
self.fold(u64::from(value));
}
#[inline]
fn write_u64(&mut self, value: u64) {
self.fold(value);
}
#[inline]
fn write_usize(&mut self, value: usize) {
self.fold(value as u64);
}
#[inline]
fn finish(&self) -> u64 {
self.state
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct BuildTableHasher;
impl BuildHasher for BuildTableHasher {
type Hasher = TableHasher;
fn build_hasher(&self) -> TableHasher {
TableHasher::default()
}
}
pub type TableMap<K, V> = std::collections::HashMap<K, V, BuildTableHasher>;
pub type TableSet<K> = std::collections::HashSet<K, BuildTableHasher>;
#[cfg(test)]
mod tests {
use super::*;
fn hashed(bytes: &[u8]) -> u64 {
let mut hasher = TableHasher::default();
hasher.write(bytes);
hasher.finish()
}
#[test]
fn the_key_shapes_this_engine_produces_do_not_collide() {
let shapes: [(&str, fn(u64) -> Vec<u8>); 5] = [
("a rowid key", |value| {
let mut key = vec![0x0au8];
key.extend_from_slice(&value.to_be_bytes());
key
}),
("a bare little-endian integer", |value| {
value.to_le_bytes().to_vec()
}),
("text", |value| format!("row-{value}").into_bytes()),
("three bytes", |value| {
(value as u32)
.to_be_bytes()
.get(1..)
.unwrap_or(&[])
.to_vec()
}),
("a two-column key", |value| {
let mut key = vec![0x0au8];
key.extend_from_slice(&(value % 64).to_be_bytes());
key.push(0x0a);
key.extend_from_slice(&(value / 64).to_be_bytes());
key
}),
];
for (name, shape) in shapes {
let mut seen = std::collections::HashMap::new();
for value in 0..200_000u64 {
let key = shape(value);
if let Some(earlier) = seen.insert(hashed(&key), value) {
panic!("{name}: {value} hashes the same as {earlier}");
}
}
}
}
#[test]
fn a_zero_extended_key_is_not_the_shorter_one() {
assert_ne!(hashed(&[1]), hashed(&[1, 0]));
assert_ne!(hashed(&[1, 0]), hashed(&[1, 0, 0]));
assert_ne!(hashed(b"a"), hashed(b"a\0\0\0\0\0\0\0"));
}
#[test]
fn a_run_of_zero_bytes_is_not_a_fixed_point() {
let empty = hashed(&[]);
let one = hashed(&[0]);
let eight = hashed(&[0; 8]);
let nine = hashed(&[0; 9]);
assert_ne!(empty, one);
assert_ne!(one, eight);
assert_ne!(eight, nine);
assert_ne!(empty, eight);
}
#[test]
fn a_word_and_its_bytes_differ_only_by_the_length() {
let value = 0x0102_0304_0506_0708u64;
let mut words = TableHasher::default();
words.write_u64(value);
words.fold(8);
let mut bytes = TableHasher::default();
bytes.write(&value.to_le_bytes());
assert_eq!(words.finish(), bytes.finish());
}
#[test]
fn the_aliases_make_a_working_table() {
let mut map: TableMap<Vec<u8>, u32> = TableMap::default();
map.insert(b"one".to_vec(), 1);
map.insert(b"two".to_vec(), 2);
assert_eq!(map.get(b"one".as_slice()), Some(&1));
assert_eq!(map.get(b"three".as_slice()), None);
let mut set: TableSet<Vec<u8>> = TableSet::default();
assert!(set.insert(b"one".to_vec()));
assert!(!set.insert(b"one".to_vec()));
}
}