use std::hash::{BuildHasherDefault, Hasher};
pub type ByNumber = BuildHasherDefault<NumberHasher>;
#[derive(Default)]
pub struct NumberHasher(u64);
const MIX: u64 = 0x517c_c1b7_2722_0a95;
impl NumberHasher {
fn add(&mut self, word: u64) {
self.0 = (self.0 ^ word).wrapping_mul(MIX);
}
}
impl Hasher for NumberHasher {
fn finish(&self) -> u64 {
self.0 ^ (self.0 >> 32)
}
fn write(&mut self, bytes: &[u8]) {
for chunk in bytes.chunks(8) {
let mut word = [0u8; 8];
word[..chunk.len()].copy_from_slice(chunk);
self.add(u64::from_le_bytes(word));
}
}
fn write_u8(&mut self, value: u8) {
self.add(u64::from(value));
}
fn write_u16(&mut self, value: u16) {
self.add(u64::from(value));
}
fn write_u32(&mut self, value: u32) {
self.add(u64::from(value));
}
fn write_u64(&mut self, value: u64) {
self.add(value);
}
fn write_usize(&mut self, value: usize) {
self.add(value as u64);
}
}
#[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 = NumberHasher::default();
value.hash(&mut hasher);
hasher.finish()
}
#[test]
fn two_keys_that_differ_hash_differently() {
assert_ne!(hash_of(&(1u32, 2u32)), hash_of(&(2u32, 1u32)));
assert_ne!(hash_of(&(0u32, 1u32)), hash_of(&(0u32, 2u32)));
assert_ne!(hash_of(&(1u32, 0u32)), hash_of(&(2u32, 0u32)));
}
#[test]
fn a_map_keyed_by_it_still_behaves_like_a_map() {
let mut map: HashMap<(u32, u32), &str, ByNumber> = HashMap::default();
for start in 0..64u32 {
map.insert((start, start + 3), "here");
}
assert_eq!(map.len(), 64);
assert_eq!(map.get(&(7, 10)), Some(&"here"));
assert_eq!(map.get(&(7, 11)), None);
}
#[test]
fn a_key_that_is_not_a_number_is_still_hashed() {
assert_ne!(hash_of(&"one"), hash_of(&"two"));
assert_ne!(hash_of(&"a longer key than eight bytes"), hash_of(&"one"));
}
}