use std::hash::Hash;
use std::slice;
use crate::common::ResizeFactor;
use crate::error::Error;
use crate::error::ErrorKind;
use crate::hash::MurmurHash3X64128;
use crate::hash::compute_seed_hash;
use crate::thetacommon::SketchEntry;
use crate::thetacommon::constants::HASH_TABLE_REBUILD_THRESHOLD;
use crate::thetacommon::constants::HASH_TABLE_RESIZE_THRESHOLD;
use crate::thetacommon::constants::MAX_LG_K;
use crate::thetacommon::constants::MAX_THETA;
use crate::thetacommon::constants::MIN_LG_K;
use crate::thetacommon::constants::STRIDE_MASK;
use crate::thetacommon::sketch_state::CompactSketchState;
pub struct SketchHashTableIter<'a, E>(slice::Iter<'a, Option<E>>);
impl<'a, E> Iterator for SketchHashTableIter<'a, E> {
type Item = &'a E;
fn next(&mut self) -> Option<Self::Item> {
self.0.find_map(Option::as_ref)
}
fn size_hint(&self) -> (usize, Option<usize>) {
(0, self.0.size_hint().1)
}
}
#[derive(Debug)]
pub struct SketchHashTable<E> {
lg_cur_size: u8,
lg_nom_size: u8,
lg_max_size: u8,
resize_factor: ResizeFactor,
sampling_probability: f32,
seed: u64,
seed_hash: u16,
retention_theta: u64,
entries: Vec<Option<E>>,
num_retained: usize,
}
impl<E> SketchHashTable<E>
where
E: SketchEntry,
{
pub fn new(
lg_nom_size: u8,
resize_factor: ResizeFactor,
sampling_probability: f32,
seed: u64,
) -> Result<Self, Error> {
if !(MIN_LG_K..=MAX_LG_K).contains(&lg_nom_size) {
return Err(Error::invalid_argument(format!(
"lg_k must be in [{MIN_LG_K}, {MAX_LG_K}], got {lg_nom_size}"
)));
}
if !(sampling_probability > 0.0 && sampling_probability <= 1.0) {
return Err(Error::invalid_argument(format!(
"sampling_probability must be in (0.0, 1.0], got {sampling_probability}"
)));
}
let seed_hash = compute_seed_hash(seed, ErrorKind::InvalidArgument)?;
let lg_max_size = lg_nom_size + 1;
let lg_cur_size = starting_sub_multiple(lg_max_size, MIN_LG_K, resize_factor.lg_value());
Ok(Self::allocate_empty(
lg_cur_size,
lg_nom_size,
resize_factor,
sampling_probability,
starting_retention_theta(sampling_probability),
seed,
seed_hash,
))
}
pub fn for_set_operation(
lg_cur_size: u8,
lg_nom_size: u8,
retention_theta: u64,
seed: u64,
seed_hash: u16,
) -> Self {
Self::allocate_empty(
lg_cur_size,
lg_nom_size,
ResizeFactor::X1,
1.0,
retention_theta,
seed,
seed_hash,
)
}
fn allocate_empty(
lg_cur_size: u8,
lg_nom_size: u8,
resize_factor: ResizeFactor,
sampling_probability: f32,
retention_theta: u64,
seed: u64,
seed_hash: u16,
) -> Self {
let lg_max_size = lg_nom_size + 1;
assert!(
lg_cur_size <= lg_max_size,
"lg_cur_size must be <= lg_nom_size + 1, got lg_cur_size={lg_cur_size}, lg_nom_size={lg_nom_size}"
);
let size = if lg_cur_size > 0 { 1 << lg_cur_size } else { 0 };
let entries = std::iter::repeat_with(|| None).take(size).collect();
Self {
lg_cur_size,
lg_nom_size,
lg_max_size,
resize_factor,
sampling_probability,
seed,
seed_hash,
retention_theta,
entries,
num_retained: 0,
}
}
pub fn hash<T: Hash>(&self, value: T) -> u64 {
let mut hasher = MurmurHash3X64128::with_seed(self.seed);
value.hash(&mut hasher);
let (h1, _) = hasher.finish128();
h1 >> 1 }
pub fn upsert_entry<F>(&mut self, hash: u64, f: F) -> bool
where
F: FnOnce(Option<&mut E>) -> Option<E>,
{
if hash == 0 || hash >= self.retention_theta {
return false;
}
let Some(index) = self.find_in_curr_entries(hash) else {
unreachable!(
"Resize or rebuild should be called to make sure it always can find the entry."
);
};
if let Some(entry) = self.entries[index].as_mut() {
f(Some(entry));
return false;
}
let Some(entry) = f(None) else {
return false;
};
debug_assert_eq!(entry.hash(), hash, "entry hash must match insertion hash");
self.entries[index] = Some(entry);
self.num_retained += 1;
let capacity_threshold = self.capacity_threshold();
if self.num_retained > capacity_threshold {
if self.lg_cur_size <= self.lg_nom_size {
self.resize();
} else {
self.rebuild();
}
}
true
}
pub fn entry(&self, hash: u64) -> Option<&E> {
if hash == 0 {
return None;
}
let index = self.find_in_curr_entries(hash)?;
match &self.entries[index] {
Some(entry) if entry.hash() == hash => Some(entry),
_ => None,
}
}
pub fn capacity_threshold(&self) -> usize {
let fraction = if self.lg_cur_size <= self.lg_nom_size {
HASH_TABLE_RESIZE_THRESHOLD
} else {
HASH_TABLE_REBUILD_THRESHOLD
};
(fraction * self.entries.len() as f64) as usize
}
pub fn trim(&mut self) {
if self.num_retained > (1 << self.lg_nom_size) {
self.rebuild();
}
}
pub fn reset(&mut self) {
let initial_retention_theta = starting_retention_theta(self.sampling_probability);
let init_lg_cur = starting_sub_multiple(
self.lg_nom_size + 1,
MIN_LG_K,
self.resize_factor.lg_value(),
);
let size = 1 << init_lg_cur;
self.entries.clear();
self.entries.resize_with(size, || None);
self.num_retained = 0;
self.retention_theta = initial_retention_theta;
self.lg_cur_size = init_lg_cur;
}
pub fn num_retained(&self) -> usize {
self.num_retained
}
pub fn retention_theta(&self) -> u64 {
self.retention_theta
}
pub fn iter_entries(&self) -> SketchHashTableIter<'_, E> {
SketchHashTableIter(self.entries.iter())
}
pub fn to_non_empty_compact_state(&self, ordered: bool) -> CompactSketchState<E>
where
E: Clone,
{
let mut retained_entries: Vec<E> = self.iter_entries().cloned().collect();
let ordered = ordered || (retained_entries.len() == 1 && self.retention_theta == MAX_THETA);
if ordered && retained_entries.len() > 1 {
retained_entries.sort_unstable_by_key(SketchEntry::hash);
}
CompactSketchState::non_empty(
retained_entries,
self.retention_theta,
self.seed_hash,
ordered,
)
}
pub fn lg_nom_size(&self) -> u8 {
self.lg_nom_size
}
pub fn seed_hash(&self) -> u16 {
self.seed_hash
}
pub fn seed(&self) -> u64 {
self.seed
}
pub fn set_retention_theta(&mut self, retention_theta: u64) {
assert!(
(1..=MAX_THETA).contains(&retention_theta),
"theta must be in [1, {MAX_THETA}], got {retention_theta}"
);
self.retention_theta = retention_theta;
}
pub fn lg_size_from_count_for_rebuild(count: usize, load_factor: f64) -> u8 {
let log2 = |n: usize| {
if n == 0 { 0_u8 } else { n.ilog2() as u8 }
};
let log2_n = log2(count);
log2_n
+ (if count > (((1u128 << ((log2_n as u32) + 1)) as f64) * load_factor) as usize {
2
} else {
1
})
}
pub fn estimated_size(&self) -> usize {
self.entries.capacity() * size_of::<Option<E>>()
}
fn find_in_curr_entries(&self, key: u64) -> Option<usize> {
Self::find_in_entries(&self.entries, key, self.lg_cur_size)
}
fn find_in_entries(entries: &[Option<E>], key: u64, lg_size: u8) -> Option<usize> {
if entries.is_empty() {
return None;
}
let size = entries.len();
let mask = size - 1;
let stride = Self::get_stride(key, lg_size);
let mut index = (key as usize) & mask;
let loop_index = index;
loop {
match &entries[index] {
None => return Some(index),
Some(entry) if entry.hash() == key => return Some(index),
_ => {}
}
index = (index + stride) & mask;
if index == loop_index {
return None;
}
}
}
fn resize(&mut self) {
let new_lg_size = std::cmp::min(
self.lg_cur_size + self.resize_factor.lg_value(),
self.lg_max_size,
);
let new_size = 1 << new_lg_size;
let mut new_entries: Vec<Option<E>> =
std::iter::repeat_with(|| None).take(new_size).collect();
for entry in std::mem::take(&mut self.entries).into_iter().flatten() {
let Some(idx) = Self::find_in_entries(&new_entries, entry.hash(), new_lg_size) else {
unreachable!(
"find_in_entries should always return Some if the entry is not empty."
);
};
new_entries[idx] = Some(entry);
}
self.entries = new_entries;
self.lg_cur_size = new_lg_size;
}
fn rebuild(&mut self) {
let k = 1usize << self.lg_nom_size;
let mut retained: Vec<E> = std::mem::take(&mut self.entries)
.into_iter()
.flatten()
.collect();
let kth_hash = {
let (_lesser, kth, _greater) = retained.select_nth_unstable_by_key(k, |e| e.hash());
kth.hash()
};
self.retention_theta = kth_hash;
retained.truncate(k);
let size = 1 << self.lg_cur_size;
let mut new_entries: Vec<Option<E>> = std::iter::repeat_with(|| None).take(size).collect();
let mut num_inserted = 0;
for entry in retained {
if let Some(idx) = Self::find_in_entries(&new_entries, entry.hash(), self.lg_cur_size) {
new_entries[idx] = Some(entry);
num_inserted += 1;
} else {
unreachable!(
"find_in_entries should always return Some if the entry is not empty."
);
}
}
assert_eq!(
num_inserted, k,
"Number of inserted entries should be equal to k."
);
self.num_retained = num_inserted;
self.entries = new_entries;
}
fn get_stride(key: u64, lg_size: u8) -> usize {
(2 * ((key >> (lg_size)) & STRIDE_MASK) + 1) as usize
}
}
pub fn starting_sub_multiple(lg_target: u8, lg_min: u8, lg_resize_factor: u8) -> u8 {
if lg_target <= lg_min {
lg_min
} else if lg_resize_factor == 0 {
lg_target
} else {
((lg_target - lg_min) % lg_resize_factor) + lg_min
}
}
pub fn starting_retention_theta(sampling_probability: f32) -> u64 {
if sampling_probability < 1.0 {
(MAX_THETA as f64 * sampling_probability as f64) as u64
} else {
MAX_THETA
}
}