use std::hash::{BuildHasherDefault, Hasher};
const SEED: u64 = 0x51_7c_c1_b7_27_22_0a_95;
const ROTATE: u32 = 5;
#[derive(Default)]
pub(super) struct FxHasher {
hash: u64,
}
impl FxHasher {
#[inline]
fn add_word(&mut self, word: u64) {
self.hash = (self.hash.rotate_left(ROTATE) ^ word).wrapping_mul(SEED);
}
}
impl Hasher for FxHasher {
#[inline]
fn write(&mut self, mut bytes: &[u8]) {
while let Some((chunk, rest)) = bytes.split_first_chunk::<8>() {
self.add_word(u64::from_ne_bytes(*chunk));
bytes = rest;
}
if let Some((chunk, rest)) = bytes.split_first_chunk::<4>() {
self.add_word(u64::from(u32::from_ne_bytes(*chunk)));
bytes = rest;
}
if let Some((chunk, rest)) = bytes.split_first_chunk::<2>() {
self.add_word(u64::from(u16::from_ne_bytes(*chunk)));
bytes = rest;
}
if let Some(&byte) = bytes.first() {
self.add_word(u64::from(byte));
}
}
#[inline]
fn finish(&self) -> u64 {
self.hash
}
}
pub(super) type FxBuildHasher = BuildHasherDefault<FxHasher>;
#[cfg(test)]
mod tests {
use super::*;
use std::hash::Hash;
fn hash_str(s: &str) -> u64 {
let mut hasher = FxHasher::default();
s.hash(&mut hasher);
hasher.finish()
}
#[test]
fn equal_keys_hash_equally_across_lengths() {
for s in [
"",
"a",
"id",
"abc",
"user",
"orders",
"customer",
"a_long_identifier_name",
] {
assert_eq!(hash_str(s), hash_str(s), "deterministic for {s:?}");
}
}
#[test]
fn distinct_keys_diffuse_to_distinct_hashes() {
let keys = [
"users",
"Users",
"USERS",
"user",
"users_",
"id",
"id2",
"i_item_id",
"ss_quantity",
"ss_list_price",
"ss_coupon_amt",
"store_sales",
"date_dim",
];
let mut hashes: Vec<u64> = keys.iter().map(|k| hash_str(k)).collect();
hashes.sort_unstable();
hashes.dedup();
assert_eq!(hashes.len(), keys.len(), "distinct keys collided wholesale");
}
}