#[derive(Debug)]
pub struct HyperLogLog {
p: u8,
m: usize,
registers: Vec<u8>, }
impl HyperLogLog {
pub const DEFAULT_P: u8 = 12;
pub fn new(p: u8) -> Self {
assert!(
(4..=16).contains(&p),
"precision p must be between 4 and 16"
);
let m = 1_usize << p;
HyperLogLog {
p,
m,
registers: vec![0; m],
}
}
pub fn insert(&mut self, hash: u64) {
let idx = (hash >> (64 - self.p)) as usize;
let w = (hash << self.p) | (1 << (self.p - 1));
let rank = w.leading_zeros() + 1;
let rank = rank as u8;
if self.registers[idx] < rank {
self.registers[idx] = rank;
}
}
pub fn merge(&mut self, other: &HyperLogLog) {
assert_eq!(self.p, other.p, "precisions must match");
for (a, &b) in self.registers.iter_mut().zip(&other.registers) {
if *a < b {
*a = b;
}
}
}
pub fn count(&self) -> f64 {
let m = self.m as f64;
let alpha = match self.m {
16 => 0.673,
32 => 0.697,
64 => 0.709,
_ => 0.7213 / (1.0 + 1.079 / m),
};
let sum: f64 = self
.registers
.iter()
.map(|&r| 2_f64.powi(-(r as i32)))
.sum();
let e = alpha * m * m / sum;
if e <= 5.0 * m {
let zeros = self.registers.iter().filter(|&&r| r == 0).count() as f64;
if zeros > 0.0 {
m * (m / zeros).ln()
} else {
e
}
} else if e <= (1u64 << 32) as f64 / 30.0 {
e
} else {
-((1u64 << 32) as f64) * (1.0 - e / (1u64 << 32) as f64).ln()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn hll_basic() {
let mut h1 = HyperLogLog::new(10); let mut h2 = HyperLogLog::new(10);
for i in 0..10_000 {
h1.insert(hash64(i));
h2.insert(hash64(i + 5_000)); }
let c1 = h1.count();
assert!((9000.0..11000.0).contains(&c1), "c1: {}", c1);
let c2 = h2.count();
assert!((9000.0..11000.0).contains(&c2), "c2: {}", c2);
h1.merge(&h2);
let cu = h1.count();
assert!((14000.0..16000.0).contains(&cu), "cu: {}", cu);
}
fn hash64(x: u64) -> u64 {
let mut z = x.wrapping_add(0x9e3779b97f4a7c15);
z = (z ^ (z >> 30)).wrapping_mul(0xbf58476d1ce4e5b9);
z = (z ^ (z >> 27)).wrapping_mul(0x94d049bb133111eb);
z ^ (z >> 31)
}
}