use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
const FNV_OFFSET_BASIS: u64 = 0xcbf29ce484222325;
const FNV_PRIME: u64 = 0x100000001b3;
const GOLDEN_RATIO: u64 = 0x9e3779b97f4a7c15;
#[inline]
pub fn hash_value<T: Hash>(value: &T) -> u64 {
let mut hasher = DefaultHasher::new();
value.hash(&mut hasher);
hasher.finish()
}
#[inline]
pub fn hash_combine(seed: u64, hash: u64) -> u64 {
seed ^ hash
.wrapping_add(GOLDEN_RATIO)
.wrapping_add(seed << 6)
.wrapping_add(seed >> 2)
}
#[inline]
pub fn hash_combine_range<T: Hash>(seed: u64, values: &[T]) -> u64 {
let mut result = seed;
for value in values {
let h = hash_value(value);
result = hash_combine(result, h);
}
result
}
#[inline]
pub fn stable_hash<T: Hash>(value: &T) -> u64 {
let intermediate = hash_value(value);
hash_u64(intermediate)
}
#[inline]
pub fn hash_u64(value: u64) -> u64 {
let bytes = value.to_le_bytes();
hash_bytes(&bytes)
}
#[inline]
pub fn hash_bytes(bytes: &[u8]) -> u64 {
let mut hash = FNV_OFFSET_BASIS;
for &byte in bytes {
hash ^= byte as u64;
hash = hash.wrapping_mul(FNV_PRIME);
}
hash
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct HashCode128 {
pub low: u64,
pub high: u64,
}
impl HashCode128 {
#[inline]
pub fn new(low: u64, high: u64) -> Self {
HashCode128 { low, high }
}
#[inline]
pub fn from_bytes(bytes: &[u8]) -> Self {
let low = hash_bytes(bytes);
let high_offset = FNV_OFFSET_BASIS.wrapping_mul(FNV_PRIME);
let mut high = high_offset;
for &byte in bytes.iter().rev() {
high ^= byte as u64;
high = high.wrapping_mul(FNV_PRIME);
}
HashCode128 { low, high }
}
#[inline]
pub fn combine(self, other: HashCode128) -> HashCode128 {
let low = hash_combine(self.low, other.low);
let high = hash_combine(hash_combine(self.high, other.high), low);
HashCode128 { low, high }
}
#[inline]
pub fn as_tuple(&self) -> (u64, u64) {
(self.low, self.high)
}
}
impl std::fmt::Display for HashCode128 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:016x}{:016x}", self.high, self.low)
}
}
impl From<u64> for HashCode128 {
#[inline]
fn from(value: u64) -> Self {
let high = value.rotate_left(32) ^ value.wrapping_mul(GOLDEN_RATIO);
HashCode128 { low: value, high }
}
}
impl std::ops::BitXor for HashCode128 {
type Output = HashCode128;
#[inline]
fn bitxor(self, rhs: HashCode128) -> HashCode128 {
HashCode128 {
low: self.low ^ rhs.low,
high: self.high ^ rhs.high,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_hash_value_basic() {
let h1 = hash_value(&42u32);
let h2 = hash_value(&42u32);
assert_eq!(h1, h2);
}
#[test]
fn test_hash_value_different_types() {
let h1 = hash_value(&1u32);
let h2 = hash_value(&2u32);
assert_ne!(h1, h2);
}
#[test]
fn test_hash_combine_basic() {
let result = hash_combine(0, 42);
assert_ne!(result, 0);
}
#[test]
fn test_hash_combine_deterministic() {
let a = hash_combine(1, 2);
let b = hash_combine(1, 2);
assert_eq!(a, b); }
#[test]
fn test_hash_combine_commutative_check() {
let h1 = hash_combine(0, 1);
let h2 = hash_combine(1, 0);
assert_ne!(h1, h2);
}
#[test]
fn test_hash_combine_mixing() {
let h1 = hash_combine(0x12345678, 0x00000000);
let h2 = hash_combine(0x12345678, 0x00000001);
let diff = h1 ^ h2;
let bit_diff = diff.count_ones();
assert!(
bit_diff >= 3,
"Expected >=3 differing bits, got {}",
bit_diff
);
}
#[test]
fn test_hash_combine_range_empty() {
let h = hash_combine_range(42u64, &[] as &[u32]);
assert_eq!(h, 42);
}
#[test]
fn test_hash_combine_range_single() {
let values = [100u32];
let h = hash_combine_range(0, &values);
assert_ne!(h, 0);
}
#[test]
fn test_hash_combine_range_multiple() {
let values = [1u32, 2u32, 3u32];
let h1 = hash_combine_range(0, &values);
let h2 = hash_combine_range(0, &values);
assert_eq!(h1, h2);
let values_rev = [3u32, 2u32, 1u32];
let h3 = hash_combine_range(0, &values_rev);
}
#[test]
fn test_stable_hash_deterministic() {
let h1 = stable_hash(&42u64);
let h2 = stable_hash(&42u64);
assert_eq!(h1, h2);
}
#[test]
fn test_stable_hash_different_values() {
let h1 = stable_hash(&1u64);
let h2 = stable_hash(&2u64);
assert_ne!(h1, h2);
}
#[test]
fn test_hash_u64_deterministic() {
let h1 = hash_u64(42);
let h2 = hash_u64(42);
assert_eq!(h1, h2);
}
#[test]
fn test_hash_u64_known_value() {
let expected = {
let mut hash = FNV_OFFSET_BASIS;
for &byte in &0u64.to_le_bytes() {
hash ^= byte as u64;
hash = hash.wrapping_mul(FNV_PRIME);
}
hash
};
assert_eq!(hash_u64(0), expected);
}
#[test]
fn test_hash_u64_no_collision_small_numbers() {
let mut seen = std::collections::HashSet::new();
for i in 0u64..1000 {
let h = hash_u64(i);
assert!(seen.insert(h), "Collision at i={}", i);
}
}
#[test]
fn test_hash_bytes_empty() {
let h = hash_bytes(b"");
assert_eq!(h, FNV_OFFSET_BASIS);
}
#[test]
fn test_hash_bytes_deterministic() {
let h1 = hash_bytes(b"hello world");
let h2 = hash_bytes(b"hello world");
assert_eq!(h1, h2);
}
#[test]
fn test_hash_bytes_different_inputs() {
let h1 = hash_bytes(b"hello");
let h2 = hash_bytes(b"world");
assert_ne!(h1, h2);
}
#[test]
fn test_hash_bytes_fnv_known_vector() {
let h = hash_bytes(b"foobar");
assert_eq!(hash_bytes(b""), 0xcbf29ce484222325);
}
#[test]
fn test_hash_code_128_new() {
let h = HashCode128::new(0xAAAA, 0xBBBB);
assert_eq!(h.low, 0xAAAA);
assert_eq!(h.high, 0xBBBB);
}
#[test]
fn test_hash_code_128_from_bytes_empty() {
let h = HashCode128::from_bytes(b"");
assert_eq!(h.low, FNV_OFFSET_BASIS);
assert_ne!(h.low, h.high);
}
#[test]
fn test_hash_code_128_from_bytes_deterministic() {
let h1 = HashCode128::from_bytes(b"test data");
let h2 = HashCode128::from_bytes(b"test data");
assert_eq!(h1, h2);
}
#[test]
fn test_hash_code_128_from_bytes_different() {
let h1 = HashCode128::from_bytes(b"alpha");
let h2 = HashCode128::from_bytes(b"beta");
assert_ne!(h1, h2);
assert!(h1.low != h2.low || h1.high != h2.high);
}
#[test]
fn test_hash_code_128_combine() {
let h1 = HashCode128::new(1, 2);
let h2 = HashCode128::new(3, 4);
let combined = h1.combine(h2);
assert_ne!(combined, h1);
assert_ne!(combined, h2);
let combined2 = h1.combine(h2);
assert_eq!(combined, combined2);
}
#[test]
fn test_hash_code_128_combine_associative_check() {
let a = HashCode128::new(1, 1);
let b = HashCode128::new(2, 2);
let c = HashCode128::new(3, 3);
let ab_c = a.combine(b).combine(c);
let a_bc = a.combine(b.combine(c));
}
#[test]
fn test_hash_code_128_as_tuple() {
let h = HashCode128::new(0xDEAD, 0xBEEF);
let (low, high) = h.as_tuple();
assert_eq!(low, 0xDEAD);
assert_eq!(high, 0xBEEF);
}
#[test]
fn test_hash_code_128_display() {
let h = HashCode128::new(0x1234, 0x5678);
let display = format!("{}", h);
assert_eq!(display.len(), 32);
assert!(display.ends_with("1234"));
assert!(display.starts_with("0000000000005678"));
}
#[test]
fn test_hash_code_128_from_u64() {
let h = HashCode128::from(42u64);
assert_eq!(h.low, 42);
assert_ne!(h.high, 0);
assert_ne!(h.high, h.low);
}
#[test]
fn test_hash_code_128_xor() {
let h1 = HashCode128::new(0xFF00, 0x00FF);
let h2 = HashCode128::new(0x0F0F, 0xF0F0);
let result = h1 ^ h2;
assert_eq!(result.low, 0xFF00 ^ 0x0F0F);
assert_eq!(result.high, 0x00FF ^ 0xF0F0);
}
#[test]
fn test_hash_code_128_eq() {
let h1 = HashCode128::new(1, 2);
let h2 = HashCode128::new(1, 2);
let h3 = HashCode128::new(1, 3);
assert_eq!(h1, h2);
assert_ne!(h1, h3);
}
#[test]
fn test_hash_code_128_clone() {
let h1 = HashCode128::new(42, 84);
let h2 = h1;
assert_eq!(h1, h2);
}
#[test]
fn test_hash_combine_chain() {
let mut seed = 0u64;
seed = hash_combine(seed, hash_value(&"name"));
seed = hash_combine(seed, hash_value(&42u32));
seed = hash_combine(seed, hash_value(&true));
let mut seed2 = 0u64;
seed2 = hash_combine(seed2, hash_value(&"name"));
seed2 = hash_combine(seed2, hash_value(&42u32));
seed2 = hash_combine(seed2, hash_value(&true));
assert_eq!(seed, seed2);
}
#[test]
fn test_hash_bytes_long_input() {
let data: Vec<u8> = (0..255).collect();
let h1 = hash_bytes(&data);
let h2 = hash_bytes(&data);
assert_eq!(h1, h2);
let mut data2 = data.clone();
data2[128] ^= 1;
let h3 = hash_bytes(&data2);
assert_ne!(h1, h3);
}
#[test]
fn test_hash_u64_edge_cases() {
assert_ne!(hash_u64(0), hash_u64(u64::MAX));
assert_ne!(hash_u64(u64::MAX), hash_u64(u64::MAX - 1));
}
#[test]
fn test_golden_ratio_constant() {
assert_eq!(GOLDEN_RATIO, 0x9e3779b97f4a7c15);
}
}