use std::hash::{BuildHasherDefault, Hasher};
pub type FxHashMap<K, V> = std::collections::HashMap<K, V, BuildHasherDefault<FxHasher>>;
const SEED: u64 = 0x51_7c_c1_b7_27_22_0a_95;
#[derive(Default)]
pub struct FxHasher {
hash: u64,
}
impl FxHasher {
#[inline]
fn add(&mut self, word: u64) {
self.hash = (self.hash.rotate_left(5) ^ word).wrapping_mul(SEED);
}
}
impl Hasher for FxHasher {
#[inline]
fn write(&mut self, mut bytes: &[u8]) {
while bytes.len() >= 8 {
self.add(u64::from_le_bytes(bytes[..8].try_into().unwrap()));
bytes = &bytes[8..];
}
if bytes.len() >= 4 {
self.add(u32::from_le_bytes(bytes[..4].try_into().unwrap()) as u64);
bytes = &bytes[4..];
}
if bytes.len() >= 2 {
self.add(u16::from_le_bytes(bytes[..2].try_into().unwrap()) as u64);
bytes = &bytes[2..];
}
if let Some(&b) = bytes.first() {
self.add(b as u64);
}
}
#[inline]
fn finish(&self) -> u64 {
self.hash
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::hash::Hash;
fn hash(s: &str) -> u64 {
let mut h = FxHasher::default();
s.hash(&mut h);
h.finish()
}
#[test]
fn fxhash_distinguishes_keys_across_lengths() {
let keys = ["", "a", "lo", "low", "open", "close", "volume", "volume_xy", "macd.signal"];
let hashes: Vec<u64> = keys.iter().map(|k| hash(k)).collect();
for i in 0..keys.len() {
for j in (i + 1)..keys.len() {
assert_ne!(hashes[i], hashes[j], "collision: {:?} vs {:?}", keys[i], keys[j]);
}
}
assert_eq!(hash("close"), hash("close"));
}
#[test]
fn fxhashmap_works_as_a_map() {
let mut m: FxHashMap<String, usize> = FxHashMap::default();
m.insert("open".into(), 0);
m.insert("close".into(), 3);
assert_eq!(m.get("open"), Some(&0));
assert_eq!(m.get("close"), Some(&3));
assert_eq!(m.get("missing"), None);
}
}