This is an automated email from the ASF dual-hosted git repository.

tisonkun pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/datasketches-rust.git


The following commit(s) were added to refs/heads/main by this push:
     new 8c1514f  refactor(theta): theta hash table into raw table, preparing 
for tuple reuse (#146)
8c1514f is described below

commit 8c1514fe79b1140ed10958a8942eb031c84c5bea
Author: Yijun Zhao <[email protected]>
AuthorDate: Fri Jul 10 21:10:20 2026 +0800

    refactor(theta): theta hash table into raw table, preparing for tuple reuse 
(#146)
---
 datasketches/src/theta/hash_table.rs     | 388 +++---------------------------
 datasketches/src/theta/mod.rs            |  17 +-
 datasketches/src/theta/raw_hash_table.rs | 400 +++++++++++++++++++++++++++++++
 3 files changed, 444 insertions(+), 361 deletions(-)

diff --git a/datasketches/src/theta/hash_table.rs 
b/datasketches/src/theta/hash_table.rs
index 1ee498d..4fbd5e6 100644
--- a/datasketches/src/theta/hash_table.rs
+++ b/datasketches/src/theta/hash_table.rs
@@ -16,20 +16,14 @@
 // under the License.
 
 use std::hash::Hash;
+use std::num::NonZeroU64;
 
-use crate::common::ResizeFactor;
-use crate::hash::MurmurHash3X64128;
-use crate::hash::compute_seed_hash;
-use crate::theta::HASH_TABLE_REBUILD_THRESHOLD;
-use crate::theta::HASH_TABLE_RESIZE_THRESHOLD;
-use crate::theta::MAX_THETA;
-use crate::theta::MIN_LG_K;
-
-/// Stride hash bits (7 bits for stride calculation)
-const STRIDE_HASH_BITS: u8 = 7;
-
-/// Stride mask
-const STRIDE_MASK: u64 = (1 << STRIDE_HASH_BITS) - 1;
+use crate::theta::raw_hash_table::RawHashTable;
+use crate::theta::raw_hash_table::RawHashTableEntry;
+#[cfg(test)]
+pub(crate) use crate::theta::raw_hash_table::starting_sub_multiple;
+#[cfg(test)]
+pub(crate) use 
crate::theta::raw_hash_table::starting_theta_from_sampling_probability;
 
 /// Specific hash table for theta sketch
 ///
@@ -38,130 +32,27 @@ const STRIDE_MASK: u64 = (1 << STRIDE_HASH_BITS) - 1;
 /// * After it reaches the capacity bigger than 2^lg_nom_size, every time the 
number of entries
 ///   exceeds the threshold, it will rebuild the table: only keep the min 
2^lg_nom_size entries and
 ///   update the theta to the k-th smallest entry.
-#[derive(Debug)]
-pub(super) struct ThetaHashTable {
-    lg_cur_size: u8,
-    lg_nom_size: u8,
-    lg_max_size: u8,
-    resize_factor: ResizeFactor,
-    sampling_probability: f32,
-    hash_seed: u64,
-
-    // Logical emptiness of the source set.
-    //
-    // * `false` if any update has been attempted (even if screened by theta)
-    // * `true` if no updates have been attempted.
-    //
-    // This can be false even when `num_retained` is 0.
-    is_empty: bool,
-
-    theta: u64,
-
-    entries: Vec<u64>,
-
-    // Number of retained non-zero hashes currently stored in `entries`.
-    num_retained: usize,
-}
-
-impl ThetaHashTable {
-    /// Create a new hash table
-    pub fn new(
-        lg_nom_size: u8,
-        resize_factor: ResizeFactor,
-        sampling_probability: f32,
-        hash_seed: u64,
-    ) -> Self {
-        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());
-        Self::from_raw_parts(
-            lg_cur_size,
-            lg_nom_size,
-            resize_factor,
-            sampling_probability,
-            starting_theta_from_sampling_probability(sampling_probability),
-            hash_seed,
-            true,
-        )
-    }
+pub(super) type ThetaHashTable = RawHashTable<ThetaEntry>;
 
-    /// Constructs a table from raw internal state.
-    ///
-    /// # Panics
-    ///
-    /// Panics if `lg_cur_size > lg_nom_size + 1`. (`lg_nom_size + 1 == 
lg_max_size`)
-    pub fn from_raw_parts(
-        lg_cur_size: u8,
-        lg_nom_size: u8,
-        resize_factor: ResizeFactor,
-        sampling_probability: f32,
-        theta: u64,
-        hash_seed: u64,
-        is_empty: bool,
-    ) -> 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 = vec![0u64; size];
-        Self {
-            lg_cur_size,
-            lg_nom_size,
-            lg_max_size,
-            resize_factor,
-            sampling_probability,
-            hash_seed,
-            is_empty,
-            theta,
-            entries,
-            num_retained: 0,
-        }
-    }
+#[derive(Debug, Clone, Copy)]
+pub(crate) struct ThetaEntry {
+    hash: NonZeroU64,
+}
 
-    /// Hash a value with the table seed and return the hash.
-    fn hash<T: Hash>(&self, value: T) -> u64 {
-        let mut hasher = MurmurHash3X64128::with_seed(self.hash_seed);
-        value.hash(&mut hasher);
-        let (h1, _) = hasher.finish128();
-        h1 >> 1 // To make it compatible with Java version
+impl ThetaEntry {
+    fn new(hash: u64) -> Self {
+        let hash = NonZeroU64::new(hash).expect("hash must be non-zero");
+        Self { hash }
     }
+}
 
-    /// Find an entry in the hash table.
-    ///
-    /// Returns the index of the entry if found, otherwise None. The entry may 
have been inserted or
-    /// empty.
-    fn find_in_curr_entries(&self, key: u64) -> Option<usize> {
-        Self::find_in_entries(&self.entries, key, self.lg_cur_size)
-    }
-
-    /// Find index in a given entries.
-    ///
-    /// Returns the index of the entry if found, otherwise None. The entry may 
have been inserted or
-    /// empty.
-    fn find_in_entries(entries: &[u64], 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 {
-            let probe = entries[index];
-            if probe == 0 || probe == key {
-                return Some(index);
-            }
-            index = (index + stride) & mask;
-            if index == loop_index {
-                return None;
-            }
-        }
+impl RawHashTableEntry for ThetaEntry {
+    fn hash(&self) -> u64 {
+        self.hash.get()
     }
+}
 
+impl ThetaHashTable {
     /// Hashes and inserts a value into the table.
     ///
     /// Returns true if the value was inserted (new), false otherwise.
@@ -174,246 +65,33 @@ impl ThetaHashTable {
     ///
     /// Returns true if the value was inserted (new), false otherwise.
     pub fn try_insert_hash(&mut self, hash: u64) -> bool {
-        self.is_empty = false;
-
-        if hash == 0 || hash >= self.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."
-            );
-        };
-
-        // Already exists
-        if self.entries[index] == hash {
-            return false;
-        }
-
-        assert_eq!(self.entries[index], 0, "Entry should be empty");
-        self.entries[index] = hash;
-        self.num_retained += 1;
-
-        // Check if we need to resize or rebuild
-        let capacity = self.get_capacity();
-        if self.num_retained > capacity {
-            if self.lg_cur_size <= self.lg_nom_size {
-                self.resize();
-            } else {
-                self.rebuild();
-            }
-        }
-        true
-    }
-
-    /// Get capacity threshold
-    fn get_capacity(&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
-    }
-
-    /// Resize the hash table
-    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;
-
-        // Get new entries and rehash all entries
-        let mut new_entries = vec![0u64; new_size];
-        for &entry in &self.entries {
-            if entry != 0 {
-                let new_index = Self::find_in_entries(&new_entries, entry, 
new_lg_size);
-                if let Some(idx) = new_index {
-                    new_entries[idx] = entry;
-                } else {
-                    unreachable!(
-                        "find_in_entries should always return Some if the 
entry is not empty."
-                    );
-                }
-            }
-        }
-
-        self.entries = new_entries;
-        self.lg_cur_size = new_lg_size;
-    }
-
-    /// Rebuild the hash table:
-    /// The number of entries will be reduced to the nominal size k.
-    fn rebuild(&mut self) {
-        // Select the k-th smallest entry as new theta and keep the lesser 
entries.
-        self.entries.retain(|&e| e != 0);
-        let k = 1u64 << self.lg_nom_size;
-        let (lesser, kth, _) = self.entries.select_nth_unstable(k as usize);
-        self.theta = *kth;
-
-        // Rebuild the table with the lesser entries.
-        let size = 1 << self.lg_cur_size;
-        let mut new_entries = vec![0u64; size];
-        let mut num_inserted = 0;
-        for entry in lesser {
-            if let Some(idx) = Self::find_in_entries(&new_entries, *entry, 
self.lg_cur_size) {
-                new_entries[idx] = *entry;
-                num_inserted += 1;
+        self.upsert_entry(hash, |existing| {
+            if existing.is_some() {
+                None
             } else {
-                unreachable!(
-                    "find_in_entries should always return Some if the entry is 
not empty."
-                );
+                Some(ThetaEntry::new(hash))
             }
-        }
-
-        assert_eq!(
-            num_inserted, k as usize,
-            "Number of inserted entries should be equal to k."
-        );
-        self.num_retained = num_inserted;
-        self.entries = new_entries;
-    }
-
-    /// Trim the table to nominal size k
-    pub fn trim(&mut self) {
-        if self.num_retained > (1 << self.lg_nom_size) {
-            self.rebuild();
-        }
-    }
-
-    /// Reset the table to empty state
-    pub fn reset(&mut self) {
-        let init_theta = 
starting_theta_from_sampling_probability(self.sampling_probability);
-        let init_lg_cur = starting_sub_multiple(
-            self.lg_nom_size + 1,
-            MIN_LG_K,
-            self.resize_factor.lg_value(),
-        );
-
-        // clear entries
-        if self.entries.len() != 1 << init_lg_cur {
-            self.entries.resize(1 << init_lg_cur, 0);
-        }
-        self.entries.fill(0);
-        self.num_retained = 0;
-        self.theta = init_theta;
-        self.is_empty = true;
-        self.lg_cur_size = init_lg_cur;
-    }
-
-    /// Return number of retained entries
-    pub fn num_retained(&self) -> usize {
-        self.num_retained
-    }
-
-    /// Get theta
-    pub fn theta(&self) -> u64 {
-        self.theta
-    }
-
-    /// Check if emptiness of the source set
-    pub fn is_empty(&self) -> bool {
-        self.is_empty
+        })
     }
 
-    /// Get iterator over entries
+    /// Get iterator over entries.
     pub fn iter(&self) -> impl Iterator<Item = u64> + '_ {
-        self.entries.iter().copied().filter(|&e| e != 0)
-    }
-
-    /// Get log2 of nominal size
-    pub fn lg_nom_size(&self) -> u8 {
-        self.lg_nom_size
-    }
-
-    /// Get the hash of the seed that was used to hash the input.
-    pub fn seed_hash(&self) -> u16 {
-        compute_seed_hash(self.hash_seed)
+        self.iter_entries().map(RawHashTableEntry::hash)
     }
 
     /// Returns true if the given hash exists in the table.
     pub fn contains_hash(&self, hash: u64) -> bool {
-        if hash == 0 {
-            return false;
-        }
-        let Some(index) = self.find_in_curr_entries(hash) else {
-            return false;
-        };
-        self.entries[index] == hash
-    }
-
-    /// Set empty flag
-    pub fn set_empty(&mut self, is_empty: bool) {
-        self.is_empty = is_empty;
-    }
-
-    /// Get the hash seed used by this table.
-    pub fn hash_seed(&self) -> u64 {
-        self.hash_seed
-    }
-
-    /// Sets theta value.
-    pub fn set_theta(&mut self, theta: u64) {
-        assert!(
-            (1..=MAX_THETA).contains(&theta),
-            "theta must be in [1, {MAX_THETA}], got {theta}"
-        );
-        self.theta = theta;
-    }
-
-    /// Returns minimal lg_size where rebuild-capacity can hold `count`.
-    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
-            })
-    }
-
-    /// Get stride for hash table probing
-    fn get_stride(key: u64, lg_size: u8) -> usize {
-        (2 * ((key >> (lg_size)) & STRIDE_MASK) + 1) as usize
-    }
-
-    /// Returns the estimated size of the heap allocations in bytes
-    pub fn estimated_size(&self) -> usize {
-        self.entries.capacity() * std::mem::size_of::<u64>()
-    }
-}
-
-/// Compute initial lg_size for hash table based on target lg_size, minimum 
lg_size, and resize
-/// factor. Make sure `lg_target = lg_init + n * lg_resize_factor`, where `n` 
is an integer and
-/// `lg_init >= lg_min`
-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
-    }
-}
-
-/// Compute initial theta for hash table based on sampling probability.
-fn starting_theta_from_sampling_probability(sampling_probability: f32) -> u64 {
-    if sampling_probability < 1.0 {
-        (MAX_THETA as f64 * sampling_probability as f64) as u64
-    } else {
-        MAX_THETA
+        self.get_entry(hash).is_some()
     }
 }
 
 #[cfg(test)]
 mod tests {
     use super::*;
+    use crate::common::ResizeFactor;
     use crate::hash::DEFAULT_UPDATE_SEED;
+    use crate::theta::MAX_THETA;
+    use crate::theta::MIN_LG_K;
 
     #[test]
     fn test_new_hash_table() {
diff --git a/datasketches/src/theta/mod.rs b/datasketches/src/theta/mod.rs
index 03b5e2a..2ca938c 100644
--- a/datasketches/src/theta/mod.rs
+++ b/datasketches/src/theta/mod.rs
@@ -42,6 +42,7 @@
 mod bit_pack;
 mod hash_table;
 mod intersection;
+mod raw_hash_table;
 mod serialization;
 mod sketch;
 
@@ -52,14 +53,18 @@ pub use self::sketch::ThetaSketchBuilder;
 pub use self::sketch::ThetaSketchView;
 
 /// Maximum theta value (signed max for compatibility with Java)
-const MAX_THETA: u64 = i64::MAX as u64;
+pub(crate) const MAX_THETA: u64 = i64::MAX as u64;
 /// Minimum log2 of K
-const MIN_LG_K: u8 = 5;
+pub(crate) const MIN_LG_K: u8 = 5;
 /// Maximum log2 of K
-const MAX_LG_K: u8 = 26;
+pub(crate) const MAX_LG_K: u8 = 26;
 /// Default log2 of K
-const DEFAULT_LG_K: u8 = 12;
+pub(crate) const DEFAULT_LG_K: u8 = 12;
 /// Resize threshold (0.5 = 50% load factor)
-const HASH_TABLE_RESIZE_THRESHOLD: f64 = 0.5;
+pub(crate) const HASH_TABLE_RESIZE_THRESHOLD: f64 = 0.5;
 /// Rebuild threshold (15/16 = 93.75% load factor)
-const HASH_TABLE_REBUILD_THRESHOLD: f64 = 15.0 / 16.0;
+pub(crate) const HASH_TABLE_REBUILD_THRESHOLD: f64 = 15.0 / 16.0;
+/// Stride hash bits (7 bits for stride calculation)
+pub(crate) const STRIDE_HASH_BITS: u8 = 7;
+/// Stride mask
+pub(crate) const STRIDE_MASK: u64 = (1 << STRIDE_HASH_BITS) - 1;
diff --git a/datasketches/src/theta/raw_hash_table.rs 
b/datasketches/src/theta/raw_hash_table.rs
new file mode 100644
index 0000000..f171a36
--- /dev/null
+++ b/datasketches/src/theta/raw_hash_table.rs
@@ -0,0 +1,400 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+use std::hash::Hash;
+
+use crate::common::ResizeFactor;
+use crate::hash::MurmurHash3X64128;
+use crate::hash::compute_seed_hash;
+use crate::theta::HASH_TABLE_REBUILD_THRESHOLD;
+use crate::theta::HASH_TABLE_RESIZE_THRESHOLD;
+use crate::theta::MAX_THETA;
+use crate::theta::MIN_LG_K;
+use crate::theta::STRIDE_MASK;
+
+pub(crate) trait RawHashTableEntry {
+    fn hash(&self) -> u64;
+}
+
+/// Generic hash-table mechanics shared by Theta and Tuple sketches.
+///
+/// The entry type supplies the retained hash and any sketch-specific payload. 
The table owns all
+/// theta screening, probing, resizing, rebuilding, trimming, and 
logical-empty state.
+#[derive(Debug)]
+pub(crate) struct RawHashTable<E> {
+    pub(crate) lg_cur_size: u8,
+    pub(crate) lg_nom_size: u8,
+    pub(crate) lg_max_size: u8,
+    pub(crate) resize_factor: ResizeFactor,
+    pub(crate) sampling_probability: f32,
+    pub(crate) hash_seed: u64,
+
+    // Logical emptiness of the source set.
+    //
+    // * `false` if any update has been attempted (even if screened by theta)
+    // * `true` if no updates have been attempted.
+    //
+    // This can be false even when `num_retained` is 0.
+    pub(crate) is_empty: bool,
+
+    pub(crate) theta: u64,
+
+    pub(crate) entries: Vec<Option<E>>,
+
+    // Number of retained non-zero hashes currently stored in `entries`.
+    pub(crate) num_retained: usize,
+}
+
+impl<E> RawHashTable<E>
+where
+    E: RawHashTableEntry,
+{
+    /// Create a new hash table.
+    pub(crate) fn new(
+        lg_nom_size: u8,
+        resize_factor: ResizeFactor,
+        sampling_probability: f32,
+        hash_seed: u64,
+    ) -> Self {
+        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());
+        Self::from_raw_parts(
+            lg_cur_size,
+            lg_nom_size,
+            resize_factor,
+            sampling_probability,
+            starting_theta_from_sampling_probability(sampling_probability),
+            hash_seed,
+            true,
+        )
+    }
+
+    /// Constructs a table from raw internal state.
+    ///
+    /// # Panics
+    ///
+    /// Panics if `lg_cur_size > lg_nom_size + 1`. (`lg_nom_size + 1 == 
lg_max_size`)
+    pub(crate) fn from_raw_parts(
+        lg_cur_size: u8,
+        lg_nom_size: u8,
+        resize_factor: ResizeFactor,
+        sampling_probability: f32,
+        theta: u64,
+        hash_seed: u64,
+        is_empty: bool,
+    ) -> 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,
+            hash_seed,
+            is_empty,
+            theta,
+            entries,
+            num_retained: 0,
+        }
+    }
+
+    /// Hash a value with the table seed and return the hash.
+    pub(crate) fn hash<T: Hash>(&self, value: T) -> u64 {
+        let mut hasher = MurmurHash3X64128::with_seed(self.hash_seed);
+        value.hash(&mut hasher);
+        let (h1, _) = hasher.finish128();
+        h1 >> 1 // To make it compatible with Java version
+    }
+
+    /// Inserts or updates the entry slot for a pre-hashed key.
+    ///
+    /// Returns true if a new entry was created, false otherwise (existing 
key, declined insertion,
+    /// or a hash screened out by theta).
+    pub(crate) fn upsert_entry<F>(&mut self, hash: u64, f: F) -> bool
+    where
+        F: FnOnce(Option<&mut E>) -> Option<E>,
+    {
+        self.is_empty = false;
+
+        if hash == 0 || hash >= self.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;
+
+        // Check if we need to resize or rebuild.
+        let capacity = self.get_capacity();
+        if self.num_retained > capacity {
+            if self.lg_cur_size <= self.lg_nom_size {
+                self.resize();
+            } else {
+                self.rebuild();
+            }
+        }
+        true
+    }
+
+    /// Returns a reference to the entry stored for `hash`, or `None` if the 
hash is not retained.
+    pub(crate) fn get_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,
+        }
+    }
+
+    /// Get capacity threshold.
+    pub(crate) fn get_capacity(&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
+    }
+
+    /// Trim the table to nominal size k.
+    pub(crate) fn trim(&mut self) {
+        if self.num_retained > (1 << self.lg_nom_size) {
+            self.rebuild();
+        }
+    }
+
+    /// Reset the table to empty state.
+    pub(crate) fn reset(&mut self) {
+        let init_theta = 
starting_theta_from_sampling_probability(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.theta = init_theta;
+        self.is_empty = true;
+        self.lg_cur_size = init_lg_cur;
+    }
+
+    /// Return number of retained entries.
+    pub(crate) fn num_retained(&self) -> usize {
+        self.num_retained
+    }
+
+    /// Get theta.
+    pub(crate) fn theta(&self) -> u64 {
+        self.theta
+    }
+
+    /// Check logical emptiness of the source set.
+    pub(crate) fn is_empty(&self) -> bool {
+        self.is_empty
+    }
+
+    /// Get iterator over retained entries.
+    pub(crate) fn iter_entries(&self) -> impl Iterator<Item = &E> + '_ {
+        self.entries.iter().filter_map(Option::as_ref)
+    }
+
+    /// Get log2 of nominal size.
+    pub(crate) fn lg_nom_size(&self) -> u8 {
+        self.lg_nom_size
+    }
+
+    /// Get the hash of the seed that was used to hash the input.
+    pub(crate) fn seed_hash(&self) -> u16 {
+        compute_seed_hash(self.hash_seed)
+    }
+
+    /// Set empty flag.
+    pub(crate) fn set_empty(&mut self, is_empty: bool) {
+        self.is_empty = is_empty;
+    }
+
+    /// Get the hash seed used by this table.
+    pub(crate) fn hash_seed(&self) -> u64 {
+        self.hash_seed
+    }
+
+    /// Sets theta value.
+    pub(crate) fn set_theta(&mut self, theta: u64) {
+        assert!(
+            (1..=MAX_THETA).contains(&theta),
+            "theta must be in [1, {MAX_THETA}], got {theta}"
+        );
+        self.theta = theta;
+    }
+
+    /// Returns minimal lg_size where rebuild-capacity can hold `count`.
+    pub(crate) 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
+            })
+    }
+
+    /// Returns the estimated size of the heap allocations in bytes.
+    pub(crate) fn estimated_size(&self) -> usize {
+        self.entries.capacity() * std::mem::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;
+
+        // Select the k-th smallest entry as new theta and keep the lesser 
entries.
+        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.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
+    }
+}
+
+/// Compute initial lg_size for hash table based on target lg_size, minimum 
lg_size, and resize
+/// factor. Make sure `lg_target = lg_init + n * lg_resize_factor`, where `n` 
is an integer and
+/// `lg_init >= lg_min`.
+pub(crate) 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
+    }
+}
+
+/// Compute initial theta for hash table based on sampling probability.
+pub(crate) fn starting_theta_from_sampling_probability(sampling_probability: 
f32) -> u64 {
+    if sampling_probability < 1.0 {
+        (MAX_THETA as f64 * sampling_probability as f64) as u64
+    } else {
+        MAX_THETA
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to