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 47f012d refactor: make sketch builders fallible (#236)
47f012d is described below
commit 47f012d59506fea734dac1995784aca75b4f4a60
Author: tison <[email protected]>
AuthorDate: Fri Aug 28 15:55:22 2026 +0800
refactor: make sketch builders fallible (#236)
Signed-off-by: tison <[email protected]>
---
CHANGELOG.md | 1 +
datasketches/src/bloom/mod.rs | 18 ++-
datasketches/src/bloom/sketch.rs | 155 +++++++++++++--------
datasketches/src/req/sketch.rs | 10 +-
datasketches/src/tdigest/sketch.rs | 3 +-
datasketches/src/thetafamily/common/hash_table.rs | 56 +++++++-
datasketches/src/thetafamily/common/union.rs | 16 +++
datasketches/src/thetafamily/theta/a_not_b.rs | 4 +-
.../src/thetafamily/theta/jaccard_similarity.rs | 4 +-
datasketches/src/thetafamily/theta/mod.rs | 2 +-
datasketches/src/thetafamily/theta/sketch.rs | 73 ++++------
datasketches/src/thetafamily/theta/union.rs | 44 +++---
datasketches/src/thetafamily/tuple/a_not_b.rs | 4 +-
datasketches/src/thetafamily/tuple/intersection.rs | 4 +-
.../src/thetafamily/tuple/jaccard_similarity.rs | 4 +-
datasketches/src/thetafamily/tuple/mod.rs | 2 +-
datasketches/src/thetafamily/tuple/sketch.rs | 49 +++----
datasketches/src/thetafamily/tuple/union.rs | 46 +++---
tests-integration/tests/bloom_test/sketch.rs | 47 ++++---
tests-integration/tests/serde_tests/bloom.rs | 8 +-
tests-integration/tests/serde_tests/theta.rs | 6 +-
tests-integration/tests/serde_tests/tuple.rs | 12 +-
tests-integration/tests/theta_test/a_not_b.rs | 16 +--
tests-integration/tests/theta_test/intersection.rs | 27 ++--
.../tests/theta_test/jaccard_similarity.rs | 25 ++--
tests-integration/tests/theta_test/sketch.rs | 51 ++++---
tests-integration/tests/theta_test/union.rs | 106 ++++++++------
tests-integration/tests/tuple_test/a_not_b.rs | 26 ++--
tests-integration/tests/tuple_test/intersection.rs | 18 +--
.../tests/tuple_test/jaccard_similarity.rs | 26 ++--
tests-integration/tests/tuple_test/main.rs | 2 +-
tests-integration/tests/tuple_test/sketch.rs | 42 ++++--
tests-integration/tests/tuple_test/union.rs | 42 ++++--
33 files changed, 565 insertions(+), 384 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6b4ffd6..74a5080 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,7 @@ All significant changes to this project will be documented in
this file.
### Breaking changes
* Change `ThetaIntersection::to_sketch` and `TupleIntersection::to_sketch` to
return `Option`. Callers must handle `None` before the first successful update;
after that, the methods return `Some` even when the intersection is empty.
+* Change `BloomFilterBuilder`, `ThetaSketchBuilder`, `ThetaUnionBuilder`,
`TupleSketchBuilder`, and `TupleUnionBuilder` to validate their configuration
in `build`, which now returns `Result`. Callers must handle construction errors
instead of relying on builder setters or constructors to panic.
### New features
diff --git a/datasketches/src/bloom/mod.rs b/datasketches/src/bloom/mod.rs
index 4ffa0f8..92e5ed8 100644
--- a/datasketches/src/bloom/mod.rs
+++ b/datasketches/src/bloom/mod.rs
@@ -38,7 +38,9 @@
//! use datasketches::bloom::BloomFilterBuilder;
//!
//! // Create a filter optimized for 1000 items with 1% false positive rate
-//! let mut filter = BloomFilterBuilder::with_accuracy(1000, 0.01).build();
+//! let mut filter = BloomFilterBuilder::with_accuracy(1000, 0.01)
+//! .build()
+//! .unwrap();
//!
//! // Insert items
//! filter.insert("apple");
@@ -71,7 +73,8 @@
//! 0.01, // Target false positive probability (1%)
//! )
//! .seed(9001) // Optional: custom seed
-//! .build();
+//! .build()
+//! .unwrap();
//! ```
//!
//! ## By Size (Manual)
@@ -85,7 +88,8 @@
//! 95_851, // Number of bits
//! 7, // Number of hash functions
//! )
-//! .build();
+//! .build()
+//! .unwrap();
//! ```
//!
//! # Set Operations
@@ -95,8 +99,12 @@
//! ```
//! use datasketches::bloom::BloomFilterBuilder;
//!
-//! let mut filter1 = BloomFilterBuilder::with_accuracy(100, 0.01).build();
-//! let mut filter2 = BloomFilterBuilder::with_accuracy(100, 0.01).build();
+//! let mut filter1 = BloomFilterBuilder::with_accuracy(100, 0.01)
+//! .build()
+//! .unwrap();
+//! let mut filter2 = BloomFilterBuilder::with_accuracy(100, 0.01)
+//! .build()
+//! .unwrap();
//!
//! filter1.insert("a");
//! filter2.insert("b");
diff --git a/datasketches/src/bloom/sketch.rs b/datasketches/src/bloom/sketch.rs
index af510f0..fbd1308 100644
--- a/datasketches/src/bloom/sketch.rs
+++ b/datasketches/src/bloom/sketch.rs
@@ -63,7 +63,9 @@ impl BloomFilter {
/// ```
/// use datasketches::bloom::BloomFilterBuilder;
///
- /// let mut filter = BloomFilterBuilder::with_accuracy(100, 0.01).build();
+ /// let mut filter = BloomFilterBuilder::with_accuracy(100, 0.01)
+ /// .build()
+ /// .unwrap();
/// filter.insert("apple");
///
/// assert!(filter.contains(&"apple")); // true - possibly present (and
known to be inserted here)
@@ -87,7 +89,9 @@ impl BloomFilter {
/// ```
/// use datasketches::bloom::BloomFilterBuilder;
///
- /// let mut filter = BloomFilterBuilder::with_accuracy(100, 0.01).build();
+ /// let mut filter = BloomFilterBuilder::with_accuracy(100, 0.01)
+ /// .build()
+ /// .unwrap();
///
/// let was_present = filter.contains_and_insert(&"apple");
/// assert!(!was_present); // First insertion
@@ -111,7 +115,9 @@ impl BloomFilter {
/// ```
/// use datasketches::bloom::BloomFilterBuilder;
///
- /// let mut filter = BloomFilterBuilder::with_accuracy(100, 0.01).build();
+ /// let mut filter = BloomFilterBuilder::with_accuracy(100, 0.01)
+ /// .build()
+ /// .unwrap();
///
/// filter.insert("apple");
/// filter.insert(42_u64);
@@ -133,7 +139,9 @@ impl BloomFilter {
/// ```
/// use datasketches::bloom::BloomFilterBuilder;
///
- /// let mut filter = BloomFilterBuilder::with_accuracy(100, 0.01).build();
+ /// let mut filter = BloomFilterBuilder::with_accuracy(100, 0.01)
+ /// .build()
+ /// .unwrap();
/// filter.insert("apple");
/// assert!(!filter.is_empty());
///
@@ -163,10 +171,12 @@ impl BloomFilter {
///
/// let mut f1 = BloomFilterBuilder::with_accuracy(100, 0.01)
/// .seed(123)
- /// .build();
+ /// .build()
+ /// .unwrap();
/// let mut f2 = BloomFilterBuilder::with_accuracy(100, 0.01)
/// .seed(123)
- /// .build();
+ /// .build()
+ /// .unwrap();
///
/// f1.insert("a");
/// f2.insert("b");
@@ -206,10 +216,12 @@ impl BloomFilter {
///
/// let mut f1 = BloomFilterBuilder::with_accuracy(100, 0.01)
/// .seed(123)
- /// .build();
+ /// .build()
+ /// .unwrap();
/// let mut f2 = BloomFilterBuilder::with_accuracy(100, 0.01)
/// .seed(123)
- /// .build();
+ /// .build()
+ /// .unwrap();
///
/// f1.insert("a");
/// f1.insert("b");
@@ -248,7 +260,9 @@ impl BloomFilter {
/// ```
/// use datasketches::bloom::BloomFilterBuilder;
///
- /// let mut filter = BloomFilterBuilder::with_accuracy(100, 0.01).build();
+ /// let mut filter = BloomFilterBuilder::with_accuracy(100, 0.01)
+ /// .build()
+ /// .unwrap();
/// filter.insert("apple");
///
/// filter.invert();
@@ -339,7 +353,9 @@ impl BloomFilter {
/// use datasketches::bloom::BloomFilter;
/// use datasketches::bloom::BloomFilterBuilder;
///
- /// let mut filter = BloomFilterBuilder::with_accuracy(100, 0.01).build();
+ /// let mut filter = BloomFilterBuilder::with_accuracy(100, 0.01)
+ /// .build()
+ /// .unwrap();
/// filter.insert("test");
///
/// let bytes = filter.serialize();
@@ -404,7 +420,9 @@ impl BloomFilter {
/// use datasketches::bloom::BloomFilter;
/// use datasketches::bloom::BloomFilterBuilder;
///
- /// let original = BloomFilterBuilder::with_accuracy(100, 0.01).build();
+ /// let original = BloomFilterBuilder::with_accuracy(100, 0.01)
+ /// .build()
+ /// .unwrap();
/// let bytes = original.serialize();
///
/// let restored = BloomFilter::deserialize(&bytes).unwrap();
@@ -592,13 +610,20 @@ impl BloomFilter {
/// * [`with_accuracy()`](Self::with_accuracy): Specify target items and false
positive rate
/// (recommended)
/// * [`with_size()`](Self::with_size): Specify requested bit count and hash
functions (manual)
+///
+/// Configuration is stored without validation and checked when
[`build()`](Self::build) is called.
#[derive(Debug, Clone)]
pub struct BloomFilterBuilder {
- num_bits: u64,
- num_hashes: u16,
+ mode: BloomFilterBuilderMode,
seed: u64,
}
+#[derive(Debug, Clone)]
+enum BloomFilterBuilderMode {
+ Accuracy { max_items: u64, fpp: f64 },
+ Size { num_bits: u64, num_hashes: u16 },
+}
+
impl BloomFilterBuilder {
/// Minimum allowed requested Bloom filter size, in bits.
pub const MIN_NUM_BITS: u64 = 1;
@@ -616,16 +641,13 @@ impl BloomFilterBuilder {
///
/// Automatically calculates the optimal number of bits and hash functions
/// to achieve the desired false positive probability for a given number
of items.
+ /// The parameters are validated when [`build()`](Self::build) is called.
///
/// # Arguments
///
/// * `max_items`: Maximum expected number of distinct items.
/// * `fpp`: Target false positive probability (for example, `0.01` for
`1%`).
///
- /// # Panics
- ///
- /// Panics if `max_items` is `0` or `fpp` is outside `(0.0, 1.0]`.
- ///
/// # Examples
///
/// ```
@@ -634,21 +656,12 @@ impl BloomFilterBuilder {
/// // Optimal for 10,000 items with 1% FPP
/// let filter = BloomFilterBuilder::with_accuracy(10_000, 0.01)
/// .seed(42)
- /// .build();
+ /// .build()
+ /// .unwrap();
/// ```
pub fn with_accuracy(max_items: u64, fpp: f64) -> Self {
- assert!(max_items > 0, "max_items must be greater than 0");
- assert!(
- fpp > 0.0 && fpp <= 1.0,
- "fpp must be between 0.0 and 1.0 (inclusive of 1.0)"
- );
-
- let num_bits = Self::suggest_num_bits(max_items, fpp);
- let num_hashes = Self::suggest_num_hashes_from_accuracy(max_items,
num_bits);
-
BloomFilterBuilder {
- num_bits,
- num_hashes,
+ mode: BloomFilterBuilderMode::Accuracy { max_items, fpp },
seed: DEFAULT_UPDATE_SEED,
}
}
@@ -657,6 +670,7 @@ impl BloomFilterBuilder {
///
/// Use this when you want precise control over the requested filter size,
/// or when working with pre-calculated parameters.
+ /// The parameters are validated when [`build()`](Self::build) is called.
///
/// The underlying storage is word-based, so the actual capacity is rounded
/// up to the next multiple of 64 bits.
@@ -666,38 +680,19 @@ impl BloomFilterBuilder {
/// * `num_bits`: Total number of bits in the filter.
/// * `num_hashes`: Number of hash functions to use.
///
- /// # Panics
- ///
- /// Panics if any of:
- /// * `num_bits < Self::MIN_NUM_BITS` or `num_bits > Self::MAX_NUM_BITS`.
- /// * `num_hashes < Self::MIN_NUM_HASHES` or `num_hashes >
Self::MAX_NUM_HASHES`.
- ///
/// # Examples
///
/// ```
/// use datasketches::bloom::BloomFilterBuilder;
///
- /// let filter = BloomFilterBuilder::with_size(10_000, 7).build();
+ /// let filter = BloomFilterBuilder::with_size(10_000, 7).build().unwrap();
/// ```
pub fn with_size(num_bits: u64, num_hashes: u16) -> Self {
- assert!(
- (Self::MIN_NUM_BITS..=Self::MAX_NUM_BITS).contains(&num_bits),
- "num_bits must be between {} and {}, got {}",
- Self::MIN_NUM_BITS,
- Self::MAX_NUM_BITS,
- num_bits,
- );
- assert!(
-
(Self::MIN_NUM_HASHES..=Self::MAX_NUM_HASHES).contains(&num_hashes),
- "num_hashes must be between {} and {}, got {}",
- Self::MIN_NUM_HASHES,
- Self::MAX_NUM_HASHES,
- num_hashes
- );
-
BloomFilterBuilder {
- num_bits,
- num_hashes,
+ mode: BloomFilterBuilderMode::Size {
+ num_bits,
+ num_hashes,
+ },
seed: DEFAULT_UPDATE_SEED,
}
}
@@ -713,7 +708,8 @@ impl BloomFilterBuilder {
///
/// let filter = BloomFilterBuilder::with_accuracy(100, 0.01)
/// .seed(12345)
- /// .build();
+ /// .build()
+ /// .unwrap();
/// ```
pub fn seed(mut self, seed: u64) -> Self {
self.seed = seed;
@@ -721,17 +717,58 @@ impl BloomFilterBuilder {
}
/// Builds the Bloom filter.
- pub fn build(self) -> BloomFilter {
- let num_hashes = self.num_hashes;
- let num_words = self.num_bits.div_ceil(64) as usize;
+ ///
+ /// # Errors
+ ///
+ /// Returns an error if the configured accuracy or size parameters are
outside their supported
+ /// ranges.
+ pub fn build(self) -> Result<BloomFilter, Error> {
+ let (num_bits, num_hashes) = match self.mode {
+ BloomFilterBuilderMode::Accuracy { max_items, fpp } => {
+ if max_items == 0 {
+ return Err(Error::invalid_argument("max_items must be
greater than 0"));
+ }
+ if !(fpp > 0.0 && fpp <= 1.0) {
+ return Err(Error::invalid_argument(
+ "fpp must be between 0.0 and 1.0 (inclusive of 1.0)",
+ ));
+ }
+ let num_bits = Self::suggest_num_bits(max_items, fpp);
+ let num_hashes =
Self::suggest_num_hashes_from_accuracy(max_items, num_bits);
+ (num_bits, num_hashes)
+ }
+ BloomFilterBuilderMode::Size {
+ num_bits,
+ num_hashes,
+ } => {
+ if
!(Self::MIN_NUM_BITS..=Self::MAX_NUM_BITS).contains(&num_bits) {
+ return Err(Error::invalid_argument(format!(
+ "num_bits must be between {} and {}, got {}",
+ Self::MIN_NUM_BITS,
+ Self::MAX_NUM_BITS,
+ num_bits
+ )));
+ }
+ if
!(Self::MIN_NUM_HASHES..=Self::MAX_NUM_HASHES).contains(&num_hashes) {
+ return Err(Error::invalid_argument(format!(
+ "num_hashes must be between {} and {}, got {}",
+ Self::MIN_NUM_HASHES,
+ Self::MAX_NUM_HASHES,
+ num_hashes
+ )));
+ }
+ (num_bits, num_hashes)
+ }
+ };
+ let num_words = num_bits.div_ceil(64) as usize;
let bit_array = vec![0u64; num_words].into_boxed_slice();
- BloomFilter {
+ Ok(BloomFilter {
seed: self.seed,
num_hashes,
num_bits_set: 0,
bit_array,
- }
+ })
}
/// Suggests optimal number of bits given max items and target FPP.
diff --git a/datasketches/src/req/sketch.rs b/datasketches/src/req/sketch.rs
index 61d8a91..83145f0 100644
--- a/datasketches/src/req/sketch.rs
+++ b/datasketches/src/req/sketch.rs
@@ -74,6 +74,11 @@ impl<T: ReqValue> ReqSketch<T> {
///
/// Panics if `k` is odd or outside `[4, 1024]`.
pub fn new(k: u16, rank_accuracy: RankAccuracy) -> Self {
+ assert!(
+ (MIN_K..=MAX_K).contains(&k),
+ "k must be in [{MIN_K}, {MAX_K}], got {k}"
+ );
+ assert_eq!(k % 2, 0, "k must be even, got {k}");
Self::make(k, rank_accuracy)
}
@@ -734,12 +739,11 @@ impl<T: ReqValue> ReqSketch<T> {
}
fn make(k: u16, rank_accuracy: RankAccuracy) -> Self {
- assert!(
+ debug_assert!(
(MIN_K..=MAX_K).contains(&k),
"k must be in [{MIN_K}, {MAX_K}], got {k}"
);
- assert_eq!(k % 2, 0, "k must be even, got {k}");
-
+ debug_assert_eq!(k % 2, 0, "k must be even, got {k}");
let mut sketch = Self {
k,
rank_accuracy,
diff --git a/datasketches/src/tdigest/sketch.rs
b/datasketches/src/tdigest/sketch.rs
index cfc4105..e96371f 100644
--- a/datasketches/src/tdigest/sketch.rs
+++ b/datasketches/src/tdigest/sketch.rs
@@ -204,6 +204,7 @@ impl TDigestMut {
/// assert_eq!(sketch.k(), 100);
/// ```
pub fn new(k: u16) -> Self {
+ assert!(k >= 10, "k must be at least 10, got {k}");
Self::make(
k,
false,
@@ -255,7 +256,7 @@ impl TDigestMut {
buffer: TDigestBuffer,
compressed_weight: u64,
) -> Self {
- assert!(k >= 10, "k must be at least 10");
+ debug_assert!(k >= 10, "k must be at least 10");
debug_assert!(buffer.unmerged_tail_len <= buffer.centroids.len());
debug_assert!(buffer.compressed_prefix_len() != 0 || compressed_weight
== 0);
diff --git a/datasketches/src/thetafamily/common/hash_table.rs
b/datasketches/src/thetafamily/common/hash_table.rs
index 3661bb7..36b3d36 100644
--- a/datasketches/src/thetafamily/common/hash_table.rs
+++ b/datasketches/src/thetafamily/common/hash_table.rs
@@ -19,11 +19,13 @@ use std::hash::Hash;
use std::slice;
use crate::common::ResizeFactor;
+use crate::error::Error;
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;
@@ -91,12 +93,64 @@ impl<E> SketchHashTable<E>
where
E: SketchEntry,
{
- /// Create a new hash table.
+ /// Creates a new hash table.
+ ///
+ /// # Panics
+ ///
+ /// Panics if `lg_nom_size` is outside `[5, 26]` or `sampling_probability`
is outside
+ /// `(0.0, 1.0]`.
pub fn new(
lg_nom_size: u8,
resize_factor: ResizeFactor,
sampling_probability: f32,
seed: u64,
+ ) -> Self {
+ assert!(
+ (MIN_LG_K..=MAX_LG_K).contains(&lg_nom_size),
+ "lg_k must be in [{MIN_LG_K}, {MAX_LG_K}], got {lg_nom_size}"
+ );
+ assert!(
+ sampling_probability > 0.0 && sampling_probability <= 1.0,
+ "sampling_probability must be in (0.0, 1.0], got
{sampling_probability}"
+ );
+ Self::make(lg_nom_size, resize_factor, sampling_probability, seed)
+ }
+
+ /// Creates a new hash table after validating its configuration.
+ ///
+ /// # Errors
+ ///
+ /// Returns an error if `lg_nom_size` is outside `[5, 26]` or
`sampling_probability` is outside
+ /// `(0.0, 1.0]`.
+ pub fn try_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}"
+ )));
+ }
+ Ok(Self::make(
+ lg_nom_size,
+ resize_factor,
+ sampling_probability,
+ seed,
+ ))
+ }
+
+ fn make(
+ lg_nom_size: u8,
+ resize_factor: ResizeFactor,
+ sampling_probability: f32,
+ 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());
diff --git a/datasketches/src/thetafamily/common/union.rs
b/datasketches/src/thetafamily/common/union.rs
index 5e5bbca..25b4541 100644
--- a/datasketches/src/thetafamily/common/union.rs
+++ b/datasketches/src/thetafamily/common/union.rs
@@ -61,6 +61,22 @@ where
}
}
+ /// Creates union state after validating the shared hash-table
configuration.
+ pub fn try_new(
+ lg_k: u8,
+ resize_factor: ResizeFactor,
+ sampling_probability: f32,
+ seed: u64,
+ policy: P,
+ ) -> Result<Self, Error> {
+ let table = SketchHashTable::try_new(lg_k, resize_factor,
sampling_probability, seed)?;
+ Ok(Self {
+ union_theta: table.theta(),
+ table,
+ policy,
+ })
+ }
+
/// Incorporate a sketch into the union.
pub fn update<S>(&mut self, sketch: S) -> Result<(), Error>
where
diff --git a/datasketches/src/thetafamily/theta/a_not_b.rs
b/datasketches/src/thetafamily/theta/a_not_b.rs
index d8edbd1..51b9791 100644
--- a/datasketches/src/thetafamily/theta/a_not_b.rs
+++ b/datasketches/src/thetafamily/theta/a_not_b.rs
@@ -39,11 +39,11 @@ use crate::thetacommon::a_not_b;
/// use datasketches::theta::ThetaANotB;
/// use datasketches::theta::ThetaSketchBuilder;
///
-/// let mut a = ThetaSketchBuilder::default().build();
+/// let mut a = ThetaSketchBuilder::default().build().unwrap();
/// a.update("apple");
/// a.update("banana");
///
-/// let mut b = ThetaSketchBuilder::default().build();
+/// let mut b = ThetaSketchBuilder::default().build().unwrap();
/// b.update("banana");
///
/// let a_not_b = ThetaANotB::default();
diff --git a/datasketches/src/thetafamily/theta/jaccard_similarity.rs
b/datasketches/src/thetafamily/theta/jaccard_similarity.rs
index b556f86..dd932e2 100644
--- a/datasketches/src/thetafamily/theta/jaccard_similarity.rs
+++ b/datasketches/src/thetafamily/theta/jaccard_similarity.rs
@@ -34,8 +34,8 @@ use crate::thetacommon::jaccard_similarity::JaccardSimilarity;
/// use datasketches::theta::ThetaJaccardSimilarity;
/// use datasketches::theta::ThetaSketchBuilder;
///
-/// let mut a = ThetaSketchBuilder::default().build();
-/// let mut b = ThetaSketchBuilder::default().build();
+/// let mut a = ThetaSketchBuilder::default().build().unwrap();
+/// let mut b = ThetaSketchBuilder::default().build().unwrap();
/// a.update("apple");
/// b.update("apple");
///
diff --git a/datasketches/src/thetafamily/theta/mod.rs
b/datasketches/src/thetafamily/theta/mod.rs
index 6a70a6b..c65fa09 100644
--- a/datasketches/src/thetafamily/theta/mod.rs
+++ b/datasketches/src/thetafamily/theta/mod.rs
@@ -35,7 +35,7 @@
//! ```
//! use datasketches::theta::ThetaSketchBuilder;
//!
-//! let mut sketch = ThetaSketchBuilder::default().build();
+//! let mut sketch = ThetaSketchBuilder::default().build().unwrap();
//! sketch.update("apple");
//! assert!(sketch.estimate() >= 1.0);
//! ```
diff --git a/datasketches/src/thetafamily/theta/sketch.rs
b/datasketches/src/thetafamily/theta/sketch.rs
index 4293e7c..f1c9f27 100644
--- a/datasketches/src/thetafamily/theta/sketch.rs
+++ b/datasketches/src/thetafamily/theta/sketch.rs
@@ -55,9 +55,7 @@ use crate::thetacommon::constants::FLAGS_IS_COMPACT;
use crate::thetacommon::constants::FLAGS_IS_EMPTY;
use crate::thetacommon::constants::FLAGS_IS_ORDERED;
use crate::thetacommon::constants::FLAGS_IS_READ_ONLY;
-use crate::thetacommon::constants::MAX_LG_K;
use crate::thetacommon::constants::MAX_THETA;
-use crate::thetacommon::constants::MIN_LG_K;
use crate::thetacommon::hash_table::SketchHashTableIter;
/// Read-only view for Theta sketches.
@@ -71,7 +69,7 @@ use crate::thetacommon::hash_table::SketchHashTableIter;
/// ```
/// use datasketches::theta::ThetaSketchBuilder;
///
-/// let mut sketch = ThetaSketchBuilder::default().build();
+/// let mut sketch = ThetaSketchBuilder::default().build().unwrap();
/// sketch.update("apple");
/// let view = sketch.as_view();
/// assert_eq!(view.num_retained(), 1);
@@ -221,11 +219,11 @@ impl ThetaSketch {
/// use datasketches::hash::value::raw_bytes;
/// use datasketches::theta::ThetaSketchBuilder;
///
- /// let mut sketch = ThetaSketchBuilder::default().build();
+ /// let mut sketch = ThetaSketchBuilder::default().build().unwrap();
/// sketch.update("apple");
/// assert!(sketch.estimate() >= 1.0);
///
- /// let mut sketch = ThetaSketchBuilder::default().build();
+ /// let mut sketch = ThetaSketchBuilder::default().build().unwrap();
/// sketch.update(raw_bytes::from_str("apple"));
/// assert!(sketch.estimate() >= 1.0);
/// ```
@@ -240,7 +238,7 @@ impl ThetaSketch {
/// ```
/// use datasketches::theta::ThetaSketchBuilder;
///
- /// let mut sketch = ThetaSketchBuilder::default().build();
+ /// let mut sketch = ThetaSketchBuilder::default().build().unwrap();
/// sketch.update("apple");
/// assert!(sketch.estimate() >= 1.0);
/// ```
@@ -305,7 +303,7 @@ impl ThetaSketch {
/// ```
/// use datasketches::theta::ThetaSketchBuilder;
///
- /// let mut sketch = ThetaSketchBuilder::default().build();
+ /// let mut sketch = ThetaSketchBuilder::default().build().unwrap();
/// sketch.update("apple");
/// let mut iter = sketch.iter();
/// assert!(iter.next().is_some());
@@ -323,7 +321,7 @@ impl ThetaSketch {
/// ```
/// use datasketches::theta::ThetaSketchBuilder;
///
- /// let mut sketch = ThetaSketchBuilder::default().build();
+ /// let mut sketch = ThetaSketchBuilder::default().build().unwrap();
/// sketch.update("apple");
/// let compact = sketch.compact(true);
/// assert_eq!(compact.num_retained(), 1);
@@ -355,7 +353,7 @@ impl ThetaSketch {
/// use datasketches::common::NumStdDev;
/// use datasketches::theta::ThetaSketchBuilder;
///
- /// let mut sketch = ThetaSketchBuilder::default().lg_k(12).build();
+ /// let mut sketch =
ThetaSketchBuilder::default().lg_k(12).build().unwrap();
/// for i in 0..10000 {
/// sketch.update(i);
/// }
@@ -389,7 +387,7 @@ impl ThetaSketch {
/// use datasketches::common::NumStdDev;
/// use datasketches::theta::ThetaSketchBuilder;
///
- /// let mut sketch = ThetaSketchBuilder::default().lg_k(12).build();
+ /// let mut sketch =
ThetaSketchBuilder::default().lg_k(12).build().unwrap();
/// for i in 0..10000 {
/// sketch.update(i);
/// }
@@ -1018,6 +1016,8 @@ impl CompactThetaSketch {
}
/// Builder for [`ThetaSketch`].
+///
+/// Configuration is stored without validation and checked when
[`build()`](Self::build) is called.
#[derive(Debug)]
pub struct ThetaSketchBuilder {
lg_k: u8,
@@ -1040,26 +1040,15 @@ impl Default for ThetaSketchBuilder {
impl ThetaSketchBuilder {
/// Sets `lg_k`, the base-2 logarithm of the nominal capacity.
///
- /// # Panics
- ///
- /// Panics if `lg_k` is outside `[5, 26]`.
- ///
/// # Examples
///
/// ```
/// use datasketches::theta::ThetaSketchBuilder;
///
- /// let sketch = ThetaSketchBuilder::default().lg_k(12).build();
+ /// let sketch = ThetaSketchBuilder::default().lg_k(12).build().unwrap();
/// assert_eq!(sketch.lg_k(), 12);
/// ```
pub fn lg_k(mut self, lg_k: u8) -> Self {
- assert!(
- (MIN_LG_K..=MAX_LG_K).contains(&lg_k),
- "lg_k must be in [{}, {}], got {}",
- MIN_LG_K,
- MAX_LG_K,
- lg_k
- );
self.lg_k = lg_k;
self
}
@@ -1075,10 +1064,6 @@ impl ThetaSketchBuilder {
/// The sampling probability controls the fraction of hashed values that
are retained.
/// It must be greater than `0.0` to ensure valid theta values for bound
calculations.
///
- /// # Panics
- ///
- /// Panics if `probability` is outside `(0.0, 1.0]`.
- ///
/// # Examples
///
/// ```
@@ -1086,13 +1071,10 @@ impl ThetaSketchBuilder {
///
/// ThetaSketchBuilder::default()
/// .sampling_probability(0.5)
- /// .build();
+ /// .build()
+ /// .unwrap();
/// ```
pub fn sampling_probability(mut self, probability: f32) -> Self {
- assert!(
- (0.0..=1.0).contains(&probability) && probability > 0.0,
- "sampling_probability must be in (0.0, 1.0], got {probability}"
- );
self.sampling_probability = probability;
self
}
@@ -1104,7 +1086,7 @@ impl ThetaSketchBuilder {
/// ```
/// use datasketches::theta::ThetaSketchBuilder;
///
- /// ThetaSketchBuilder::default().seed(7).build();
+ /// ThetaSketchBuilder::default().seed(7).build().unwrap();
/// ```
pub fn seed(mut self, seed: u64) -> Self {
self.seed = seed;
@@ -1113,22 +1095,27 @@ impl ThetaSketchBuilder {
/// Builds the [`ThetaSketch`].
///
+ /// # Errors
+ ///
+ /// Returns an error if `lg_k` is outside `[5, 26]` or
`sampling_probability` is outside
+ /// `(0.0, 1.0]`.
+ ///
/// # Examples
///
/// ```
/// use datasketches::theta::ThetaSketchBuilder;
///
- /// ThetaSketchBuilder::default().lg_k(10).build();
+ /// ThetaSketchBuilder::default().lg_k(10).build().unwrap();
/// ```
- pub fn build(self) -> ThetaSketch {
- let table = ThetaHashTable::new(
+ pub fn build(self) -> Result<ThetaSketch, Error> {
+ let table = ThetaHashTable::try_new(
self.lg_k,
self.resize_factor,
self.sampling_probability,
self.seed,
- );
+ )?;
- ThetaSketch { table }
+ Ok(ThetaSketch { table })
}
}
@@ -1190,7 +1177,7 @@ mod tests {
#[test]
fn theta_and_compact_theta_equivalent() {
- let mut exact_theta = ThetaSketchBuilder::default().lg_k(12).build();
+ let mut exact_theta =
ThetaSketchBuilder::default().lg_k(12).build().unwrap();
for i in 0..2000 {
exact_theta.update(i);
}
@@ -1199,7 +1186,7 @@ mod tests {
assert_theta_and_compact_equivalent_ordered(&exact_theta, ordered);
}
- let mut estimation_theta =
ThetaSketchBuilder::default().lg_k(5).build();
+ let mut estimation_theta =
ThetaSketchBuilder::default().lg_k(5).build().unwrap();
for i in 0..5000 {
estimation_theta.update(i);
}
@@ -1211,7 +1198,7 @@ mod tests {
#[test]
fn
compact_theta_serialize_deserialize_round_trip_equivalent_to_compact_and_theta()
{
- let mut theta = ThetaSketchBuilder::default().lg_k(5).build();
+ let mut theta = ThetaSketchBuilder::default().lg_k(5).build().unwrap();
for i in 0..5000 {
theta.update(i);
}
@@ -1227,7 +1214,7 @@ mod tests {
#[test]
fn compact_theta_serialize_compressed_round_trip_tail_entries() {
- let mut theta = ThetaSketchBuilder::default().lg_k(12).build();
+ let mut theta =
ThetaSketchBuilder::default().lg_k(12).build().unwrap();
for i in 0..13 {
theta.update(i);
}
@@ -1242,7 +1229,7 @@ mod tests {
#[test]
fn compact_theta_serialize_compressed_round_trip_more_than_255_entries() {
- let mut theta = ThetaSketchBuilder::default().lg_k(12).build();
+ let mut theta =
ThetaSketchBuilder::default().lg_k(12).build().unwrap();
for i in 0..300 {
theta.update(i);
}
@@ -1257,7 +1244,7 @@ mod tests {
#[test]
fn compact_theta_serialize_compressed_round_trip_estimation_mode() {
- let mut theta = ThetaSketchBuilder::default().lg_k(5).build();
+ let mut theta = ThetaSketchBuilder::default().lg_k(5).build().unwrap();
for i in 0..5000 {
theta.update(i);
}
diff --git a/datasketches/src/thetafamily/theta/union.rs
b/datasketches/src/thetafamily/theta/union.rs
index d10b1d2..956e6c6 100644
--- a/datasketches/src/thetafamily/theta/union.rs
+++ b/datasketches/src/thetafamily/theta/union.rs
@@ -22,8 +22,6 @@ use crate::theta::CompactThetaSketch;
use crate::theta::ThetaSketchView;
use crate::theta::hash_table::ThetaEntry;
use crate::thetacommon::constants::DEFAULT_LG_K;
-use crate::thetacommon::constants::MAX_LG_K;
-use crate::thetacommon::constants::MIN_LG_K;
use crate::thetacommon::union::UnionMergePolicy;
use crate::thetacommon::union::UnionState;
@@ -75,6 +73,8 @@ impl ThetaUnion {
}
/// Builder for [`ThetaUnion`].
+///
+/// Configuration is stored without validation and checked when
[`build()`](Self::build) is called.
#[derive(Debug, Clone)]
pub struct ThetaUnionBuilder {
lg_k: u8,
@@ -97,22 +97,14 @@ impl Default for ThetaUnionBuilder {
impl ThetaUnionBuilder {
/// Sets `lg_k`, the base-2 logarithm of the nominal capacity.
///
- /// # Panics
- ///
- /// Panics if `lg_k` is outside `[5, 26]`.
- ///
/// # Examples
///
/// ```
/// use datasketches::theta::ThetaUnionBuilder;
///
- /// ThetaUnionBuilder::default().lg_k(12).build();
+ /// ThetaUnionBuilder::default().lg_k(12).build().unwrap();
/// ```
pub fn lg_k(mut self, lg_k: u8) -> Self {
- assert!(
- (MIN_LG_K..=MAX_LG_K).contains(&lg_k),
- "lg_k must be in [{MIN_LG_K}, {MAX_LG_K}], got {lg_k}"
- );
self.lg_k = lg_k;
self
}
@@ -125,10 +117,6 @@ impl ThetaUnionBuilder {
/// Sets the sampling probability.
///
- /// # Panics
- ///
- /// Panics if `probability` is outside `(0.0, 1.0]`.
- ///
/// # Examples
///
/// ```
@@ -136,13 +124,10 @@ impl ThetaUnionBuilder {
///
/// ThetaUnionBuilder::default()
/// .sampling_probability(0.5)
- /// .build();
+ /// .build()
+ /// .unwrap();
/// ```
pub fn sampling_probability(mut self, probability: f32) -> Self {
- assert!(
- (0.0..=1.0).contains(&probability) && probability > 0.0,
- "sampling_probability must be in (0.0, 1.0], got {probability}"
- );
self.sampling_probability = probability;
self
}
@@ -154,7 +139,7 @@ impl ThetaUnionBuilder {
/// ```
/// use datasketches::theta::ThetaUnionBuilder;
///
- /// ThetaUnionBuilder::default().seed(7).build();
+ /// ThetaUnionBuilder::default().seed(7).build().unwrap();
/// ```
pub fn seed(mut self, seed: u64) -> Self {
self.seed = seed;
@@ -163,22 +148,27 @@ impl ThetaUnionBuilder {
/// Builds the [`ThetaUnion`].
///
+ /// # Errors
+ ///
+ /// Returns an error if `lg_k` is outside `[5, 26]` or
`sampling_probability` is outside
+ /// `(0.0, 1.0]`.
+ ///
/// # Examples
///
/// ```
/// use datasketches::theta::ThetaUnionBuilder;
///
- /// ThetaUnionBuilder::default().lg_k(10).build();
+ /// ThetaUnionBuilder::default().lg_k(10).build().unwrap();
/// ```
- pub fn build(self) -> ThetaUnion {
- ThetaUnion {
- state: UnionState::new(
+ pub fn build(self) -> Result<ThetaUnion, Error> {
+ Ok(ThetaUnion {
+ state: UnionState::try_new(
self.lg_k,
self.resize_factor,
self.sampling_probability,
self.seed,
NoopUnionPolicy,
- ),
- }
+ )?,
+ })
}
}
diff --git a/datasketches/src/thetafamily/tuple/a_not_b.rs
b/datasketches/src/thetafamily/tuple/a_not_b.rs
index dbe6c3b..914c220 100644
--- a/datasketches/src/thetafamily/tuple/a_not_b.rs
+++ b/datasketches/src/thetafamily/tuple/a_not_b.rs
@@ -43,11 +43,11 @@ use crate::tuple::sketch::TupleSketchView;
/// use datasketches::tuple::TupleSketchBuilder;
///
/// let update_policy = DefaultUpdatePolicy::<u64>::default();
-/// let mut a = TupleSketchBuilder::new(update_policy).build();
+/// let mut a = TupleSketchBuilder::new(update_policy).build().unwrap();
/// a.update("apple", 1);
/// a.update("banana", 1);
///
-/// let mut b = TupleSketchBuilder::new(update_policy).build();
+/// let mut b = TupleSketchBuilder::new(update_policy).build().unwrap();
/// b.update("banana", 1);
///
/// let a_not_b = TupleANotB::default();
diff --git a/datasketches/src/thetafamily/tuple/intersection.rs
b/datasketches/src/thetafamily/tuple/intersection.rs
index c337f33..0d0833e 100644
--- a/datasketches/src/thetafamily/tuple/intersection.rs
+++ b/datasketches/src/thetafamily/tuple/intersection.rs
@@ -68,11 +68,11 @@ use crate::tuple::sketch::TupleSketchView;
/// }
///
/// let update_policy = DefaultUpdatePolicy::<u64>::default();
-/// let mut a = TupleSketchBuilder::new(update_policy).build();
+/// let mut a = TupleSketchBuilder::new(update_policy).build().unwrap();
/// a.update("shared", 3);
/// a.update("only_a", 1);
///
-/// let mut b = TupleSketchBuilder::new(update_policy).build();
+/// let mut b = TupleSketchBuilder::new(update_policy).build().unwrap();
/// b.update("shared", 4);
/// b.update("only_b", 1);
///
diff --git a/datasketches/src/thetafamily/tuple/jaccard_similarity.rs
b/datasketches/src/thetafamily/tuple/jaccard_similarity.rs
index e65becb..d2fcc66 100644
--- a/datasketches/src/thetafamily/tuple/jaccard_similarity.rs
+++ b/datasketches/src/thetafamily/tuple/jaccard_similarity.rs
@@ -37,8 +37,8 @@ use crate::tuple::TupleSketchView;
/// use datasketches::tuple::TupleSketchBuilder;
///
/// let policy = DefaultUpdatePolicy::<u64>::default();
-/// let mut a = TupleSketchBuilder::new(policy).build();
-/// let mut b = TupleSketchBuilder::new(policy).build();
+/// let mut a = TupleSketchBuilder::new(policy).build().unwrap();
+/// let mut b = TupleSketchBuilder::new(policy).build().unwrap();
/// a.update("apple", 1);
/// b.update("apple", 2);
///
diff --git a/datasketches/src/thetafamily/tuple/mod.rs
b/datasketches/src/thetafamily/tuple/mod.rs
index c159d35..c057ee5 100644
--- a/datasketches/src/thetafamily/tuple/mod.rs
+++ b/datasketches/src/thetafamily/tuple/mod.rs
@@ -35,7 +35,7 @@
//! use datasketches::tuple::TupleSketchBuilder;
//!
//! let policy = DefaultUpdatePolicy::<u64>::default();
-//! let mut sketch = TupleSketchBuilder::new(policy).build();
+//! let mut sketch = TupleSketchBuilder::new(policy).build().unwrap();
//! sketch.update("apple", 1_u64);
//! assert!(sketch.estimate() >= 1.0);
//! ```
diff --git a/datasketches/src/thetafamily/tuple/sketch.rs
b/datasketches/src/thetafamily/tuple/sketch.rs
index fd1c8fb..ada2244 100644
--- a/datasketches/src/thetafamily/tuple/sketch.rs
+++ b/datasketches/src/thetafamily/tuple/sketch.rs
@@ -46,9 +46,7 @@ use crate::thetacommon::constants::FLAGS_IS_COMPACT;
use crate::thetacommon::constants::FLAGS_IS_EMPTY;
use crate::thetacommon::constants::FLAGS_IS_ORDERED;
use crate::thetacommon::constants::FLAGS_IS_READ_ONLY;
-use crate::thetacommon::constants::MAX_LG_K;
use crate::thetacommon::constants::MAX_THETA;
-use crate::thetacommon::constants::MIN_LG_K;
use crate::thetacommon::hash_table::SketchHashTableIter;
use crate::tuple::hash_table::TupleEntry;
use crate::tuple::hash_table::TupleHashTable;
@@ -71,7 +69,9 @@ use crate::tuple::serialization::TupleSummaryValue;
/// use datasketches::tuple::DefaultUpdatePolicy;
/// use datasketches::tuple::TupleSketchBuilder;
///
-/// let mut sketch =
TupleSketchBuilder::new(DefaultUpdatePolicy::<u64>::default()).build();
+/// let mut sketch =
TupleSketchBuilder::new(DefaultUpdatePolicy::<u64>::default())
+/// .build()
+/// .unwrap();
/// sketch.update("apple", 1);
/// let view = sketch.as_view();
/// assert_eq!(view.iter().next().unwrap().1, &1);
@@ -232,7 +232,7 @@ impl<'a, S> From<&'a CompactTupleSketch<S>> for
TupleSketchView<'a, S> {
/// use datasketches::tuple::TupleSketchBuilder;
///
/// let policy = DefaultUpdatePolicy::<u64>::default();
-/// let mut sketch = TupleSketchBuilder::new(policy).build();
+/// let mut sketch = TupleSketchBuilder::new(policy).build().unwrap();
/// sketch.update("apple", 1);
/// sketch.update("apple", 1);
/// assert!(sketch.estimate() >= 1.0);
@@ -269,7 +269,7 @@ where
/// use datasketches::tuple::TupleSketchBuilder;
///
/// let policy = DefaultUpdatePolicy::<u64>::default();
- /// let mut sketch = TupleSketchBuilder::new(policy).build();
+ /// let mut sketch = TupleSketchBuilder::new(policy).build().unwrap();
/// sketch.update(42, 5);
/// ```
pub fn update<U>(&mut self, key: impl Hash, value: U)
@@ -395,7 +395,7 @@ where
/// use datasketches::tuple::TupleSketchBuilder;
///
/// let policy = DefaultUpdatePolicy::<u64>::default();
- /// let mut sketch = TupleSketchBuilder::new(policy).build();
+ /// let mut sketch = TupleSketchBuilder::new(policy).build().unwrap();
/// sketch.update("apple", 1);
/// let compact = sketch.compact(true);
/// assert_eq!(compact.num_retained(), 1);
@@ -554,7 +554,7 @@ impl<S> CompactTupleSketch<S> {
/// use datasketches::tuple::TupleSketchBuilder;
///
/// let policy = DefaultUpdatePolicy::<u64>::default();
- /// let mut sketch = TupleSketchBuilder::new(policy).build();
+ /// let mut sketch = TupleSketchBuilder::new(policy).build().unwrap();
/// sketch.update("apple", 1);
/// let bytes = sketch.compact(true).serialize();
/// assert!(!bytes.is_empty());
@@ -719,6 +719,8 @@ impl<S> CompactTupleSketch<S> {
/// Every builder carries a concrete [`SummaryPolicy`]. Use
/// [`DefaultUpdatePolicy`](crate::tuple::DefaultUpdatePolicy) for
default-constructed additive
/// summaries, or supply a custom policy.
+///
+/// Configuration is stored without validation and checked when
[`build()`](Self::build) is called.
#[derive(Debug)]
pub struct TupleSketchBuilder<P>
where
@@ -760,7 +762,7 @@ where
/// }
/// }
///
- /// let mut sketch = TupleSketchBuilder::new(MaxPolicy).build();
+ /// let mut sketch = TupleSketchBuilder::new(MaxPolicy).build().unwrap();
/// sketch.update("k", 3);
/// sketch.update("k", 7);
/// ```
@@ -775,15 +777,7 @@ where
}
/// Sets `lg_k`, the base-2 logarithm of the nominal capacity.
- ///
- /// # Panics
- ///
- /// Panics if `lg_k` is outside `[5, 26]`.
pub fn lg_k(mut self, lg_k: u8) -> Self {
- assert!(
- (MIN_LG_K..=MAX_LG_K).contains(&lg_k),
- "lg_k must be in [{MIN_LG_K}, {MAX_LG_K}], got {lg_k}"
- );
self.lg_k = lg_k;
self
}
@@ -795,15 +789,7 @@ where
}
/// Sets the sampling probability.
- ///
- /// # Panics
- ///
- /// Panics if `probability` is outside `(0.0, 1.0]`.
pub fn sampling_probability(mut self, probability: f32) -> Self {
- assert!(
- (0.0..=1.0).contains(&probability) && probability > 0.0,
- "sampling_probability must be in (0.0, 1.0], got {probability}"
- );
self.sampling_probability = probability;
self
}
@@ -815,15 +801,20 @@ where
}
/// Builds a [`TupleSketch`] using the supplied policy.
- pub fn build(self) -> TupleSketch<P> {
- TupleSketch {
- table: TupleHashTable::new(
+ ///
+ /// # Errors
+ ///
+ /// Returns an error if `lg_k` is outside `[5, 26]` or
`sampling_probability` is outside
+ /// `(0.0, 1.0]`.
+ pub fn build(self) -> Result<TupleSketch<P>, Error> {
+ Ok(TupleSketch {
+ table: TupleHashTable::try_new(
self.lg_k,
self.resize_factor,
self.sampling_probability,
self.seed,
- ),
+ )?,
policy: self.policy,
- }
+ })
}
}
diff --git a/datasketches/src/thetafamily/tuple/union.rs
b/datasketches/src/thetafamily/tuple/union.rs
index 25bef18..ddea80d 100644
--- a/datasketches/src/thetafamily/tuple/union.rs
+++ b/datasketches/src/thetafamily/tuple/union.rs
@@ -26,8 +26,6 @@ use crate::common::ResizeFactor;
use crate::error::Error;
use crate::hash::DEFAULT_UPDATE_SEED;
use crate::thetacommon::constants::DEFAULT_LG_K;
-use crate::thetacommon::constants::MAX_LG_K;
-use crate::thetacommon::constants::MIN_LG_K;
use crate::thetacommon::union::UnionState;
use crate::tuple::hash_table::TupleEntry;
use crate::tuple::policy::SummaryCombinePolicy;
@@ -49,15 +47,17 @@ use crate::tuple::sketch::TupleSketchView;
/// use datasketches::tuple::TupleUnionBuilder;
///
/// let update_policy = DefaultUpdatePolicy::<u64>::default();
-/// let mut a = TupleSketchBuilder::new(update_policy).build();
+/// let mut a = TupleSketchBuilder::new(update_policy).build().unwrap();
/// a.update("apple", 1);
/// a.update("banana", 1);
///
-/// let mut b = TupleSketchBuilder::new(update_policy).build();
+/// let mut b = TupleSketchBuilder::new(update_policy).build().unwrap();
/// b.update("banana", 1);
/// b.update("cherry", 1);
///
-/// let mut union =
TupleUnionBuilder::new(DefaultUnionPolicy::<u64>::default()).build();
+/// let mut union =
TupleUnionBuilder::new(DefaultUnionPolicy::<u64>::default())
+/// .build()
+/// .unwrap();
/// union.update(&a).unwrap();
/// union.update(&b).unwrap();
///
@@ -130,6 +130,8 @@ where
/// Every builder carries a concrete [`SummaryCombinePolicy`]. Use
/// [`DefaultUnionPolicy`](crate::tuple::DefaultUnionPolicy) for additive
summaries, or supply a
/// custom combine policy.
+///
+/// Configuration is stored without validation and checked when
[`build()`](Self::build) is called.
#[derive(Debug)]
pub struct TupleUnionBuilder<P>
where
@@ -156,7 +158,8 @@ where
///
/// let union =
TupleUnionBuilder::new(DefaultUnionPolicy::<u64>::default())
/// .lg_k(12)
- /// .build();
+ /// .build()
+ /// .unwrap();
/// ```
pub fn new(policy: P) -> Self {
Self {
@@ -169,15 +172,7 @@ where
}
/// Sets `lg_k`, the base-2 logarithm of the nominal capacity.
- ///
- /// # Panics
- ///
- /// Panics if `lg_k` is outside `[5, 26]`.
pub fn lg_k(mut self, lg_k: u8) -> Self {
- assert!(
- (MIN_LG_K..=MAX_LG_K).contains(&lg_k),
- "lg_k must be in [{MIN_LG_K}, {MAX_LG_K}], got {lg_k}"
- );
self.lg_k = lg_k;
self
}
@@ -189,15 +184,7 @@ where
}
/// Sets the sampling probability.
- ///
- /// # Panics
- ///
- /// Panics if `probability` is outside `(0.0, 1.0]`.
pub fn sampling_probability(mut self, probability: f32) -> Self {
- assert!(
- (0.0..=1.0).contains(&probability) && probability > 0.0,
- "sampling_probability must be in (0.0, 1.0], got {probability}"
- );
self.sampling_probability = probability;
self
}
@@ -209,15 +196,20 @@ where
}
/// Builds the [`TupleUnion`].
- pub fn build(self) -> TupleUnion<P> {
- TupleUnion {
- state: UnionState::new(
+ ///
+ /// # Errors
+ ///
+ /// Returns an error if `lg_k` is outside `[5, 26]` or
`sampling_probability` is outside
+ /// `(0.0, 1.0]`.
+ pub fn build(self) -> Result<TupleUnion<P>, Error> {
+ Ok(TupleUnion {
+ state: UnionState::try_new(
self.lg_k,
self.resize_factor,
self.sampling_probability,
self.seed,
self.policy,
- ),
- }
+ )?,
+ })
}
}
diff --git a/tests-integration/tests/bloom_test/sketch.rs
b/tests-integration/tests/bloom_test/sketch.rs
index e164140..15f44a9 100644
--- a/tests-integration/tests/bloom_test/sketch.rs
+++ b/tests-integration/tests/bloom_test/sketch.rs
@@ -16,6 +16,7 @@
// under the License.
use datasketches::bloom::BloomFilterBuilder;
+use datasketches::error::ErrorKind;
use googletest::assert_that;
use googletest::prelude::ge;
use googletest::prelude::gt;
@@ -29,6 +30,7 @@ fn filter() -> datasketches::bloom::BloomFilter {
BloomFilterBuilder::with_size(NUM_BITS, NUM_HASHES)
.seed(SEED)
.build()
+ .unwrap()
}
#[test]
@@ -103,13 +105,16 @@ fn test_compatibility_checks_all_configuration() {
let different_seed = BloomFilterBuilder::with_size(NUM_BITS, NUM_HASHES)
.seed(SEED + 1)
- .build();
+ .build()
+ .unwrap();
let different_size = BloomFilterBuilder::with_size(NUM_BITS * 2,
NUM_HASHES)
.seed(SEED)
- .build();
+ .build()
+ .unwrap();
let different_hashes = BloomFilterBuilder::with_size(NUM_BITS, NUM_HASHES
+ 1)
.seed(SEED)
- .build();
+ .build()
+ .unwrap();
assert!(!baseline.is_compatible(&different_seed));
assert!(!baseline.is_compatible(&different_size));
@@ -122,7 +127,8 @@ fn test_union_rejects_incompatible_filters() {
let mut left = filter();
let right = BloomFilterBuilder::with_size(NUM_BITS, NUM_HASHES)
.seed(SEED + 1)
- .build();
+ .build()
+ .unwrap();
left.union(&right);
}
@@ -132,36 +138,41 @@ fn test_intersection_rejects_incompatible_filters() {
let mut left = filter();
let right = BloomFilterBuilder::with_size(NUM_BITS, NUM_HASHES)
.seed(SEED + 1)
- .build();
+ .build()
+ .unwrap();
left.intersect(&right);
}
#[test]
fn test_requested_size_rounds_to_word_boundary() {
- let filter = BloomFilterBuilder::with_size(65, 3).build();
+ let filter = BloomFilterBuilder::with_size(65, 3).build().unwrap();
assert_eq!(filter.capacity(), 128);
}
#[test]
-#[should_panic(expected = "max_items must be greater than 0")]
-fn test_accuracy_builder_rejects_zero_items() {
- BloomFilterBuilder::with_accuracy(0, 0.01);
+fn test_accuracy_builder_rejects_zero_items_at_build() {
+ let error = BloomFilterBuilder::with_accuracy(0, 0.01)
+ .build()
+ .unwrap_err();
+ assert_eq!(error.kind(), ErrorKind::InvalidArgument);
}
#[test]
-#[should_panic(expected = "fpp must be between")]
-fn test_accuracy_builder_rejects_invalid_probability() {
- BloomFilterBuilder::with_accuracy(100, 1.5);
+fn test_accuracy_builder_rejects_invalid_probability_at_build() {
+ let error = BloomFilterBuilder::with_accuracy(100, 1.5)
+ .build()
+ .unwrap_err();
+ assert_eq!(error.kind(), ErrorKind::InvalidArgument);
}
#[test]
-#[should_panic(expected = "num_bits must be between")]
-fn test_size_builder_rejects_zero_bits() {
- BloomFilterBuilder::with_size(0, 3);
+fn test_size_builder_rejects_zero_bits_at_build() {
+ let error = BloomFilterBuilder::with_size(0, 3).build().unwrap_err();
+ assert_eq!(error.kind(), ErrorKind::InvalidArgument);
}
#[test]
-#[should_panic(expected = "num_hashes must be between")]
-fn test_size_builder_rejects_zero_hashes() {
- BloomFilterBuilder::with_size(128, 0);
+fn test_size_builder_rejects_zero_hashes_at_build() {
+ let error = BloomFilterBuilder::with_size(128, 0).build().unwrap_err();
+ assert_eq!(error.kind(), ErrorKind::InvalidArgument);
}
diff --git a/tests-integration/tests/serde_tests/bloom.rs
b/tests-integration/tests/serde_tests/bloom.rs
index 708c7f7..4c3e712 100644
--- a/tests-integration/tests/serde_tests/bloom.rs
+++ b/tests-integration/tests/serde_tests/bloom.rs
@@ -180,7 +180,9 @@ fn test_go_compatibility() {
fn test_cached_num_bits_set_is_validated_or_recomputed() {
const NUM_BITS_SET_OFFSET: usize = 24;
- let mut filter = BloomFilterBuilder::with_accuracy(100, 0.01).build();
+ let mut filter = BloomFilterBuilder::with_accuracy(100, 0.01)
+ .build()
+ .unwrap();
filter.insert("apple");
filter.insert("banana");
let actual_bits_set = filter.bits_used();
@@ -207,7 +209,9 @@ fn test_cached_num_bits_set_is_validated_or_recomputed() {
fn test_nonempty_payload_length_is_checked_before_allocating() {
const NUM_LONGS_OFFSET: usize = 16;
- let mut filter = BloomFilterBuilder::with_accuracy(100, 0.01).build();
+ let mut filter = BloomFilterBuilder::with_accuracy(100, 0.01)
+ .build()
+ .unwrap();
filter.insert("apple");
let mut bytes = filter.serialize();
bytes[NUM_LONGS_OFFSET..NUM_LONGS_OFFSET + size_of::<i32>()]
diff --git a/tests-integration/tests/serde_tests/theta.rs
b/tests-integration/tests/serde_tests/theta.rs
index e1ee343..0c1342f 100644
--- a/tests-integration/tests/serde_tests/theta.rs
+++ b/tests-integration/tests/serde_tests/theta.rs
@@ -29,7 +29,7 @@ use googletest::prelude::near;
use crate::serialization_test_data;
fn serialize_v2_exact(entries: &[u64]) -> Vec<u8> {
- let current = ThetaSketchBuilder::default().build().compact(true);
+ let current = ThetaSketchBuilder::default().build().unwrap().compact(true);
let current_bytes = current.serialize();
let mut bytes = SketchBytes::with_capacity((2 + entries.len()) *
size_of::<u64>());
bytes.write_u8(2); // preamble longs
@@ -160,7 +160,7 @@ fn test_go_compatibility() {
#[test]
fn malformed_input_is_rejected() {
- let mut sketch = ThetaSketchBuilder::default().lg_k(5).build();
+ let mut sketch = ThetaSketchBuilder::default().lg_k(5).build().unwrap();
for value in 0..5000 {
sketch.update(value);
}
@@ -190,7 +190,7 @@ fn declared_entry_payload_is_checked_before_allocating() {
uncompressed[8..12].copy_from_slice(&u32::MAX.to_le_bytes());
assert!(CompactThetaSketch::deserialize(&uncompressed).is_err());
- let mut sketch = ThetaSketchBuilder::default().lg_k(5).build();
+ let mut sketch = ThetaSketchBuilder::default().lg_k(5).build().unwrap();
for value in 0..5000 {
sketch.update(value);
}
diff --git a/tests-integration/tests/serde_tests/tuple.rs
b/tests-integration/tests/serde_tests/tuple.rs
index 33103f0..2b73c0b 100644
--- a/tests-integration/tests/serde_tests/tuple.rs
+++ b/tests-integration/tests/serde_tests/tuple.rs
@@ -109,7 +109,9 @@ fn test_go_compatibility() {
#[test]
fn round_trip_preserves_summaries() {
- let mut sketch =
TupleSketchBuilder::new(DefaultUpdatePolicy::<u64>::default()).build();
+ let mut sketch =
TupleSketchBuilder::new(DefaultUpdatePolicy::<u64>::default())
+ .build()
+ .unwrap();
for value in 0..50 {
sketch.update(value, 1);
sketch.update(value, 2);
@@ -125,7 +127,9 @@ fn round_trip_preserves_summaries() {
#[test]
fn malformed_input_is_rejected() {
- let mut sketch =
TupleSketchBuilder::new(DefaultUpdatePolicy::<u64>::default()).build();
+ let mut sketch =
TupleSketchBuilder::new(DefaultUpdatePolicy::<u64>::default())
+ .build()
+ .unwrap();
for value in 0..100 {
sketch.update(value, 1);
}
@@ -146,7 +150,9 @@ fn malformed_input_is_rejected() {
#[test]
fn declared_entry_payload_is_checked_before_allocating() {
- let mut sketch =
TupleSketchBuilder::new(DefaultUpdatePolicy::<u64>::default()).build();
+ let mut sketch =
TupleSketchBuilder::new(DefaultUpdatePolicy::<u64>::default())
+ .build()
+ .unwrap();
for value in 0..100 {
sketch.update(value, 1);
}
diff --git a/tests-integration/tests/theta_test/a_not_b.rs
b/tests-integration/tests/theta_test/a_not_b.rs
index bc98820..d5918ca 100644
--- a/tests-integration/tests/theta_test/a_not_b.rs
+++ b/tests-integration/tests/theta_test/a_not_b.rs
@@ -31,7 +31,7 @@ use googletest::prelude::lt;
use googletest::prelude::near;
fn sketch_with_range(start: u64, count: u64) -> ThetaSketch {
- let mut sketch = ThetaSketchBuilder::default().build();
+ let mut sketch = ThetaSketchBuilder::default().build().unwrap();
for i in 0..count {
sketch.update(start + i);
}
@@ -40,10 +40,10 @@ fn sketch_with_range(start: u64, count: u64) -> ThetaSketch
{
#[test]
fn test_basic_difference() {
- let mut a = ThetaSketchBuilder::default().build();
+ let mut a = ThetaSketchBuilder::default().build().unwrap();
a.update("shared");
a.update("only_a");
- let mut b = ThetaSketchBuilder::default().build();
+ let mut b = ThetaSketchBuilder::default().build().unwrap();
b.update("shared");
b.update("only_b");
@@ -71,7 +71,7 @@ fn test_accepts_updatable_and_compact_inputs() {
#[test]
fn test_seed_mismatch_returns_error() {
- let mut one_other_seed = ThetaSketchBuilder::default().seed(2).build();
+ let mut one_other_seed =
ThetaSketchBuilder::default().seed(2).build().unwrap();
one_other_seed.update("value");
let good = sketch_with_range(0, 10);
@@ -89,7 +89,7 @@ fn test_seed_mismatch_returns_error() {
#[test]
fn test_seed_mismatch_ignored_for_empty_inputs() {
// Empty inputs carry no keys, so their seeds are not validated.
- let empty_other_seed = ThetaSketchBuilder::default().seed(2).build();
+ let empty_other_seed =
ThetaSketchBuilder::default().seed(2).build().unwrap();
let good = sketch_with_range(0, 10);
let a_not_b = ThetaANotB::default();
@@ -103,7 +103,7 @@ fn test_seed_mismatch_ignored_for_empty_inputs() {
#[test]
fn test_empty_a_returns_empty() {
- let empty = ThetaSketchBuilder::default().build();
+ let empty = ThetaSketchBuilder::default().build().unwrap();
let b = sketch_with_range(0, 1000);
let a_not_b = ThetaANotB::default();
@@ -117,7 +117,7 @@ fn test_empty_a_returns_empty() {
#[test]
fn test_empty_b_returns_a() {
let a = sketch_with_range(0, 1000);
- let empty = ThetaSketchBuilder::default().build();
+ let empty = ThetaSketchBuilder::default().build().unwrap();
let a_not_b = ThetaANotB::default();
let r = a_not_b.compute(&a, &empty, true).unwrap();
@@ -186,7 +186,7 @@ fn test_exact_superset_b_returns_empty() {
#[test]
fn test_result_ordering() {
let a = sketch_with_range(0, 64);
- let empty = ThetaSketchBuilder::default().build();
+ let empty = ThetaSketchBuilder::default().build().unwrap();
let a_not_b = ThetaANotB::default();
diff --git a/tests-integration/tests/theta_test/intersection.rs
b/tests-integration/tests/theta_test/intersection.rs
index 49c7593..dd11896 100644
--- a/tests-integration/tests/theta_test/intersection.rs
+++ b/tests-integration/tests/theta_test/intersection.rs
@@ -27,7 +27,7 @@ use googletest::prelude::near;
use googletest::prelude::none;
fn sketch_with_range(start: u64, count: u64) -> ThetaSketch {
- let mut sketch = ThetaSketchBuilder::default().build();
+ let mut sketch = ThetaSketchBuilder::default().build().unwrap();
for i in 0..count {
sketch.update(start + i);
}
@@ -36,7 +36,7 @@ fn sketch_with_range(start: u64, count: u64) -> ThetaSketch {
#[test]
fn test_has_result_state_machine() {
- let mut a = ThetaSketchBuilder::default().build();
+ let mut a = ThetaSketchBuilder::default().build().unwrap();
a.update("x");
let mut i = ThetaIntersection::default();
@@ -54,11 +54,11 @@ fn test_result_before_first_update_returns_none() {
#[test]
fn test_update_accepts_compact_sketch() {
- let mut a = ThetaSketchBuilder::default().build();
+ let mut a = ThetaSketchBuilder::default().build().unwrap();
a.update("x");
a.update("y");
- let mut b = ThetaSketchBuilder::default().build();
+ let mut b = ThetaSketchBuilder::default().build().unwrap();
b.update("y");
b.update("z");
@@ -70,7 +70,7 @@ fn test_update_accepts_compact_sketch() {
assert_eq!(r.estimate(), 1.0);
assert!(r.is_ordered());
- let mut c = ThetaSketchBuilder::default().build();
+ let mut c = ThetaSketchBuilder::default().build().unwrap();
c.update("a");
c.update("b");
c.update("c");
@@ -84,7 +84,7 @@ fn test_update_accepts_compact_sketch() {
#[test]
fn test_seed_mismatch_behaviour_for_empty_sketch() {
- let empty_other_seed = ThetaSketchBuilder::default().seed(2).build();
+ let empty_other_seed =
ThetaSketchBuilder::default().seed(2).build().unwrap();
let mut i = ThetaIntersection::with_seed(1);
i.update(&empty_other_seed).unwrap();
@@ -95,7 +95,7 @@ fn test_seed_mismatch_behaviour_for_empty_sketch() {
#[test]
fn test_seed_mismatch_behaviour() {
- let mut one_other_seed = ThetaSketchBuilder::default().seed(2).build();
+ let mut one_other_seed =
ThetaSketchBuilder::default().seed(2).build().unwrap();
one_other_seed.update("value");
let mut i = ThetaIntersection::with_seed(1);
@@ -104,9 +104,9 @@ fn test_seed_mismatch_behaviour() {
#[test]
fn test_terminal_empty_state_ignores_future_updates() {
- let empty = ThetaSketchBuilder::default().build();
+ let empty = ThetaSketchBuilder::default().build().unwrap();
- let mut non_empty = ThetaSketchBuilder::default().build();
+ let mut non_empty = ThetaSketchBuilder::default().build().unwrap();
non_empty.update("x");
let mut i = ThetaIntersection::default();
@@ -119,7 +119,7 @@ fn test_terminal_empty_state_ignores_future_updates() {
#[test]
fn test_to_sketch_unordered_is_not_ordered() {
- let mut a = ThetaSketchBuilder::default().build();
+ let mut a = ThetaSketchBuilder::default().build().unwrap();
for i in 0..64 {
a.update(i);
}
@@ -132,7 +132,7 @@ fn test_to_sketch_unordered_is_not_ordered() {
#[test]
fn test_empty_update_twice() {
- let empty = ThetaSketchBuilder::default().build();
+ let empty = ThetaSketchBuilder::default().build().unwrap();
let mut i = ThetaIntersection::default();
i.update(&empty).unwrap();
@@ -154,7 +154,8 @@ fn test_empty_update_twice() {
fn test_non_empty_no_retained_keys() {
let mut s = ThetaSketchBuilder::default()
.sampling_probability(0.001)
- .build();
+ .build()
+ .unwrap();
s.update(1u64);
let mut i = ThetaIntersection::default();
@@ -314,7 +315,7 @@ fn test_estimation_disjoint_ordered() {
#[test]
fn test_seed_mismatch_non_empty_returns_error() {
- let mut s = ThetaSketchBuilder::default().build();
+ let mut s = ThetaSketchBuilder::default().build().unwrap();
s.update(1u64);
let mut i = ThetaIntersection::with_seed(123);
diff --git a/tests-integration/tests/theta_test/jaccard_similarity.rs
b/tests-integration/tests/theta_test/jaccard_similarity.rs
index 104dd51..aa72912 100644
--- a/tests-integration/tests/theta_test/jaccard_similarity.rs
+++ b/tests-integration/tests/theta_test/jaccard_similarity.rs
@@ -41,7 +41,7 @@ fn assert_jaccard_estimate(actual: JaccardSimilarity,
expected: f64) {
}
fn sketch_with_range(start: u64, count: u64) -> ThetaSketch {
- let mut sketch = ThetaSketchBuilder::default().build();
+ let mut sketch = ThetaSketchBuilder::default().build().unwrap();
for value in start..start + count {
sketch.update(value);
}
@@ -49,7 +49,7 @@ fn sketch_with_range(start: u64, count: u64) -> ThetaSketch {
}
fn sketch_with_range_and_seed(start: u64, count: u64, seed: u64) ->
ThetaSketch {
- let mut sketch = ThetaSketchBuilder::default().seed(seed).build();
+ let mut sketch = ThetaSketchBuilder::default().seed(seed).build().unwrap();
for value in start..start + count {
sketch.update(value);
}
@@ -58,8 +58,8 @@ fn sketch_with_range_and_seed(start: u64, count: u64, seed:
u64) -> ThetaSketch
#[test]
fn test_empty() {
- let sketch_a = ThetaSketchBuilder::default().build();
- let sketch_b = ThetaSketchBuilder::default().build();
+ let sketch_a = ThetaSketchBuilder::default().build().unwrap();
+ let sketch_b = ThetaSketchBuilder::default().build().unwrap();
let operator = ThetaJaccardSimilarity::default();
let jaccard = operator.compute(&sketch_a, &sketch_b).unwrap();
@@ -70,7 +70,7 @@ fn test_empty() {
#[test]
fn test_exactly_equal() {
- let empty = ThetaSketchBuilder::default().build();
+ let empty = ThetaSketchBuilder::default().build().unwrap();
let sketch_a = sketch_with_range(0, 1000);
let sketch_b = sketch_with_range(0, 1000);
let sketch_c = sketch_with_range(1000, 1000);
@@ -162,10 +162,10 @@ fn test_half_overlap_estimation_mode_custom_seed() {
#[test]
fn test_seed_mismatch() {
- let empty = ThetaSketchBuilder::default().build();
- let mut sketch_a = ThetaSketchBuilder::default().build();
+ let empty = ThetaSketchBuilder::default().build().unwrap();
+ let mut sketch_a = ThetaSketchBuilder::default().build().unwrap();
sketch_a.update(1u64);
- let mut sketch_b = ThetaSketchBuilder::default().seed(123).build();
+ let mut sketch_b =
ThetaSketchBuilder::default().seed(123).build().unwrap();
sketch_b.update(1u64);
assert_that!(
@@ -187,13 +187,16 @@ fn test_seed_mismatch() {
fn test_distinct_non_empty_sketches_with_no_retained_entries_are_uncertain() {
let mut sketch_a = ThetaSketchBuilder::default()
.sampling_probability(1e-12)
- .build();
+ .build()
+ .unwrap();
let mut sketch_b = ThetaSketchBuilder::default()
.sampling_probability(1e-12)
- .build();
+ .build()
+ .unwrap();
let mut different_theta = ThetaSketchBuilder::default()
.sampling_probability(2e-12)
- .build();
+ .build()
+ .unwrap();
sketch_a.update("apple");
sketch_b.update("banana");
different_theta.update("orange");
diff --git a/tests-integration/tests/theta_test/sketch.rs
b/tests-integration/tests/theta_test/sketch.rs
index f4d976f..5bf7aaa 100644
--- a/tests-integration/tests/theta_test/sketch.rs
+++ b/tests-integration/tests/theta_test/sketch.rs
@@ -16,6 +16,7 @@
// under the License.
use datasketches::common::NumStdDev;
+use datasketches::error::ErrorKind;
use datasketches::hash::value::canonical_float;
use datasketches::theta::ThetaSketchBuilder;
use googletest::assert_that;
@@ -25,9 +26,21 @@ use googletest::prelude::le;
use googletest::prelude::lt;
use googletest::prelude::near;
+#[test]
+fn builder_validates_configuration_at_build() {
+ let error = ThetaSketchBuilder::default().lg_k(4).build().unwrap_err();
+ assert_eq!(error.kind(), ErrorKind::InvalidArgument);
+
+ let error = ThetaSketchBuilder::default()
+ .sampling_probability(f32::NAN)
+ .build()
+ .unwrap_err();
+ assert_eq!(error.kind(), ErrorKind::InvalidArgument);
+}
+
#[test]
fn test_basic_update() {
- let mut sketch = ThetaSketchBuilder::default().lg_k(12).build();
+ let mut sketch = ThetaSketchBuilder::default().lg_k(12).build().unwrap();
assert!(sketch.is_empty());
assert_eq!(sketch.estimate(), 0.0);
@@ -41,7 +54,7 @@ fn test_basic_update() {
#[test]
fn test_update_various_types() {
- let mut sketch = ThetaSketchBuilder::default().lg_k(12).build();
+ let mut sketch = ThetaSketchBuilder::default().lg_k(12).build().unwrap();
sketch.update("string");
sketch.update(42i64);
@@ -56,7 +69,7 @@ fn test_update_various_types() {
assert!(!sketch.is_empty());
assert_eq!(sketch.estimate(), 5.0);
- let mut sketch = ThetaSketchBuilder::default().lg_k(12).build();
+ let mut sketch = ThetaSketchBuilder::default().lg_k(12).build().unwrap();
sketch.update("string");
sketch.update(42i64);
@@ -74,7 +87,7 @@ fn test_update_various_types() {
#[test]
fn test_duplicate_updates() {
- let mut sketch = ThetaSketchBuilder::default().lg_k(12).build();
+ let mut sketch = ThetaSketchBuilder::default().lg_k(12).build().unwrap();
for _ in 0..100 {
sketch.update("same_value");
@@ -85,7 +98,7 @@ fn test_duplicate_updates() {
#[test]
fn test_theta_reduction() {
- let mut sketch = ThetaSketchBuilder::default().lg_k(5).build(); // Small k
to trigger theta reduction
+ let mut sketch = ThetaSketchBuilder::default().lg_k(5).build().unwrap();
// Small k to trigger theta reduction
assert!(!sketch.is_estimation_mode()); // Should be in estimation mode
// Insert many values to trigger theta reduction
@@ -99,8 +112,8 @@ fn test_theta_reduction() {
#[test]
fn test_trim() {
- let mut exact = ThetaSketchBuilder::default().lg_k(12).build();
- let mut sketch = ThetaSketchBuilder::default().lg_k(5).build();
+ let mut exact = ThetaSketchBuilder::default().lg_k(12).build().unwrap();
+ let mut sketch = ThetaSketchBuilder::default().lg_k(5).build().unwrap();
for i in 0..1000 {
exact.update(i);
@@ -124,7 +137,7 @@ fn test_trim() {
#[test]
fn test_reset() {
- let mut sketch = ThetaSketchBuilder::default().lg_k(5).build();
+ let mut sketch = ThetaSketchBuilder::default().lg_k(5).build().unwrap();
// Insert many values
for i in 0..1000 {
@@ -147,7 +160,7 @@ fn test_reset() {
#[test]
fn test_iterator() {
- let mut sketch = ThetaSketchBuilder::default().lg_k(12).build();
+ let mut sketch = ThetaSketchBuilder::default().lg_k(12).build().unwrap();
sketch.update("value1");
sketch.update("value2");
@@ -159,7 +172,7 @@ fn test_iterator() {
#[test]
fn test_bounds_empty_sketch() {
- let sketch = ThetaSketchBuilder::default().lg_k(12).build();
+ let sketch = ThetaSketchBuilder::default().lg_k(12).build().unwrap();
assert!(sketch.is_empty());
assert!(!sketch.is_estimation_mode());
assert_eq!(sketch.theta(), 1.0);
@@ -174,7 +187,7 @@ fn test_bounds_empty_sketch() {
#[test]
fn test_bounds_exact_mode() {
- let mut sketch = ThetaSketchBuilder::default().lg_k(12).build();
+ let mut sketch = ThetaSketchBuilder::default().lg_k(12).build().unwrap();
for i in 0..2000 {
sketch.update(i);
}
@@ -188,7 +201,7 @@ fn test_bounds_exact_mode() {
#[test]
fn test_bounds_estimation_mode() {
- let mut sketch = ThetaSketchBuilder::default().lg_k(12).build();
+ let mut sketch = ThetaSketchBuilder::default().lg_k(12).build().unwrap();
let n = 10000;
for i in 0..n {
sketch.update(i);
@@ -228,7 +241,8 @@ fn test_bounds_with_sampling() {
let mut sketch = ThetaSketchBuilder::default()
.lg_k(12)
.sampling_probability(0.5)
- .build();
+ .build()
+ .unwrap();
for i in 0..1000 {
sketch.update(i);
@@ -248,7 +262,7 @@ fn test_bounds_with_sampling() {
#[test]
fn test_bounds_all_num_std_devs() {
- let mut sketch = ThetaSketchBuilder::default().lg_k(12).build();
+ let mut sketch = ThetaSketchBuilder::default().lg_k(12).build().unwrap();
for i in 0..10000 {
sketch.update(i);
}
@@ -275,7 +289,8 @@ fn test_bounds_empty_estimation_mode() {
let sketch = ThetaSketchBuilder::default()
.lg_k(12)
.sampling_probability(0.1)
- .build();
+ .build()
+ .unwrap();
// The sketch is empty but theta < 1.0, so it's in estimation mode
// However, when empty, both bounds should return 0.0 per Java
implementation
@@ -293,7 +308,8 @@ fn
test_compact_preserves_logical_non_empty_after_screened_update() {
let mut sketch = ThetaSketchBuilder::default()
.lg_k(12)
.sampling_probability(0.5)
- .build();
+ .build()
+ .unwrap();
sketch.update(*candidate);
!sketch.is_empty() && sketch.num_retained() == 0
})
@@ -302,7 +318,8 @@ fn
test_compact_preserves_logical_non_empty_after_screened_update() {
let mut sketch = ThetaSketchBuilder::default()
.lg_k(12)
.sampling_probability(0.5)
- .build();
+ .build()
+ .unwrap();
sketch.update(screened_value);
assert!(!sketch.is_empty());
diff --git a/tests-integration/tests/theta_test/union.rs
b/tests-integration/tests/theta_test/union.rs
index c19eb9d..5b5ede5 100644
--- a/tests-integration/tests/theta_test/union.rs
+++ b/tests-integration/tests/theta_test/union.rs
@@ -15,6 +15,7 @@
// specific language governing permissions and limitations
// under the License.
+use datasketches::error::ErrorKind;
use datasketches::theta::CompactThetaSketch;
use datasketches::theta::ThetaSketch;
use datasketches::theta::ThetaSketchBuilder;
@@ -25,8 +26,20 @@ use googletest::prelude::err;
use googletest::prelude::le;
use googletest::prelude::near;
+#[test]
+fn builder_validates_configuration_at_build() {
+ let error = ThetaUnionBuilder::default().lg_k(27).build().unwrap_err();
+ assert_eq!(error.kind(), ErrorKind::InvalidArgument);
+
+ let error = ThetaUnionBuilder::default()
+ .sampling_probability(0.0)
+ .build()
+ .unwrap_err();
+ assert_eq!(error.kind(), ErrorKind::InvalidArgument);
+}
+
fn sketch_with_range(lg_k: u8, start: i64, count: i64) -> ThetaSketch {
- let mut sketch = ThetaSketchBuilder::default().lg_k(lg_k).build();
+ let mut sketch = ThetaSketchBuilder::default().lg_k(lg_k).build().unwrap();
for value in start..start + count {
sketch.update(value);
}
@@ -45,8 +58,8 @@ fn assert_estimate_close(sketch: &CompactThetaSketch,
expected: f64, tolerance:
#[test]
fn test_empty_union() {
- let sketch = ThetaSketchBuilder::default().build();
- let mut union = ThetaUnionBuilder::default().build();
+ let sketch = ThetaSketchBuilder::default().build().unwrap();
+ let mut union = ThetaUnionBuilder::default().build().unwrap();
let result = union.to_sketch(true);
assert_eq!(result.num_retained(), 0);
assert!(result.is_empty());
@@ -63,10 +76,11 @@ fn test_empty_union() {
fn test_non_empty_no_retained_keys() {
let mut sketch = ThetaSketchBuilder::default()
.sampling_probability(0.001)
- .build();
+ .build()
+ .unwrap();
sketch.update(1u64);
- let mut union = ThetaUnionBuilder::default().build();
+ let mut union = ThetaUnionBuilder::default().build().unwrap();
union.update(&sketch).unwrap();
let result = union.to_sketch(true);
assert_eq!(result.num_retained(), 0);
@@ -77,17 +91,17 @@ fn test_non_empty_no_retained_keys() {
#[test]
fn test_exact_mode_half_overlap() {
- let mut sketch1 = ThetaSketchBuilder::default().build();
+ let mut sketch1 = ThetaSketchBuilder::default().build().unwrap();
for value in 0i64..1000i64 {
sketch1.update(value);
}
- let mut sketch2 = ThetaSketchBuilder::default().build();
+ let mut sketch2 = ThetaSketchBuilder::default().build().unwrap();
for value in 500i64..1500i64 {
sketch2.update(value);
}
- let mut union = ThetaUnionBuilder::default().build();
+ let mut union = ThetaUnionBuilder::default().build().unwrap();
union.update(&sketch1).unwrap();
union.update(&sketch2).unwrap();
let result = union.to_sketch(true);
@@ -104,19 +118,19 @@ fn test_exact_mode_half_overlap() {
#[test]
fn test_exact_mode_half_overlap_compact() {
- let mut sketch1 = ThetaSketchBuilder::default().build();
+ let mut sketch1 = ThetaSketchBuilder::default().build().unwrap();
for value in 0i64..1000i64 {
sketch1.update(value);
}
let compact1 =
CompactThetaSketch::deserialize(&sketch1.compact(true).serialize()).unwrap();
- let mut sketch2 = ThetaSketchBuilder::default().build();
+ let mut sketch2 = ThetaSketchBuilder::default().build().unwrap();
for value in 500i64..1500i64 {
sketch2.update(value);
}
let compact2 =
CompactThetaSketch::deserialize(&sketch2.compact(true).serialize()).unwrap();
- let mut union = ThetaUnionBuilder::default().build();
+ let mut union = ThetaUnionBuilder::default().build().unwrap();
union.update(&compact1).unwrap();
union.update(&compact2).unwrap();
let result = union.to_sketch(true);
@@ -127,17 +141,17 @@ fn test_exact_mode_half_overlap_compact() {
#[test]
fn test_estimation_mode_half_overlap() {
- let mut sketch1 = ThetaSketchBuilder::default().build();
+ let mut sketch1 = ThetaSketchBuilder::default().build().unwrap();
for value in 0i64..10000i64 {
sketch1.update(value);
}
- let mut sketch2 = ThetaSketchBuilder::default().build();
+ let mut sketch2 = ThetaSketchBuilder::default().build().unwrap();
for value in 5000i64..15000i64 {
sketch2.update(value);
}
- let mut union = ThetaUnionBuilder::default().build();
+ let mut union = ThetaUnionBuilder::default().build().unwrap();
union.update(&sketch1).unwrap();
union.update(&sketch2).unwrap();
let result = union.to_sketch(true);
@@ -154,38 +168,38 @@ fn test_estimation_mode_half_overlap() {
#[test]
fn test_seed_mismatch() {
- let mut sketch = ThetaSketchBuilder::default().build();
+ let mut sketch = ThetaSketchBuilder::default().build().unwrap();
sketch.update(1u64);
- let mut union = ThetaUnionBuilder::default().seed(123).build();
+ let mut union = ThetaUnionBuilder::default().seed(123).build().unwrap();
assert_that!(union.update(&sketch), err(anything()));
}
#[test]
fn test_larger_k() {
- let mut sketch1 = ThetaSketchBuilder::default().lg_k(14).build();
+ let mut sketch1 = ThetaSketchBuilder::default().lg_k(14).build().unwrap();
for value in 0i64..16384i64 {
sketch1.update(value);
}
- let mut sketch2 = ThetaSketchBuilder::default().lg_k(14).build();
+ let mut sketch2 = ThetaSketchBuilder::default().lg_k(14).build().unwrap();
for value in 0i64..26384i64 {
sketch2.update(value);
}
- let mut sketch3 = ThetaSketchBuilder::default().lg_k(14).build();
+ let mut sketch3 = ThetaSketchBuilder::default().lg_k(14).build().unwrap();
for value in 0i64..86384i64 {
sketch3.update(value);
}
- let mut union1 = ThetaUnionBuilder::default().lg_k(16).build();
+ let mut union1 = ThetaUnionBuilder::default().lg_k(16).build().unwrap();
union1.update(&sketch2).unwrap();
union1.update(&sketch1).unwrap();
union1.update(&sketch3).unwrap();
let result1 = union1.to_sketch(true);
assert_eq!(result1.estimate(), sketch3.estimate());
- let mut union2 = ThetaUnionBuilder::default().lg_k(16).build();
+ let mut union2 = ThetaUnionBuilder::default().lg_k(16).build().unwrap();
union2.update(&sketch1).unwrap();
union2.update(&sketch3).unwrap();
union2.update(&sketch2).unwrap();
@@ -200,7 +214,7 @@ fn test_exact_union_no_overlap() {
let sketch1 = sketch_with_range(lg_k, 0, k / 2);
let sketch2 = sketch_with_range(lg_k, k / 2, k / 2);
- let mut union = ThetaUnionBuilder::default().lg_k(lg_k).build();
+ let mut union = ThetaUnionBuilder::default().lg_k(lg_k).build().unwrap();
union.update(&sketch1).unwrap();
union.update(&sketch2).unwrap();
@@ -217,7 +231,7 @@ fn test_estimation_union_no_overlap() {
let sketch1 = sketch_with_range(lg_k, 0, 2 * k);
let sketch2 = sketch_with_range(lg_k, 2 * k, 2 * k);
- let mut union = ThetaUnionBuilder::default().lg_k(lg_k).build();
+ let mut union = ThetaUnionBuilder::default().lg_k(lg_k).build().unwrap();
union.update(&sketch1).unwrap();
union.update(&sketch2).unwrap();
@@ -235,7 +249,7 @@ fn test_exact_union_with_overlap() {
let sketch1 = sketch_with_range(lg_k, 0, k / 2);
let sketch2 = sketch_with_range(lg_k, 0, k);
- let mut union = ThetaUnionBuilder::default().lg_k(lg_k).build();
+ let mut union = ThetaUnionBuilder::default().lg_k(lg_k).build().unwrap();
union.update(&sketch1).unwrap();
union.update(&sketch2).unwrap();
@@ -254,11 +268,11 @@ fn test_ordered_and_unordered_compact_inputs() {
let compact_ordered = sketch2.compact(true);
let compact_unordered = sketch2.compact(false);
- let mut ordered_union = ThetaUnionBuilder::default().lg_k(lg_k).build();
+ let mut ordered_union =
ThetaUnionBuilder::default().lg_k(lg_k).build().unwrap();
ordered_union.update(&sketch1).unwrap();
ordered_union.update(&compact_ordered).unwrap();
- let mut unordered_union = ThetaUnionBuilder::default().lg_k(lg_k).build();
+ let mut unordered_union =
ThetaUnionBuilder::default().lg_k(lg_k).build().unwrap();
unordered_union.update(&sketch1).unwrap();
unordered_union.update(&compact_unordered).unwrap();
@@ -278,7 +292,7 @@ fn test_result_ordering_forms_have_same_estimate() {
let sketch1 = sketch_with_range(12, 0, 8192);
let sketch2 = sketch_with_range(12, 8192, 1024);
- let mut union = ThetaUnionBuilder::default().lg_k(12).build();
+ let mut union = ThetaUnionBuilder::default().lg_k(12).build().unwrap();
union.update(&sketch1).unwrap();
union.update(&sketch2).unwrap();
@@ -299,7 +313,7 @@ fn test_multi_union() {
(126_797, 26_797),
(153_594, 26_797),
];
- let mut union = ThetaUnionBuilder::default().lg_k(lg_k).build();
+ let mut union = ThetaUnionBuilder::default().lg_k(lg_k).build().unwrap();
for (start, count) in ranges {
let sketch = sketch_with_range(lg_k, start, count);
@@ -316,7 +330,7 @@ fn test_result_does_not_reset_union() {
let compact1 = sketch_with_range(lg_k, 0, k).compact(true);
let compact2 = sketch_with_range(lg_k, k, k).compact(true);
- let mut union = ThetaUnionBuilder::default().lg_k(lg_k).build();
+ let mut union = ThetaUnionBuilder::default().lg_k(lg_k).build().unwrap();
union.update(&compact1).unwrap();
union.update(&compact2).unwrap();
let first = union.to_sketch(true);
@@ -333,7 +347,7 @@ fn test_union_full_overlap() {
let compact1 = sketch_with_range(lg_k, 0, k).compact(true);
let compact2 = sketch_with_range(lg_k, 0, k).compact(true);
- let mut union = ThetaUnionBuilder::default().lg_k(lg_k).build();
+ let mut union = ThetaUnionBuilder::default().lg_k(lg_k).build().unwrap();
union.update(&compact1).unwrap();
union.update(&compact2).unwrap();
let result = union.to_sketch(true);
@@ -356,11 +370,11 @@ fn
test_ordered_input_early_stop_matches_unordered_input() {
let unordered1 = sketch1.compact(false);
let unordered2 = sketch2.compact(false);
- let mut ordered_union = ThetaUnionBuilder::default().lg_k(lg_k +
1).build();
+ let mut ordered_union = ThetaUnionBuilder::default().lg_k(lg_k +
1).build().unwrap();
ordered_union.update(&ordered1).unwrap();
ordered_union.update(&ordered2).unwrap();
- let mut unordered_union = ThetaUnionBuilder::default().lg_k(lg_k +
1).build();
+ let mut unordered_union = ThetaUnionBuilder::default().lg_k(lg_k +
1).build().unwrap();
unordered_union.update(&unordered1).unwrap();
unordered_union.update(&unordered2).unwrap();
@@ -378,7 +392,7 @@ fn test_union_cutback_to_k() {
let compact1 = sketch_with_range(lg_k, 0, 3 * k).compact(true);
let compact2 = sketch_with_range(lg_k, 6 * k, 3 * k).compact(true);
- let mut union = ThetaUnionBuilder::default().lg_k(lg_k).build();
+ let mut union = ThetaUnionBuilder::default().lg_k(lg_k).build().unwrap();
union.update(&compact1).unwrap();
union.update(&compact2).unwrap();
let result = union.to_sketch(true);
@@ -389,23 +403,23 @@ fn test_union_cutback_to_k() {
#[test]
fn test_union_empty_valid_rules() {
- let empty1 = ThetaSketchBuilder::default().build().compact(true);
- let empty2 = ThetaSketchBuilder::default().build().compact(true);
- let mut one = ThetaSketchBuilder::default().build();
+ let empty1 = ThetaSketchBuilder::default().build().unwrap().compact(true);
+ let empty2 = ThetaSketchBuilder::default().build().unwrap().compact(true);
+ let mut one = ThetaSketchBuilder::default().build().unwrap();
one.update(1i64);
let one = one.compact(true);
- let mut empty_union = ThetaUnionBuilder::default().lg_k(5).build();
+ let mut empty_union =
ThetaUnionBuilder::default().lg_k(5).build().unwrap();
empty_union.update(&empty1).unwrap();
empty_union.update(&empty2).unwrap();
assert!(empty_union.to_sketch(true).is_empty());
- let mut left_non_empty_union =
ThetaUnionBuilder::default().lg_k(5).build();
+ let mut left_non_empty_union =
ThetaUnionBuilder::default().lg_k(5).build().unwrap();
left_non_empty_union.update(&one).unwrap();
left_non_empty_union.update(&empty2).unwrap();
assert!(!left_non_empty_union.to_sketch(true).is_empty());
- let mut right_non_empty_union =
ThetaUnionBuilder::default().lg_k(5).build();
+ let mut right_non_empty_union =
ThetaUnionBuilder::default().lg_k(5).build().unwrap();
right_non_empty_union.update(&empty1).unwrap();
right_non_empty_union.update(&one).unwrap();
assert!(!right_non_empty_union.to_sketch(true).is_empty());
@@ -416,7 +430,7 @@ fn test_trim_to_k() {
let hi_sketch = sketch_with_range(10, 0, 3749);
let lo_sketch = sketch_with_range(9, 10_000, 1783);
- let mut union = ThetaUnionBuilder::default().lg_k(10).build();
+ let mut union = ThetaUnionBuilder::default().lg_k(10).build().unwrap();
union.update(&hi_sketch).unwrap();
union.update(&lo_sketch).unwrap();
let result = union.to_sketch(true);
@@ -427,7 +441,7 @@ fn test_trim_to_k() {
#[test]
fn test_builder_lg_k() {
let sketch = sketch_with_range(10, 0, 1000);
- let mut union = ThetaUnionBuilder::default().lg_k(10).build();
+ let mut union = ThetaUnionBuilder::default().lg_k(10).build().unwrap();
union.update(&sketch).unwrap();
assert_eq!(union.to_sketch(true).estimate(), 1000.0);
@@ -444,9 +458,9 @@ enum CornerSketchState {
fn corner_sketch(state: CornerSketchState, p: f32, value: i64) -> ThetaSketch {
let builder = ThetaSketchBuilder::default().lg_k(5);
let mut sketch = match state {
- CornerSketchState::Empty | CornerSketchState::Exact => builder.build(),
+ CornerSketchState::Empty | CornerSketchState::Exact =>
builder.build().unwrap(),
CornerSketchState::Estimation | CornerSketchState::Degenerate => {
- builder.sampling_probability(p).build()
+ builder.sampling_probability(p).build().unwrap()
}
};
if !matches!(state, CornerSketchState::Empty) {
@@ -657,7 +671,7 @@ fn test_corner_case_union_states() {
let sketch_a = corner_sketch(state_a, p_a, value_a);
let sketch_b = corner_sketch(state_b, p_b, value_b);
- let mut union = ThetaUnionBuilder::default().build();
+ let mut union = ThetaUnionBuilder::default().build().unwrap();
union.update(&sketch_a).unwrap();
union.update(&sketch_b).unwrap();
let result = union.to_sketch(true);
@@ -680,7 +694,7 @@ fn test_corner_case_union_states() {
let compact_a = sketch_a.compact(true);
let compact_b = sketch_b.compact(true);
- let mut union = ThetaUnionBuilder::default().build();
+ let mut union = ThetaUnionBuilder::default().build().unwrap();
union.update(&compact_a).unwrap();
union.update(&compact_b).unwrap();
let compact_result = union.to_sketch(true);
@@ -693,7 +707,7 @@ fn test_corner_case_union_states() {
#[test]
fn test_union_estimated_size() {
- let mut union = ThetaUnionBuilder::default().build();
+ let mut union = ThetaUnionBuilder::default().build().unwrap();
assert_eq!(union.estimated_size(), 1096);
let sketch = sketch_with_range(12, 0, 1000);
diff --git a/tests-integration/tests/tuple_test/a_not_b.rs
b/tests-integration/tests/tuple_test/a_not_b.rs
index cae015b..f766136 100644
--- a/tests-integration/tests/tuple_test/a_not_b.rs
+++ b/tests-integration/tests/tuple_test/a_not_b.rs
@@ -38,10 +38,10 @@ fn sorted_entries(sketch: &CompactTupleSketch<u64>) ->
Vec<(u64, u64)> {
#[test]
fn difference_keeps_only_a_summaries() {
- let mut a = default_tuple_sketch_builder().build();
+ let mut a = default_tuple_sketch_builder().build().unwrap();
a.update("shared", 3u64);
a.update("only_a", 5u64);
- let mut b = default_tuple_sketch_builder().build();
+ let mut b = default_tuple_sketch_builder().build().unwrap();
b.update("shared", 9u64);
b.update("only_b", 7u64);
@@ -72,8 +72,8 @@ fn accepts_mutable_and_compact_inputs() {
#[test]
fn input_and_result_ordering_preserve_entries() {
- let mut a = default_tuple_sketch_builder().lg_k(8).build();
- let mut b = default_tuple_sketch_builder().lg_k(8).build();
+ let mut a = default_tuple_sketch_builder().lg_k(8).build().unwrap();
+ let mut b = default_tuple_sketch_builder().lg_k(8).build().unwrap();
for value in 0..20_000 {
a.update(value, 1u64);
}
@@ -108,7 +108,7 @@ fn input_and_result_ordering_preserve_entries() {
#[test]
fn empty_inputs_do_not_impose_a_seed() {
- let empty_other_seed = default_tuple_sketch_builder().seed(2).build();
+ let empty_other_seed =
default_tuple_sketch_builder().seed(2).build().unwrap();
let non_empty = tuple_sketch_with_range(0, 10);
let op = TupleANotB::default();
@@ -127,9 +127,9 @@ fn empty_inputs_do_not_impose_a_seed() {
#[test]
fn non_empty_inputs_require_the_operator_seed() {
- let mut other_seed = default_tuple_sketch_builder().seed(2).build();
+ let mut other_seed =
default_tuple_sketch_builder().seed(2).build().unwrap();
other_seed.update("value", 1u64);
- let empty = default_tuple_sketch_builder().build();
+ let empty = default_tuple_sketch_builder().build().unwrap();
let good = tuple_sketch_with_range(0, 10);
let op = TupleANotB::default();
@@ -146,7 +146,8 @@ fn
empty_b_preserves_logically_non_empty_a_without_retained_entries() {
.find(|candidate| {
let mut sketch = default_tuple_sketch_builder()
.sampling_probability(0.001)
- .build();
+ .build()
+ .unwrap();
sketch.update(*candidate, 1u64);
!sketch.is_empty() && sketch.num_retained() == 0
})
@@ -154,9 +155,10 @@ fn
empty_b_preserves_logically_non_empty_a_without_retained_entries() {
let mut a = default_tuple_sketch_builder()
.sampling_probability(0.001)
- .build();
+ .build()
+ .unwrap();
a.update(screened_value, 1u64);
- let empty_b = default_tuple_sketch_builder().seed(999).build();
+ let empty_b = default_tuple_sketch_builder().seed(999).build().unwrap();
let result = TupleANotB::default().compute(&a, &empty_b, true).unwrap();
@@ -167,8 +169,8 @@ fn
empty_b_preserves_logically_non_empty_a_without_retained_entries() {
#[test]
fn estimation_bounds_cover_the_true_difference() {
- let mut a = default_tuple_sketch_builder().lg_k(8).build();
- let mut b = default_tuple_sketch_builder().lg_k(8).build();
+ let mut a = default_tuple_sketch_builder().lg_k(8).build().unwrap();
+ let mut b = default_tuple_sketch_builder().lg_k(8).build().unwrap();
for value in 0..50_000 {
a.update(value, 1u64);
}
diff --git a/tests-integration/tests/tuple_test/intersection.rs
b/tests-integration/tests/tuple_test/intersection.rs
index 8983c06..456058b 100644
--- a/tests-integration/tests/tuple_test/intersection.rs
+++ b/tests-integration/tests/tuple_test/intersection.rs
@@ -64,10 +64,10 @@ fn result_before_first_update_returns_none() {
#[test]
fn overlap_combines_summaries() {
- let mut a = default_tuple_sketch_builder().build();
+ let mut a = default_tuple_sketch_builder().build().unwrap();
a.update("shared", 3u64);
a.update("only_a", 100u64);
- let mut b = default_tuple_sketch_builder().build();
+ let mut b = default_tuple_sketch_builder().build().unwrap();
b.update("shared", 4u64);
b.update("only_b", 200u64);
@@ -114,7 +114,8 @@ fn
logically_non_empty_input_without_retained_entries_is_preserved() {
.find(|candidate| {
let mut sketch = default_tuple_sketch_builder()
.sampling_probability(0.001)
- .build();
+ .build()
+ .unwrap();
sketch.update(*candidate, 1u64);
!sketch.is_empty() && sketch.num_retained() == 0
})
@@ -122,7 +123,8 @@ fn
logically_non_empty_input_without_retained_entries_is_preserved() {
let mut sketch = default_tuple_sketch_builder()
.sampling_probability(0.001)
- .build();
+ .build()
+ .unwrap();
sketch.update(screened_value, 1u64);
let mut intersection = TupleIntersection::new(SumPolicy);
@@ -136,8 +138,8 @@ fn
logically_non_empty_input_without_retained_entries_is_preserved() {
#[test]
fn only_non_empty_inputs_require_the_operator_seed() {
- let empty_other_seed = default_tuple_sketch_builder().seed(2).build();
- let mut non_empty_other_seed =
default_tuple_sketch_builder().seed(2).build();
+ let empty_other_seed =
default_tuple_sketch_builder().seed(2).build().unwrap();
+ let mut non_empty_other_seed =
default_tuple_sketch_builder().seed(2).build().unwrap();
non_empty_other_seed.update("value", 1u64);
let mut intersection = TupleIntersection::with_seed(SumPolicy, 1);
@@ -160,8 +162,8 @@ fn result_ordering_follows_the_request() {
#[test]
fn estimation_bounds_cover_the_true_intersection() {
- let mut a = default_tuple_sketch_builder().lg_k(8).build();
- let mut b = default_tuple_sketch_builder().lg_k(8).build();
+ let mut a = default_tuple_sketch_builder().lg_k(8).build().unwrap();
+ let mut b = default_tuple_sketch_builder().lg_k(8).build().unwrap();
for value in 0..50_000 {
a.update(value, 1u64);
}
diff --git a/tests-integration/tests/tuple_test/jaccard_similarity.rs
b/tests-integration/tests/tuple_test/jaccard_similarity.rs
index 7bb6c2a..0c54c2e 100644
--- a/tests-integration/tests/tuple_test/jaccard_similarity.rs
+++ b/tests-integration/tests/tuple_test/jaccard_similarity.rs
@@ -45,8 +45,8 @@ fn assert_jaccard_estimate(actual: JaccardSimilarity,
expected: f64) {
#[test]
fn test_empty() {
- let sketch_a = default_tuple_sketch_builder().build();
- let sketch_b = default_tuple_sketch_builder().build();
+ let sketch_a = default_tuple_sketch_builder().build().unwrap();
+ let sketch_b = default_tuple_sketch_builder().build().unwrap();
let operator = TupleJaccardSimilarity::default();
let jaccard = operator.compute(&sketch_a, &sketch_b).unwrap();
@@ -57,8 +57,12 @@ fn test_empty() {
#[test]
fn test_summary_values_and_types_do_not_affect_similarity() {
- let mut sketch_a =
TupleSketchBuilder::new(DefaultUpdatePolicy::<u64>::default()).build();
- let mut sketch_b =
TupleSketchBuilder::new(DefaultUpdatePolicy::<i64>::default()).build();
+ let mut sketch_a =
TupleSketchBuilder::new(DefaultUpdatePolicy::<u64>::default())
+ .build()
+ .unwrap();
+ let mut sketch_b =
TupleSketchBuilder::new(DefaultUpdatePolicy::<i64>::default())
+ .build()
+ .unwrap();
for key in 0..1000 {
sketch_a.update(key, 1u64);
sketch_b.update(key, -7i64);
@@ -96,13 +100,15 @@ fn test_half_overlap_estimation_mode() {
#[test]
fn test_custom_seed_and_seed_mismatch() {
let seed = 123;
- let empty = default_tuple_sketch_builder().build();
+ let empty = default_tuple_sketch_builder().build().unwrap();
let mut sketch_a =
TupleSketchBuilder::new(DefaultUpdatePolicy::<u64>::default())
.seed(seed)
- .build();
+ .build()
+ .unwrap();
let mut sketch_b =
TupleSketchBuilder::new(DefaultUpdatePolicy::<u64>::default())
.seed(seed)
- .build();
+ .build()
+ .unwrap();
for value in 0..1000 {
sketch_a.update(value, 1u64);
sketch_b.update(value, 2u64);
@@ -131,10 +137,12 @@ fn test_custom_seed_and_seed_mismatch() {
fn test_distinct_non_empty_sketches_with_no_retained_entries_are_uncertain() {
let mut sketch_a = default_tuple_sketch_builder()
.sampling_probability(1e-12)
- .build();
+ .build()
+ .unwrap();
let mut sketch_b = default_tuple_sketch_builder()
.sampling_probability(1e-12)
- .build();
+ .build()
+ .unwrap();
sketch_a.update("apple", 1u64);
sketch_b.update("banana", 1u64);
diff --git a/tests-integration/tests/tuple_test/main.rs
b/tests-integration/tests/tuple_test/main.rs
index cbb6290..240e413 100644
--- a/tests-integration/tests/tuple_test/main.rs
+++ b/tests-integration/tests/tuple_test/main.rs
@@ -30,7 +30,7 @@ fn default_tuple_sketch_builder() ->
TupleSketchBuilder<DefaultUpdatePolicy<u64>
}
fn tuple_sketch_with_range(start: u64, count: u64) ->
TupleSketch<DefaultUpdatePolicy<u64>> {
- let mut sketch = default_tuple_sketch_builder().build();
+ let mut sketch = default_tuple_sketch_builder().build().unwrap();
for i in 0..count {
sketch.update(start + i, 1u64);
}
diff --git a/tests-integration/tests/tuple_test/sketch.rs
b/tests-integration/tests/tuple_test/sketch.rs
index 9a17a9d..4298228 100644
--- a/tests-integration/tests/tuple_test/sketch.rs
+++ b/tests-integration/tests/tuple_test/sketch.rs
@@ -16,6 +16,7 @@
// under the License.
use datasketches::common::NumStdDev;
+use datasketches::error::ErrorKind;
use datasketches::hash::value;
use datasketches::tuple::CompactTupleSketch;
use datasketches::tuple::DefaultUpdatePolicy;
@@ -30,9 +31,21 @@ use googletest::prelude::lt;
use crate::default_tuple_sketch_builder;
+#[test]
+fn builder_validates_configuration_at_build() {
+ let error = default_tuple_sketch_builder().lg_k(4).build().unwrap_err();
+ assert_eq!(error.kind(), ErrorKind::InvalidArgument);
+
+ let error = default_tuple_sketch_builder()
+ .sampling_probability(f32::INFINITY)
+ .build()
+ .unwrap_err();
+ assert_eq!(error.kind(), ErrorKind::InvalidArgument);
+}
+
#[test]
fn updates_distinct_keys_and_accumulates_summaries() {
- let mut sketch = default_tuple_sketch_builder().build();
+ let mut sketch = default_tuple_sketch_builder().build().unwrap();
sketch.update("shared", 2u64);
sketch.update("shared", 3u64);
sketch.update("other", 7u64);
@@ -47,7 +60,7 @@ fn updates_distinct_keys_and_accumulates_summaries() {
#[test]
fn accepts_supported_hash_representations() {
- let mut sketch = default_tuple_sketch_builder().build();
+ let mut sketch = default_tuple_sketch_builder().build().unwrap();
sketch.update("string", 1u64);
sketch.update(42i64, 1u64);
sketch.update(42u64, 1u64);
@@ -60,7 +73,9 @@ fn accepts_supported_hash_representations() {
#[test]
fn default_update_policy_accepts_distinct_rhs_type() {
- let mut sketch =
TupleSketchBuilder::new(DefaultUpdatePolicy::<String>::default()).build();
+ let mut sketch =
TupleSketchBuilder::new(DefaultUpdatePolicy::<String>::default())
+ .build()
+ .unwrap();
sketch.update("key", "hello");
sketch.update("key", " world");
@@ -94,7 +109,9 @@ where
#[test]
fn custom_update_policy_accepts_multiple_value_representations() {
- let mut sketch = TupleSketchBuilder::new(ArraySumPolicy { num_values: 2
}).build();
+ let mut sketch = TupleSketchBuilder::new(ArraySumPolicy { num_values: 2 })
+ .build()
+ .unwrap();
sketch.update("key", &[1.0, 2.0]);
sketch.update("key", vec![3.0, 4.0]);
@@ -104,7 +121,7 @@ fn
custom_update_policy_accepts_multiple_value_representations() {
#[test]
fn trim_and_reset_update_public_state() {
- let mut sketch = default_tuple_sketch_builder().lg_k(5).build();
+ let mut sketch = default_tuple_sketch_builder().lg_k(5).build().unwrap();
for value in 0..1000 {
sketch.update(value, 1u64);
}
@@ -123,14 +140,14 @@ fn trim_and_reset_update_public_state() {
#[test]
fn bounds_cover_exact_and_estimation_results() {
- let mut exact = default_tuple_sketch_builder().build();
+ let mut exact = default_tuple_sketch_builder().build().unwrap();
for value in 0..100 {
exact.update(value, 1u64);
}
assert_eq!(exact.lower_bound(NumStdDev::One), 100.0);
assert_eq!(exact.upper_bound(NumStdDev::Three), 100.0);
- let mut estimated = default_tuple_sketch_builder().lg_k(8).build();
+ let mut estimated =
default_tuple_sketch_builder().lg_k(8).build().unwrap();
for value in 0..50_000 {
estimated.update(value, 1u64);
}
@@ -151,7 +168,8 @@ fn bounds_cover_exact_and_estimation_results() {
fn empty_sampled_sketch_has_zero_bounds() {
let sketch = default_tuple_sketch_builder()
.sampling_probability(0.1)
- .build();
+ .build()
+ .unwrap();
assert!(sketch.is_empty());
assert!(sketch.is_estimation_mode());
@@ -185,7 +203,7 @@ fn assert_compact_preserves_state(
#[test]
fn compact_preserves_state_in_exact_and_estimation_modes() {
for (lg_k, num_updates, expected_estimation_mode) in [(12, 2_000, false),
(5, 5_000, true)] {
- let mut sketch = default_tuple_sketch_builder().lg_k(lg_k).build();
+ let mut sketch =
default_tuple_sketch_builder().lg_k(lg_k).build().unwrap();
for key in 0..num_updates {
sketch.update(key, key + 1);
sketch.update(key, 10u64);
@@ -205,7 +223,8 @@ fn
compact_preserves_logical_non_empty_after_screened_update() {
.find(|candidate| {
let mut sketch = default_tuple_sketch_builder()
.sampling_probability(0.5)
- .build();
+ .build()
+ .unwrap();
sketch.update(*candidate, 1u64);
!sketch.is_empty() && sketch.num_retained() == 0
})
@@ -213,7 +232,8 @@ fn
compact_preserves_logical_non_empty_after_screened_update() {
let mut sketch = default_tuple_sketch_builder()
.sampling_probability(0.5)
- .build();
+ .build()
+ .unwrap();
sketch.update(screened_value, 1u64);
let compact = sketch.compact(false);
diff --git a/tests-integration/tests/tuple_test/union.rs
b/tests-integration/tests/tuple_test/union.rs
index b521888..87d0b8d 100644
--- a/tests-integration/tests/tuple_test/union.rs
+++ b/tests-integration/tests/tuple_test/union.rs
@@ -33,16 +33,28 @@ fn default_union_builder() ->
TupleUnionBuilder<DefaultUnionPolicy<u64>> {
TupleUnionBuilder::new(DefaultUnionPolicy::<u64>::default())
}
+#[test]
+fn builder_validates_configuration_at_build() {
+ let error = default_union_builder().lg_k(27).build().unwrap_err();
+ assert_eq!(error.kind(), ErrorKind::InvalidArgument);
+
+ let error = default_union_builder()
+ .sampling_probability(-0.1)
+ .build()
+ .unwrap_err();
+ assert_eq!(error.kind(), ErrorKind::InvalidArgument);
+}
+
#[test]
fn union_combines_overlapping_summaries() {
- let mut a = default_tuple_sketch_builder().build();
+ let mut a = default_tuple_sketch_builder().build().unwrap();
a.update("shared", 3u64);
a.update("only_a", 1u64);
- let mut b = default_tuple_sketch_builder().build();
+ let mut b = default_tuple_sketch_builder().build().unwrap();
b.update("shared", 4u64);
b.update("only_b", 1u64);
- let mut union = default_union_builder().build();
+ let mut union = default_union_builder().build().unwrap();
union.update(&a).unwrap();
union.update(&b).unwrap();
let result = union.to_sketch(true);
@@ -58,7 +70,7 @@ fn accepts_mutable_and_compact_inputs() {
let a = tuple_sketch_with_range(0, 500);
let b = tuple_sketch_with_range(250, 500);
- let mut union = default_union_builder().build();
+ let mut union = default_union_builder().build().unwrap();
union.update(&a).unwrap();
union.update(&b.compact(true)).unwrap();
@@ -68,7 +80,7 @@ fn accepts_mutable_and_compact_inputs() {
#[test]
fn reset_restores_the_initial_empty_state() {
let input = tuple_sketch_with_range(0, 100);
- let mut union = default_union_builder().build();
+ let mut union = default_union_builder().build().unwrap();
assert!(union.to_sketch(true).is_empty());
union.update(&input).unwrap();
@@ -82,9 +94,9 @@ fn reset_restores_the_initial_empty_state() {
#[test]
fn non_empty_input_requires_the_union_seed() {
- let mut input = default_tuple_sketch_builder().seed(1).build();
+ let mut input = default_tuple_sketch_builder().seed(1).build().unwrap();
input.update("value", 1u64);
- let mut union = default_union_builder().seed(2).build();
+ let mut union = default_union_builder().seed(2).build().unwrap();
let err = union.update(&input).unwrap_err();
assert_eq!(err.kind(), ErrorKind::InvalidArgument);
@@ -109,12 +121,12 @@ impl SummaryCombinePolicy for MaxPolicy {
#[test]
fn custom_combine_policy_controls_overlapping_summaries() {
- let mut a = default_tuple_sketch_builder().build();
+ let mut a = default_tuple_sketch_builder().build().unwrap();
a.update("shared", 3u64);
- let mut b = default_tuple_sketch_builder().build();
+ let mut b = default_tuple_sketch_builder().build().unwrap();
b.update("shared", 9u64);
- let mut union = TupleUnionBuilder::new(MaxPolicy).build();
+ let mut union = TupleUnionBuilder::new(MaxPolicy).build().unwrap();
union.update(&a).unwrap();
union.update(&b).unwrap();
@@ -124,7 +136,7 @@ fn custom_combine_policy_controls_overlapping_summaries() {
#[test]
fn result_ordering_follows_the_request() {
let input = tuple_sketch_with_range(0, 100);
- let mut union = default_union_builder().build();
+ let mut union = default_union_builder().build().unwrap();
union.update(&input).unwrap();
assert!(union.to_sketch(true).is_ordered());
@@ -133,8 +145,8 @@ fn result_ordering_follows_the_request() {
#[test]
fn estimation_bounds_cover_the_true_union() {
- let mut a = default_tuple_sketch_builder().lg_k(8).build();
- let mut b = default_tuple_sketch_builder().lg_k(8).build();
+ let mut a = default_tuple_sketch_builder().lg_k(8).build().unwrap();
+ let mut b = default_tuple_sketch_builder().lg_k(8).build().unwrap();
for value in 0..50_000 {
a.update(value, 1u64);
}
@@ -142,7 +154,7 @@ fn estimation_bounds_cover_the_true_union() {
b.update(value, 1u64);
}
- let mut union = default_union_builder().lg_k(8).build();
+ let mut union = default_union_builder().lg_k(8).build().unwrap();
union.update(&a).unwrap();
union.update(&b).unwrap();
let result = union.to_sketch(true);
@@ -155,7 +167,7 @@ fn estimation_bounds_cover_the_true_union() {
#[test]
fn union_estimated_size_grows_with_updates() {
- let mut union = default_union_builder().build();
+ let mut union = default_union_builder().build().unwrap();
assert_eq!(union.estimated_size(), 2120);
let sketch = tuple_sketch_with_range(0, 1000);
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]