use yo_common::{Code, Error, Result};
pub const P: u32 = 14;
pub const REGISTERS: usize = 1 << P;
pub const Q: u32 = 64 - P;
const BITS: usize = 6;
const REGISTER_MAX: u32 = 63;
pub const HDR: usize = 16;
pub const DENSE: usize = HDR + REGISTERS * BITS / 8;
pub const SPARSE_MAX: usize = 3000;
const MAGIC: [u8; 4] = *b"HYLL";
const DENSE_TAG: u8 = 0;
const SPARSE_TAG: u8 = 1;
const SEED: u64 = 0xadc8_3b19;
const XZERO_BIT: u8 = 0x40;
const VAL_BIT: u8 = 0x80;
const ZERO_MAX: usize = 64;
const VAL_MAX: u8 = 32;
const VAL_MAX_LEN: usize = 4;
const NOT_HLL: &str = "Key is not a valid HyperLogLog string value.";
const CORRUPT: &str = "Corrupted HLL object detected";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Encoding {
Dense,
Sparse,
}
impl Encoding {
#[must_use]
pub const fn name(self) -> &'static str {
match self {
Encoding::Dense => "dense",
Encoding::Sparse => "sparse",
}
}
}
#[must_use]
pub fn not_hll() -> Error {
Error::new(Code::WrongType, NOT_HLL)
}
#[must_use]
pub fn corrupt() -> Error {
Error::new(Code::Corrupt, CORRUPT)
}
#[must_use]
pub fn hash(ele: &[u8]) -> u64 {
const M: u64 = 0xc6a4_a793_5bd1_e995;
const R: u32 = 47;
let mut h = SEED ^ (ele.len() as u64).wrapping_mul(M);
let (blocks, tail) = ele.as_chunks::<8>();
for block in blocks {
let mut k = u64::from_le_bytes(*block);
k = k.wrapping_mul(M);
k ^= k >> R;
k = k.wrapping_mul(M);
h ^= k;
h = h.wrapping_mul(M);
}
if !tail.is_empty() {
for (i, &b) in tail.iter().enumerate() {
h ^= u64::from(b) << (8 * i);
}
h = h.wrapping_mul(M);
}
h ^= h >> R;
h = h.wrapping_mul(M);
h ^= h >> R;
h
}
#[must_use]
pub fn place(ele: &[u8]) -> (usize, u8) {
let h = hash(ele);
let index = (h & (REGISTERS as u64 - 1)) as usize;
let rest = (h >> P) | (1 << Q);
(index, rest.trailing_zeros() as u8 + 1)
}
pub fn empty(out: &mut Vec<u8>) {
out.clear();
out.extend_from_slice(&MAGIC);
out.push(SPARSE_TAG);
out.extend_from_slice(&[0; 3]);
out.extend_from_slice(&[0; 8]);
let mut left = REGISTERS;
while left > 0 {
let run = left.min(1 << P);
out.extend_from_slice(&xzero_bytes(run));
left -= run;
}
}
pub fn check(bytes: &[u8]) -> Result<Encoding> {
if bytes.len() < HDR || bytes[..4] != MAGIC {
return Err(not_hll());
}
match bytes[4] {
DENSE_TAG if bytes.len() == DENSE => Ok(Encoding::Dense),
SPARSE_TAG => Ok(Encoding::Sparse),
_ => Err(not_hll()),
}
}
#[must_use]
pub fn cached(bytes: &[u8]) -> Option<u64> {
let card = u64::from_le_bytes(bytes[8..16].try_into().expect("eight bytes"));
(card >> 63 == 0).then_some(card)
}
pub fn cache(bytes: &mut [u8], n: u64) {
bytes[8..16].copy_from_slice(&(n & !(1 << 63)).to_le_bytes());
}
pub fn invalidate(bytes: &mut [u8]) {
bytes[15] |= 0x80;
}
#[must_use]
#[inline]
pub fn dense_get(regs: &[u8], index: usize) -> u8 {
let bit = index * BITS;
let (byte, shift) = (bit / 8, (bit % 8) as u32);
let low = u32::from(regs[byte]);
let high = regs.get(byte + 1).map_or(0, |&b| u32::from(b));
(((low >> shift) | (high << (8 - shift))) & REGISTER_MAX) as u8
}
#[inline]
pub fn dense_set(regs: &mut [u8], index: usize, val: u8) -> bool {
if dense_get(regs, index) >= val {
return false;
}
let bit = index * BITS;
let (byte, shift) = (bit / 8, (bit % 8) as u32);
let v = u32::from(val);
regs[byte] = ((u32::from(regs[byte]) & !(REGISTER_MAX << shift)) | (v << shift)) as u8;
if shift > 2 {
let rest = 8 - shift;
let high = &mut regs[byte + 1];
*high = ((u32::from(*high) & !(REGISTER_MAX >> rest)) | (v >> rest)) as u8;
}
true
}
const fn is_zero(b: u8) -> bool {
b & 0xc0 == 0
}
const fn is_xzero(b: u8) -> bool {
b & 0xc0 == XZERO_BIT
}
const fn is_val(b: u8) -> bool {
b & VAL_BIT != 0
}
const fn zero_len(b: u8) -> usize {
(b & 0x3f) as usize + 1
}
const fn xzero_len(a: u8, b: u8) -> usize {
(((a & 0x3f) as usize) << 8 | b as usize) + 1
}
const fn val_value(b: u8) -> u8 {
((b >> 2) & 0x1f) + 1
}
const fn val_len(b: u8) -> usize {
(b & 3) as usize + 1
}
const fn val_byte(val: u8, len: usize) -> u8 {
((val - 1) << 2) | (len as u8 - 1) | VAL_BIT
}
const fn zero_byte(len: usize) -> u8 {
(len - 1) as u8
}
const fn xzero_bytes(len: usize) -> [u8; 2] {
let n = len - 1;
[((n >> 8) as u8) | XZERO_BIT, (n & 0xff) as u8]
}
fn opcode(sparse: &[u8], at: usize) -> Option<(usize, usize)> {
let b = *sparse.get(at)?;
if is_zero(b) {
Some((zero_len(b), 1))
} else if is_xzero(b) {
Some((xzero_len(b, *sparse.get(at + 1)?), 2))
} else {
Some((val_len(b), 1))
}
}
fn walk(sparse: &[u8], mut each: impl FnMut(u8, usize, usize)) -> bool {
let mut at = 0;
let mut index = 0;
while at < sparse.len() {
let b = sparse[at];
if is_val(b) {
let len = val_len(b);
if index + len > REGISTERS {
return false;
}
each(val_value(b), index, len);
index += len;
at += 1;
} else if is_zero(b) {
index += zero_len(b);
at += 1;
} else {
let Some(&next) = sparse.get(at + 1) else {
return false;
};
index += xzero_len(b, next);
at += 2;
}
}
index == REGISTERS
}
pub fn to_dense(buf: &mut Vec<u8>) -> bool {
if buf[4] == DENSE_TAG {
return true;
}
let mut regs = [0u8; REGISTERS];
if !walk(&buf[HDR..], |val, at, len| {
regs[at..at + len].fill(val);
}) {
return false;
}
buf.truncate(HDR);
buf.resize(DENSE, 0);
buf[4] = DENSE_TAG;
let body = &mut buf[HDR..];
for (i, &val) in regs.iter().enumerate() {
if val != 0 {
dense_set(body, i, val);
}
}
true
}
pub fn set(buf: &mut Vec<u8>, index: usize, val: u8) -> Option<bool> {
if buf[4] == DENSE_TAG {
let changed = dense_set(&mut buf[HDR..], index, val);
if changed {
invalidate(buf);
}
return Some(changed);
}
if val > VAL_MAX {
return promote(buf, index, val);
}
let (mut at, mut first, mut prev, mut span) = (HDR, 0usize, None, 0usize);
while at < buf.len() {
let (covers, bytes) = opcode(buf, at)?;
span = covers;
if index < first + span {
break;
}
prev = Some(at);
at += bytes;
first += span;
}
if span == 0 || at >= buf.len() {
return None;
}
let here = buf[at];
let (zero, xzero, run) = if is_val(here) {
(false, false, val_len(here))
} else if is_zero(here) {
(true, false, zero_len(here))
} else {
(false, true, xzero_len(here, *buf.get(at + 1)?))
};
if is_val(here) {
if val_value(here) >= val {
return Some(false);
}
if run == 1 {
buf[at] = val_byte(val, 1);
return Some(finish(buf, prev));
}
}
if zero && run == 1 {
buf[at] = val_byte(val, 1);
return Some(finish(buf, prev));
}
let mut seq = [0u8; 5];
let mut n = 0;
let last = first + span - 1;
let gap = |seq: &mut [u8; 5], n: &mut usize, len: usize| {
if len > ZERO_MAX {
seq[*n..*n + 2].copy_from_slice(&xzero_bytes(len));
*n += 2;
} else {
seq[*n] = zero_byte(len);
*n += 1;
}
};
if zero || xzero {
if index != first {
gap(&mut seq, &mut n, index - first);
}
seq[n] = val_byte(val, 1);
n += 1;
if index != last {
gap(&mut seq, &mut n, last - index);
}
} else {
let had = val_value(here);
if index != first {
seq[n] = val_byte(had, index - first);
n += 1;
}
seq[n] = val_byte(val, 1);
n += 1;
if index != last {
seq[n] = val_byte(had, last - index);
n += 1;
}
}
let old = if xzero { 2 } else { 1 };
let end = buf.len();
if n > old && end + (n - old) > SPARSE_MAX {
return promote(buf, index, val);
}
if n > old {
buf.resize(end + (n - old), 0);
buf.copy_within(at + old..end, at + n);
} else if n < old {
buf.copy_within(at + old..end, at + n);
buf.truncate(end - (old - n));
}
buf[at..at + n].copy_from_slice(&seq[..n]);
Some(finish(buf, prev))
}
fn promote(buf: &mut Vec<u8>, index: usize, val: u8) -> Option<bool> {
if !to_dense(buf) {
return None;
}
let changed = dense_set(&mut buf[HDR..], index, val);
invalidate(buf);
Some(changed)
}
fn finish(buf: &mut Vec<u8>, prev: Option<usize>) -> bool {
let mut at = prev.unwrap_or(HDR);
let mut left = 5;
while at < buf.len() && left > 0 {
left -= 1;
let b = buf[at];
if is_xzero(b) {
at += 2;
continue;
}
if is_zero(b) {
at += 1;
continue;
}
if let Some(&next) = buf.get(at + 1)
&& is_val(next)
&& val_value(b) == val_value(next)
{
let len = val_len(b) + val_len(next);
if len <= VAL_MAX_LEN {
buf[at + 1] = val_byte(val_value(b), len);
let end = buf.len();
buf.copy_within(at + 1..end, at);
buf.truncate(end - 1);
continue;
}
}
at += 1;
}
invalidate(buf);
true
}
fn histogram(bytes: &[u8], enc: Encoding) -> Option<[u32; 64]> {
let mut hist = [0u32; 64];
match enc {
Encoding::Dense => {
let regs = &bytes[HDR..];
for i in 0..REGISTERS {
hist[dense_get(regs, i) as usize] += 1;
}
}
Encoding::Sparse => {
let mut seen = 0;
if !walk(&bytes[HDR..], |val, _, len| {
hist[val as usize] += len as u32;
seen += len as u32;
}) {
return None;
}
hist[0] = REGISTERS as u32 - seen;
}
}
Some(hist)
}
pub fn merge(max: &mut [u8; REGISTERS], bytes: &[u8], enc: Encoding) -> bool {
match enc {
Encoding::Dense => {
let regs = &bytes[HDR..];
for (i, slot) in max.iter_mut().enumerate() {
*slot = (*slot).max(dense_get(regs, i));
}
true
}
Encoding::Sparse => walk(&bytes[HDR..], |val, at, len| {
for slot in &mut max[at..at + len] {
*slot = (*slot).max(val);
}
}),
}
}
fn tau(mut x: f64) -> f64 {
if x == 0.0 || x == 1.0 {
return 0.0;
}
let mut y = 1.0;
let mut z = 1.0 - x;
loop {
x = x.sqrt();
let was = z;
y *= 0.5;
z -= (1.0 - x).powi(2) * y;
if was == z {
return z / 3.0;
}
}
}
fn sigma(mut x: f64) -> f64 {
if x == 1.0 {
return f64::INFINITY;
}
let mut y = 1.0;
let mut z = x;
loop {
x *= x;
let was = z;
z += x * y;
y += y;
if was == z {
return z;
}
}
}
#[must_use]
pub fn estimate(hist: &[u32; 64]) -> u64 {
const ALPHA_INF: f64 = 0.721_347_520_444_481_7;
let m = REGISTERS as f64;
let mut z = m * tau((m - f64::from(hist[Q as usize + 1])) / m);
for j in (1..=Q as usize).rev() {
z += f64::from(hist[j]);
z *= 0.5;
}
z += m * sigma(f64::from(hist[0]) / m);
(ALPHA_INF * m * m / z).round() as u64
}
pub fn count(bytes: &[u8], enc: Encoding) -> Result<u64> {
match histogram(bytes, enc) {
Some(hist) => Ok(estimate(&hist)),
None => Err(corrupt()),
}
}
pub fn decode(bytes: &[u8], out: &mut Vec<u8>) {
use std::io::Write;
let sparse = &bytes[HDR..];
let mut at = 0;
while at < sparse.len() {
if !out.is_empty() {
out.push(b' ');
}
let b = sparse[at];
if is_val(b) {
let _ = write!(out, "v:{},{}", val_value(b), val_len(b));
at += 1;
} else if is_zero(b) {
let _ = write!(out, "z:{}", zero_len(b));
at += 1;
} else {
let Some(&next) = sparse.get(at + 1) else {
return;
};
let _ = write!(out, "Z:{}", xzero_len(b, next));
at += 2;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn an_element_lands_where_a_real_server_puts_it() {
assert_eq!(place(b"a"), (12711, 2));
assert_eq!(place(b"b"), (15780, 1));
assert_eq!(place(b"c"), (8436, 1));
}
#[test]
fn a_sketch_is_the_bytes_a_real_server_writes() {
let mut buf = Vec::new();
empty(&mut buf);
assert_eq!(buf.len(), 18);
assert_eq!(&buf[..], b"HYLL\x01\0\0\0\0\0\0\0\0\0\0\0\x7f\xff");
invalidate(&mut buf);
assert_eq!(&buf[..], b"HYLL\x01\0\0\0\0\0\0\0\0\0\0\x80\x7f\xff");
for ele in [&b"a"[..], b"b", b"c"] {
let (index, val) = place(ele);
assert_eq!(set(&mut buf, index, val), Some(true));
}
assert_eq!(
&buf[..],
b"HYLL\x01\0\0\0\0\0\0\0\0\0\0\x80\x60\xf3\x80\x50\xb1\x84\x4b\xfb\x80\x42\x5a"
);
}
#[test]
fn writing_the_same_element_twice_is_not_a_change() {
let mut buf = Vec::new();
empty(&mut buf);
let (index, val) = place(b"a");
assert_eq!(set(&mut buf, index, val), Some(true));
let before = buf.clone();
assert_eq!(set(&mut buf, index, val), Some(false));
assert_eq!(buf, before);
}
#[test]
fn a_dense_register_is_six_bits_packed_from_the_bottom() {
let mut regs = vec![0u8; REGISTERS * BITS / 8];
let mut want = vec![0u8; REGISTERS];
for (i, slot) in want.iter_mut().enumerate() {
*slot = ((i * 7 + 1) % 52) as u8;
}
for step in [1usize, 3, 5] {
let mut i = 0;
while i < REGISTERS {
let val = want[i];
if val > dense_get(®s, i) {
assert!(dense_set(&mut regs, i, val));
}
i += step;
}
}
for (i, &val) in want.iter().enumerate() {
assert_eq!(dense_get(®s, i), val, "register {i}");
}
assert!(!dense_set(&mut regs, 5, 0));
}
#[test]
fn turning_dense_keeps_every_register() {
let mut buf = Vec::new();
empty(&mut buf);
let mut want = [0u8; REGISTERS];
for i in 0..400 {
let ele = format!("e:{i}");
let (index, val) = place(ele.as_bytes());
set(&mut buf, index, val).expect("a write");
want[index] = want[index].max(val);
}
let sparse = count(&buf, Encoding::Sparse).expect("a count");
assert!(to_dense(&mut buf));
assert_eq!(buf.len(), DENSE);
assert_eq!(check(&buf).expect("a sketch"), Encoding::Dense);
for (i, &val) in want.iter().enumerate() {
assert_eq!(dense_get(&buf[HDR..], i), val, "register {i}");
}
assert_eq!(count(&buf, Encoding::Dense).expect("a count"), sparse);
}
#[test]
fn a_sketch_turns_dense_when_it_outgrows_the_sparse_form() {
let mut buf = Vec::new();
empty(&mut buf);
for i in 0..2000 {
let ele = format!("e:{i}");
let (index, val) = place(ele.as_bytes());
set(&mut buf, index, val).expect("a write");
assert!(buf.len() <= SPARSE_MAX || buf.len() == DENSE);
}
assert_eq!(check(&buf).expect("a sketch"), Encoding::Dense);
}
#[test]
fn a_large_register_turns_the_sketch_dense() {
let mut buf = Vec::new();
empty(&mut buf);
assert_eq!(set(&mut buf, 100, VAL_MAX), Some(true));
assert_eq!(check(&buf).expect("a sketch"), Encoding::Sparse);
assert_eq!(set(&mut buf, 200, VAL_MAX + 1), Some(true));
assert_eq!(check(&buf).expect("a sketch"), Encoding::Dense);
assert_eq!(dense_get(&buf[HDR..], 100), VAL_MAX);
assert_eq!(dense_get(&buf[HDR..], 200), VAL_MAX + 1);
}
#[test]
fn neighbouring_runs_of_the_same_value_are_joined() {
let mut buf = Vec::new();
empty(&mut buf);
for i in 0..4 {
set(&mut buf, 100 + i, 1).expect("a write");
}
let mut decoded = Vec::new();
decode(&buf, &mut decoded);
assert_eq!(decoded, b"Z:100 v:1,4 Z:16280");
}
#[test]
fn all_three_opcodes_decode_the_way_a_real_server_prints_them() {
let mut buf = Vec::new();
empty(&mut buf);
buf.truncate(HDR);
buf.extend_from_slice(&xzero_bytes(100));
buf.push(val_byte(1, 4));
buf.push(zero_byte(10));
buf.push(val_byte(3, 2));
buf.extend_from_slice(&xzero_bytes(REGISTERS - 100 - 4 - 10 - 2));
let mut decoded = Vec::new();
decode(&buf, &mut decoded);
assert_eq!(decoded, b"Z:100 v:1,4 z:10 v:3,2 Z:16268");
assert_eq!(count(&buf, Encoding::Sparse).expect("a count"), 6);
}
#[test]
fn the_estimate_is_close_to_the_truth() {
for n in [10usize, 100, 1000, 10_000, 100_000] {
let mut buf = Vec::new();
empty(&mut buf);
for i in 0..n {
let ele = format!("element:{i}");
let (index, val) = place(ele.as_bytes());
set(&mut buf, index, val).expect("a write");
}
let enc = check(&buf).expect("a sketch");
let got = count(&buf, enc).expect("a count") as f64;
let off = (got - n as f64).abs() / n as f64;
assert!(off < 0.02, "{n} counted as {got}");
}
}
#[test]
fn the_estimate_is_the_number_a_real_server_gives() {
for (n, want) in [(100usize, 100u64), (1000, 995), (10_000, 10_077)] {
let mut buf = Vec::new();
empty(&mut buf);
for i in 0..n {
let ele = format!("e:{i}");
let (index, val) = place(ele.as_bytes());
set(&mut buf, index, val).expect("a write");
}
let enc = check(&buf).expect("a sketch");
assert_eq!(count(&buf, enc).expect("a count"), want, "{n} elements");
}
}
#[test]
fn the_two_sizes_are_the_ones_a_real_server_has() {
let build = |n: usize| {
let mut buf = Vec::new();
empty(&mut buf);
for i in 0..n {
let ele = format!("e:{i}");
let (index, val) = place(ele.as_bytes());
set(&mut buf, index, val).expect("a write");
}
buf
};
assert_eq!(build(1000).len(), 1880);
assert_eq!(build(10_000).len(), DENSE);
assert_eq!(DENSE, 12304);
const { assert!(1880 <= SPARSE_MAX) };
}
#[test]
fn a_string_that_is_not_a_sketch_is_refused() {
assert!(check(b"").is_err());
assert!(check(b"HYLL").is_err());
assert!(check(b"NOPE\x01\0\0\0\0\0\0\0\0\0\0\0\x7f\xff").is_err());
assert!(check(b"HYLL\x02\0\0\0\0\0\0\0\0\0\0\0\x7f\xff").is_err());
assert!(check(b"HYLL\0\0\0\0\0\0\0\0\0\0\0\0\x7f\xff").is_err());
let mut buf = Vec::new();
empty(&mut buf);
assert_eq!(cached(&buf), Some(0));
cache(&mut buf, 12345);
assert_eq!(cached(&buf), Some(12345));
invalidate(&mut buf);
assert_eq!(cached(&buf), None);
}
#[test]
fn a_body_that_does_not_add_up_is_corrupt() {
let mut buf = Vec::new();
empty(&mut buf);
buf.truncate(buf.len() - 1);
assert!(count(&buf, Encoding::Sparse).is_err());
let mut short = Vec::new();
empty(&mut short);
short.pop();
short.pop();
assert!(count(&short, Encoding::Sparse).is_err());
}
#[test]
fn merging_takes_the_larger_of_every_register() {
let build = |from: usize, to: usize| {
let mut buf = Vec::new();
empty(&mut buf);
for i in from..to {
let ele = format!("e:{i}");
let (index, val) = place(ele.as_bytes());
set(&mut buf, index, val).expect("a write");
}
buf
};
let a = build(0, 500);
let b = build(400, 900);
let mut max = [0u8; REGISTERS];
assert!(merge(&mut max, &a, Encoding::Sparse));
assert!(merge(&mut max, &b, Encoding::Sparse));
let both = build(0, 900);
let mut want = [0u8; REGISTERS];
assert!(merge(&mut want, &both, check(&both).expect("a sketch")));
assert_eq!(max, want);
}
}