use alloc::vec;
use alloc::vec::Vec;
use core::hash::{Hash, Hasher};
use twox_hash::XxHash64;
const SEED: u64 = 0xaabbccdd;
pub const fn split_hash64(hash: u64, m: usize) -> (usize, u32, u32) {
(
((hash >> 48) as usize % m),
((hash >> 24) as u32 & 0xffffff),
((hash & 0xffffff) as u32),
)
}
pub fn compute_hash<K: Hash + ?Sized>(key: &K) -> u64 {
let mut hasher = XxHash64::with_seed(SEED);
key.hash(&mut hasher);
hasher.finish()
}
pub fn compute_index(f: (u32, u32), d: (u32, u32), m: u32) -> Option<usize> {
if d == (0, 0) || m == 0 {
None
} else {
Some((f.1.wrapping_mul(d.0).wrapping_add(f.0).wrapping_add(d.1) % m) as usize)
}
}
#[expect(clippy::indexing_slicing, clippy::unwrap_used)]
pub fn compute_displacements(
key_hashes: impl ExactSizeIterator<Item = u64>,
) -> (Vec<(u32, u32)>, Vec<usize>) {
let len = key_hashes.len();
let mut bucket_sizes = vec![0; len];
let mut bucket_flatten = Vec::with_capacity(len);
key_hashes.into_iter().enumerate().for_each(|(i, kh)| {
let h = split_hash64(kh, len);
bucket_sizes[h.0] += 1;
bucket_flatten.push((h, i))
});
bucket_flatten.sort_by(|&(ha, _), &(hb, _)| {
(bucket_sizes[hb.0], hb).cmp(&(bucket_sizes[ha.0], ha))
});
let mut generation = 0;
let mut occupied = vec![false; len];
let mut assignments = vec![0; len];
let mut current_displacements = Vec::with_capacity(16);
let mut displacements = vec![(0, 0); len];
let mut reverse_mapping = vec![0; len];
let mut start = 0;
while start < len {
let g = bucket_flatten[start].0 .0;
let end = start + bucket_sizes[g];
let buckets = &bucket_flatten[start..end];
'd0: for d0 in 0..len as u32 {
'd1: for d1 in 0..len as u32 {
if (d0, d1) == (0, 0) {
continue;
}
current_displacements.clear();
generation += 1;
for ((_, f0, f1), _) in buckets {
let displacement_idx = compute_index((*f0, *f1), (d0, d1), len as u32).unwrap();
if occupied[displacement_idx] || assignments[displacement_idx] == generation {
continue 'd1;
}
assignments[displacement_idx] = generation;
current_displacements.push(displacement_idx);
}
displacements[g] = (d0, d1);
for (i, displacement_idx) in current_displacements.iter().enumerate() {
let (_, idx) = &buckets[i];
occupied[*displacement_idx] = true;
reverse_mapping[*displacement_idx] = *idx;
}
break 'd0;
}
}
start = end;
}
(displacements, reverse_mapping)
}