#![allow(dead_code)]
#[cfg(not(feature = "std"))]
extern crate alloc;
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
#[derive(Debug, Clone, Copy)]
pub struct FrequencyHash {
num_buckets: usize,
n: usize,
offset: usize,
scale: usize,
}
impl FrequencyHash {
pub fn new(num_buckets: usize, n: usize) -> Self {
Self {
num_buckets,
n,
offset: 0,
scale: 1,
}
}
pub fn with_permutation(num_buckets: usize, n: usize, seed: u64) -> Self {
let offset = ((seed * 1103515245 + 12345) % (n as u64)) as usize;
let scale = Self::find_coprime(n, seed);
Self {
num_buckets,
n,
offset,
scale,
}
}
fn find_coprime(n: usize, seed: u64) -> usize {
let start = ((seed * 48271) % (n as u64)) as usize;
let start = start.max(1);
for candidate in start..n {
if gcd(candidate, n) == 1 {
return candidate;
}
}
1
}
#[inline]
pub fn hash(&self, freq: usize) -> usize {
let permuted = (self.scale.wrapping_mul(freq).wrapping_add(self.offset)) % self.n;
permuted % self.num_buckets
}
pub fn inverse_hash(&self, bucket: usize) -> Vec<usize> {
let mut candidates = Vec::new();
for freq in 0..self.n {
if self.hash(freq) == bucket {
candidates.push(freq);
}
}
candidates
}
pub fn collision_probability(&self, k: usize) -> f64 {
let b = self.num_buckets as f64;
let k_f = k as f64;
(k_f * k_f) / (2.0 * b)
}
pub fn num_buckets(&self) -> usize {
self.num_buckets
}
pub fn signal_length(&self) -> usize {
self.n
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct CrtHash {
hashes: Vec<FrequencyHash>,
product: usize,
n: usize,
}
#[allow(dead_code)]
impl CrtHash {
pub fn new(bucket_counts: &[usize], n: usize) -> Self {
let hashes: Vec<_> = bucket_counts
.iter()
.map(|&b| FrequencyHash::new(b, n))
.collect();
let product: usize = bucket_counts.iter().product();
Self { hashes, product, n }
}
pub fn from_factors(factors: &[usize], n: usize) -> Self {
Self::new(factors, n)
}
pub fn hash(&self, freq: usize) -> Vec<usize> {
self.hashes.iter().map(|h| h.hash(freq)).collect()
}
pub fn recover_frequency(&self, bucket_indices: &[usize]) -> Option<usize> {
if bucket_indices.len() != self.hashes.len() {
return None;
}
let mut result = 0usize;
for (i, &idx) in bucket_indices.iter().enumerate() {
let ni = self.hashes[i].num_buckets();
let mi = self.product / ni;
if let Some(yi) = mod_inverse(mi % ni, ni) {
result = result.wrapping_add(idx.wrapping_mul(mi).wrapping_mul(yi));
} else {
return None;
}
}
let freq = result % self.product;
if freq < self.n {
Some(freq)
} else {
None
}
}
pub fn num_hashes(&self) -> usize {
self.hashes.len()
}
pub fn hashes(&self) -> &[FrequencyHash] {
&self.hashes
}
}
#[allow(dead_code)]
fn gcd(a: usize, b: usize) -> usize {
if b == 0 {
a
} else {
gcd(b, a % b)
}
}
#[allow(dead_code)]
fn mod_inverse(a: usize, m: usize) -> Option<usize> {
if m == 0 {
return None;
}
let (gcd, x, _) = extended_gcd(a as i64, m as i64);
if gcd != 1 {
return None; }
let inv = ((x % m as i64) + m as i64) % m as i64;
Some(inv as usize)
}
#[allow(dead_code)]
fn extended_gcd(a: i64, b: i64) -> (i64, i64, i64) {
if a == 0 {
(b, 0, 1)
} else {
let (gcd, x, y) = extended_gcd(b % a, a);
(gcd, y - (b / a) * x, x)
}
}
pub fn generate_coprime_factors(target: usize, max_factor: usize) -> Vec<usize> {
let small_primes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47];
let mut factors = Vec::new();
let mut product = 1usize;
for &p in &small_primes {
if product >= target {
break;
}
if p <= max_factor {
factors.push(p);
product *= p;
}
}
factors
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_frequency_hash() {
let hash = FrequencyHash::new(16, 1024);
assert_eq!(hash.hash(0), 0);
assert_eq!(hash.hash(16), 0);
assert_eq!(hash.hash(17), 1);
}
#[test]
fn test_hash_with_permutation() {
let hash = FrequencyHash::with_permutation(16, 1024, 12345);
for freq in 0..100 {
let bucket = hash.hash(freq);
assert!(bucket < 16);
}
}
#[test]
fn test_inverse_hash() {
let hash = FrequencyHash::new(8, 64);
let candidates = hash.inverse_hash(0);
assert!(candidates.contains(&0));
assert!(candidates.contains(&8));
assert!(candidates.contains(&16));
}
#[test]
fn test_collision_probability() {
let hash = FrequencyHash::new(100, 1000);
let prob = hash.collision_probability(10);
assert!((prob - 0.5).abs() < 0.01);
}
#[test]
fn test_crt_hash() {
let crt = CrtHash::new(&[3, 5, 7], 1024);
for freq in 0..100 {
let buckets = crt.hash(freq);
if let Some(recovered) = crt.recover_frequency(&buckets) {
assert_eq!(recovered % 105, freq % 105);
}
}
}
#[test]
fn test_gcd() {
assert_eq!(gcd(12, 8), 4);
assert_eq!(gcd(17, 13), 1);
assert_eq!(gcd(100, 35), 5);
}
#[test]
fn test_mod_inverse() {
assert_eq!(mod_inverse(3, 10), Some(7));
assert_eq!(mod_inverse(4, 8), None);
}
#[test]
fn test_generate_coprime_factors() {
let factors = generate_coprime_factors(100, 50);
assert!(factors.len() >= 2);
let product: usize = factors.iter().product();
assert!(product >= 100);
for i in 0..factors.len() {
for j in i + 1..factors.len() {
assert_eq!(gcd(factors[i], factors[j]), 1);
}
}
}
}