const C1: u32 = 0xcc9e2d51;
const C2: u32 = 0x1b873593;
const DEFAULT_SEED: u32 = 42;
fn mix_k1(mut k1: u32) -> u32 {
k1 = k1.wrapping_mul(C1);
k1 = k1.rotate_left(15);
k1 = k1.wrapping_mul(C2);
k1
}
fn mix_h1(mut h1: u32, k1: u32) -> u32 {
h1 ^= k1;
h1 = h1.rotate_left(13);
h1 = h1.wrapping_mul(5).wrapping_add(0xe6546b64);
h1
}
fn fmix(mut h: u32) -> u32 {
h ^= h >> 16;
h = h.wrapping_mul(0x85ebca6b);
h ^= h >> 13;
h = h.wrapping_mul(0xc2b2ae35);
h ^= h >> 16;
h
}
pub fn hash_by_words(data: &[u8]) -> i32 {
assert!(
data.len().is_multiple_of(4),
"hash_by_words: data length must be word-aligned (multiple of 4), got {}",
data.len()
);
let mut h1 = DEFAULT_SEED;
for chunk in data.chunks_exact(4) {
let word = u32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]);
let k1 = mix_k1(word);
h1 = mix_h1(h1, k1);
}
fmix(h1 ^ data.len() as u32) as i32
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_hash_empty() {
let h = hash_by_words(&[]);
let expected = fmix(DEFAULT_SEED) as i32;
assert_eq!(h, expected);
}
#[test]
fn test_hash_single_int() {
let data = 1_i32.to_le_bytes();
let h = hash_by_words(&data);
let k1 = mix_k1(1);
let h1 = mix_h1(DEFAULT_SEED, k1);
let expected = fmix(h1 ^ 4) as i32;
assert_eq!(h, expected);
}
#[test]
#[should_panic(expected = "word-aligned")]
fn test_hash_non_aligned_panics() {
hash_by_words(&[1, 2, 3]);
}
}