use std::hash::{BuildHasherDefault, Hasher};
pub type FxBuildHasher = BuildHasherDefault<FxHasher>;
const SEED: u64 = 0x51_7c_c1_b7_27_22_0a_95;
const ROTATE: u32 = 5;
#[derive(Debug, Clone, Copy, Default)]
pub struct FxHasher {
hash: u64,
}
impl FxHasher {
#[inline]
fn add(&mut self, word: u64) {
self.hash = (self.hash.rotate_left(ROTATE) ^ word).wrapping_mul(SEED);
}
}
impl Hasher for FxHasher {
#[inline]
fn finish(&self) -> u64 {
self.hash
}
#[inline]
fn write(&mut self, bytes: &[u8]) {
let (words, remainder) = bytes.as_chunks::<8>();
for word in words {
self.add(u64::from_ne_bytes(*word));
}
for &byte in remainder {
self.add(u64::from(byte));
}
}
#[inline]
fn write_u8(&mut self, n: u8) {
self.add(u64::from(n));
}
#[inline]
fn write_u16(&mut self, n: u16) {
self.add(u64::from(n));
}
#[inline]
fn write_u32(&mut self, n: u32) {
self.add(u64::from(n));
}
#[inline]
fn write_u64(&mut self, n: u64) {
self.add(n);
}
#[inline]
fn write_usize(&mut self, n: usize) {
self.add(n as u64);
}
#[inline]
fn write_i32(&mut self, n: i32) {
self.add(u64::from(n.cast_unsigned()));
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
use std::hash::Hash;
fn hash_of<T: Hash>(value: &T) -> u64 {
let mut hasher = FxHasher::default();
value.hash(&mut hasher);
hasher.finish()
}
#[test]
fn distinct_small_integers_do_not_collide() {
let hashes: std::collections::HashSet<u64> = (0_u32..10_000).map(|n| hash_of(&n)).collect();
assert_eq!(hashes.len(), 10_000);
}
#[test]
fn a_tuple_of_integers_spreads_over_the_low_bits() {
let mut buckets = [0_u32; 256];
for a in 0_i32..40 {
for b in 0_i32..40 {
let h = hash_of(&(a, b, a ^ b, a.wrapping_mul(b)));
let bucket = usize::try_from(h & 0xff).unwrap_or(0);
if let Some(slot) = buckets.get_mut(bucket) {
*slot += 1;
}
}
}
let worst = buckets.iter().copied().max().unwrap_or(0);
assert!(worst < 40, "worst bucket held {worst} of 1600");
assert!(
buckets.iter().filter(|&&n| n == 0).count() < 32,
"too many empty buckets: {}",
buckets.iter().filter(|&&n| n == 0).count()
);
}
#[test]
fn it_works_as_a_hashmap_hasher() {
let mut map: HashMap<u32, u32, FxBuildHasher> = HashMap::default();
for n in 0..1000 {
map.insert(n, n * 2);
}
for n in 0..1000 {
assert_eq!(map.get(&n), Some(&(n * 2)));
}
assert_eq!(map.get(&1000), None);
}
#[test]
fn it_is_deterministic_across_instances() {
assert_eq!(hash_of(&12345_u32), hash_of(&12345_u32));
assert_ne!(hash_of(&12345_u32), hash_of(&12346_u32));
}
#[test]
fn a_negative_i32_does_not_saturate_the_high_word() {
assert_ne!(hash_of(&(-1_i32, -2_i32)), hash_of(&(-2_i32, -1_i32)));
assert_ne!(hash_of(&(-1_i32, 0_i32)), hash_of(&(0_i32, -1_i32)));
}
}