const MAX_SLOTS: usize = 1 << 25;
const MAX_DISTINCT: usize = MAX_SLOTS / 8 * 7;
const SETS: usize = 1 << SET_BITS;
const SET_BITS: u32 = 8;
const BUFFERED: usize = 64;
const FIRST_SLOTS: usize = 1 << 4;
fn full(len: usize, slots: usize) -> bool {
len * 8 >= slots * 7
}
fn hash(value: u64) -> u64 {
value.wrapping_mul(0x9E37_79B9_7F4A_7C15)
}
#[derive(Debug)]
pub(crate) struct ExactDistinct {
sets: Vec<Vec<u64>>,
held: Vec<usize>,
buffered: Vec<u64>,
waiting: Vec<u8>,
zero: bool,
len: usize,
gave_up: bool,
}
impl ExactDistinct {
pub(crate) fn new() -> Self {
Self {
sets: vec![vec![0; FIRST_SLOTS]; SETS],
held: vec![0; SETS],
buffered: vec![0; SETS * BUFFERED],
waiting: vec![0; SETS],
zero: false,
len: 0,
gave_up: false,
}
}
pub(crate) fn insert(&mut self, value: u64) {
if self.gave_up {
return;
}
if value == 0 {
self.zero = true;
return;
}
let hash = hash(value);
let set = (hash >> (64 - SET_BITS)) as usize;
let waiting = usize::from(self.waiting[set]);
self.buffered[set * BUFFERED + waiting] = hash;
self.waiting[set] = (waiting + 1) as u8;
if waiting + 1 == BUFFERED {
self.drain(set);
}
}
pub(crate) fn count(&mut self) -> Option<u64> {
for set in 0..SETS {
if self.gave_up {
break;
}
self.drain(set);
}
(!self.gave_up).then(|| self.len as u64 + u64::from(self.zero))
}
fn drain(&mut self, set: usize) {
let waiting = usize::from(std::mem::take(&mut self.waiting[set]));
let from = set * BUFFERED;
for at in from..from + waiting {
let hash = self.buffered[at];
if !place(&mut self.sets[set], hash) {
continue;
}
self.held[set] += 1;
self.len += 1;
if full(self.held[set], self.sets[set].len()) {
let wanted = self.sets[set].len() * 2;
let old = std::mem::replace(&mut self.sets[set], vec![0; wanted]);
for hash in old.into_iter().filter(|&hash| hash != 0) {
place(&mut self.sets[set], hash);
}
}
}
if self.len >= MAX_DISTINCT {
self.gave_up = true;
self.sets = Vec::new();
self.buffered = Vec::new();
}
}
}
fn place(slots: &mut [u64], hash: u64) -> bool {
let mask = slots.len() - 1;
let mut at = ((hash << SET_BITS) >> (64 - slots.len().trailing_zeros())) as usize;
loop {
match slots[at] {
0 => {
slots[at] = hash;
return true;
}
held if held == hash => return false,
_ => at = (at + 1) & mask,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn counts_each_value_once_across_growth_and_counts_zero() {
let mut set = ExactDistinct::new();
let mut oracle = std::collections::HashSet::new();
for round in 0..3 {
for value in 0..50_000_u64 {
for bits in [value.wrapping_mul(0x0123_4567_89AB_CDEF), (-(value as i64)) as u64] {
set.insert(bits);
oracle.insert(bits);
}
}
assert_eq!(set.count(), Some(oracle.len() as u64), "round {round} counted wrong");
}
}
#[test]
fn a_column_past_the_cap_records_nothing() {
let mut set = ExactDistinct::new();
for value in 1..MAX_DISTINCT as u64 {
set.insert(value);
}
set.insert(0);
assert_eq!(set.count(), Some(MAX_DISTINCT as u64), "gave up before the cap");
set.insert(MAX_DISTINCT as u64);
assert_eq!(set.count(), None, "counted past the cap");
assert!(set.sets.is_empty(), "a column that gave up still holds its table");
}
}