akar_storage/
hyperloglog.rs1use std::collections::hash_map::DefaultHasher;
12use std::hash::{Hash, Hasher};
13
14const P: u32 = 6;
16const M: usize = 1 << P;
18const Q: u32 = 64 - P;
20const ALPHA_MM: f64 = 0.709;
23
24const LINEAR_COUNTING_THRESHOLD: f64 = M as f64 * 2.5;
26
27#[derive(Debug, Clone)]
29pub struct HyperLogLog {
30 registers: [u8; M],
32}
33
34impl Default for HyperLogLog {
35 fn default() -> Self {
36 Self::new()
37 }
38}
39
40impl HyperLogLog {
41 pub fn new() -> Self {
42 Self { registers: [0u8; M] }
43 }
44
45 pub fn insert<T: Hash>(&mut self, value: &T) {
47 let hash = Self::hash_value(value);
48 let idx = (hash & ((1 << P) - 1)) as usize; let w = (hash >> P) | (1u64 << Q); let leading_zeros = (w.trailing_zeros() + 1) as u8; if leading_zeros > self.registers[idx] {
52 self.registers[idx] = leading_zeros;
53 }
54 }
55
56 pub fn insert_hash(&mut self, hash: u64) {
58 let idx = (hash & ((1 << P) - 1)) as usize;
59 let w = (hash >> P) | (1u64 << Q);
60 let leading_zeros = (w.trailing_zeros() + 1) as u8;
61 if leading_zeros > self.registers[idx] {
62 self.registers[idx] = leading_zeros;
63 }
64 }
65
66 pub fn merge(&mut self, other: &HyperLogLog) {
68 for i in 0..M {
69 if other.registers[i] > self.registers[i] {
70 self.registers[i] = other.registers[i];
71 }
72 }
73 }
74
75 pub fn count(&self) -> u64 {
77 let z_inv: f64 = self.registers.iter().map(|&r| 2.0f64.powi(-(r as i32))).sum();
79 let raw_estimate = ALPHA_MM * (M as f64).powi(2) / z_inv;
80
81 if raw_estimate <= LINEAR_COUNTING_THRESHOLD {
83 let zero_regs = self.registers.iter().filter(|&&r| r == 0).count() as f64;
84 if zero_regs > 0.0 {
85 return (M as f64 * (M as f64 / zero_regs).ln()).round() as u64;
86 }
87 }
88
89 raw_estimate.round() as u64
91 }
92
93 pub fn non_zero_registers(&self) -> usize {
95 self.registers.iter().filter(|&&r| r > 0).count()
96 }
97
98 pub fn clear(&mut self) {
100 self.registers = [0u8; M];
101 }
102
103 fn hash_value<T: Hash>(value: &T) -> u64 {
104 let mut hasher = DefaultHasher::new();
105 value.hash(&mut hasher);
106 hasher.finish()
107 }
108}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113
114 #[test]
115 fn test_empty_hll() {
116 let hll = HyperLogLog::new();
117 assert_eq!(hll.count(), 0);
118 }
119
120 #[test]
121 fn test_single_element() {
122 let mut hll = HyperLogLog::new();
123 hll.insert(&42i64);
124 let count = hll.count();
125 assert!((1..=5).contains(&count), "Expected ~1, got {}", count);
126 }
127
128 #[test]
129 fn test_small_set() {
130 let mut hll = HyperLogLog::new();
131 for i in 0..100i64 {
132 hll.insert(&i);
133 }
134 let count = hll.count();
135 let error = ((count as f64 - 100.0) / 100.0).abs();
136 assert!(
137 error < 0.30,
138 "Expected ~100, got {} (error: {:.1}%)",
139 count,
140 error * 100.0
141 );
142 }
143
144 #[test]
145 fn test_large_set() {
146 let mut hll = HyperLogLog::new();
147 for i in 0..10_000i64 {
148 hll.insert(&i);
149 }
150 let count = hll.count();
151 let error = ((count as f64 - 10_000.0) / 10_000.0).abs();
152 assert!(
153 error < 0.15,
154 "Expected ~10000, got {} (error: {:.1}%)",
155 count,
156 error * 100.0
157 );
158 }
159
160 #[test]
161 fn test_merge() {
162 let mut hll1 = HyperLogLog::new();
163 let mut hll2 = HyperLogLog::new();
164 for i in 0..500i64 {
165 hll1.insert(&i);
166 }
167 for i in 250..750i64 {
168 hll2.insert(&i);
169 }
170 hll1.merge(&hll2);
171 let count = hll1.count();
172 let error = ((count as f64 - 750.0) / 750.0).abs();
173 assert!(
174 error < 0.35,
175 "Expected ~750, got {} (error: {:.1}%)",
176 count,
177 error * 100.0
178 );
179 }
180
181 #[test]
182 fn test_clear() {
183 let mut hll = HyperLogLog::new();
184 for i in 0..1_000i64 {
185 hll.insert(&i);
186 }
187 assert!(hll.count() > 0);
188 hll.clear();
189 assert_eq!(hll.count(), 0);
190 }
191}