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 9b695cb feat: return Option from intersection to_sketch before first
update (#211)
9b695cb is described below
commit 9b695cb701f5df233569ead1112ad650ae3885ad
Author: Vedant Madane <[email protected]>
AuthorDate: Tue Aug 25 07:03:48 2026 +0530
feat: return Option from intersection to_sketch before first update (#211)
Signed-off-by: Vedant Madane <[email protected]>
Co-authored-by: tison <[email protected]>
---
datasketches/src/thetafamily/theta/intersection.rs | 27 ++++++-------
datasketches/src/thetafamily/tuple/intersection.rs | 31 ++++++++-------
datasketches/tests/theta_test/intersection.rs | 45 ++++++++++------------
datasketches/tests/tuple_test/intersection.rs | 21 +++++-----
4 files changed, 60 insertions(+), 64 deletions(-)
diff --git a/datasketches/src/thetafamily/theta/intersection.rs
b/datasketches/src/thetafamily/theta/intersection.rs
index 3f2f0f8..5186e18 100644
--- a/datasketches/src/thetafamily/theta/intersection.rs
+++ b/datasketches/src/thetafamily/theta/intersection.rs
@@ -25,8 +25,9 @@ use crate::thetacommon::intersection::IntersectionState;
/// Stateful intersection operator for Theta sketches.
///
-/// Before the first [`update`](Self::update), the result is undefined; use
-/// [`has_result`](Self::has_result) to check.
+/// A newly created operator has no result. [`has_result`](Self::has_result)
returns `false` and
+/// [`to_sketch`](Self::to_sketch) returns `None` until the first successful
+/// [`update`](Self::update).
#[derive(Debug)]
pub struct ThetaIntersection {
state: IntersectionState<ThetaEntry, NoopIntersectionPolicy>,
@@ -63,7 +64,7 @@ impl ThetaIntersection {
self.state.update(sketch)
}
- /// Returns whether this operator has received at least one update.
+ /// Returns `true` after the first successful [`update`](Self::update).
pub fn has_result(&self) -> bool {
self.state.has_result()
}
@@ -73,18 +74,18 @@ impl ThetaIntersection {
size_of::<Self>() + self.state.estimated_size()
}
- /// Returns the intersection result as a compact theta sketch.
+ /// Returns the current intersection as a compact theta sketch.
///
- /// # Panics
+ /// Returns `None` until the first successful [`update`](Self::update).
After that, returns
+ /// `Some` even when the intersection is empty.
///
- /// Panics if called before the first [`update`](Self::update).
- pub fn to_sketch(&self, ordered: bool) -> CompactThetaSketch {
- assert!(
- self.state.has_result(),
- "ThetaIntersection::to_sketch() called before first update()"
- );
+ /// If `ordered` is `true`, retained hashes are sorted in ascending order.
+ pub fn to_sketch(&self, ordered: bool) -> Option<CompactThetaSketch> {
+ if !self.state.has_result() {
+ return None;
+ }
let parts = self.state.to_compact_parts(ordered);
- CompactThetaSketch::from_parts(
+ Some(CompactThetaSketch::from_parts(
parts
.entries
.into_iter()
@@ -94,6 +95,6 @@ impl ThetaIntersection {
parts.seed_hash,
parts.ordered,
parts.empty,
- )
+ ))
}
}
diff --git a/datasketches/src/thetafamily/tuple/intersection.rs
b/datasketches/src/thetafamily/tuple/intersection.rs
index a93712c..c337f33 100644
--- a/datasketches/src/thetafamily/tuple/intersection.rs
+++ b/datasketches/src/thetafamily/tuple/intersection.rs
@@ -38,8 +38,9 @@ use crate::tuple::sketch::TupleSketchView;
/// `P` is the [`SummaryCombinePolicy`] applied to keys present in more than
one input. There is no
/// default policy (see the module docs), so one must be supplied at
construction.
///
-/// Before the first [`update`](Self::update), the result is undefined; use
-/// [`has_result`](Self::has_result) to check.
+/// A newly created operator has no result. [`has_result`](Self::has_result)
returns `false` and
+/// [`to_sketch`](Self::to_sketch) returns `None` until the first successful
+/// [`update`](Self::update).
///
/// # Examples
///
@@ -79,7 +80,7 @@ use crate::tuple::sketch::TupleSketchView;
/// intersection.update(&a).unwrap();
/// intersection.update(&b).unwrap();
///
-/// let result = intersection.to_sketch(true);
+/// let result = intersection.to_sketch(true).unwrap();
/// assert_eq!(result.num_retained(), 1); // only "shared"
/// assert_eq!(result.iter().next().unwrap().1, &7); // 3 + 4
/// ```
@@ -128,7 +129,7 @@ where
self.state.update(sketch)
}
- /// Returns whether this operator has received at least one update.
+ /// Returns `true` after the first successful [`update`](Self::update).
pub fn has_result(&self) -> bool {
self.state.has_result()
}
@@ -138,28 +139,26 @@ where
size_of::<Self>() + self.state.estimated_size()
}
- /// Returns the intersection result as a compact Tuple sketch.
+ /// Returns the current intersection as a compact Tuple sketch.
///
- /// If `ordered` is `true`, retained entries are sorted ascending by hash.
+ /// Returns `None` until the first successful [`update`](Self::update).
After that, returns
+ /// `Some` even when the intersection is empty.
///
- /// # Panics
- ///
- /// Panics if called before the first [`update`](Self::update).
- pub fn to_sketch(&self, ordered: bool) -> CompactTupleSketch<P::Summary>
+ /// If `ordered` is `true`, retained entries are sorted in ascending hash
order.
+ pub fn to_sketch(&self, ordered: bool) ->
Option<CompactTupleSketch<P::Summary>>
where
P::Summary: Clone,
{
- assert!(
- self.state.has_result(),
- "TupleIntersection::to_sketch() called before first update()"
- );
+ if !self.state.has_result() {
+ return None;
+ }
let parts = self.state.to_compact_parts(ordered);
- CompactTupleSketch::from_parts(
+ Some(CompactTupleSketch::from_parts(
parts.entries,
parts.theta,
parts.seed_hash,
parts.ordered,
parts.empty,
- )
+ ))
}
}
diff --git a/datasketches/tests/theta_test/intersection.rs
b/datasketches/tests/theta_test/intersection.rs
index 65f9d70..6e9a103 100644
--- a/datasketches/tests/theta_test/intersection.rs
+++ b/datasketches/tests/theta_test/intersection.rs
@@ -37,16 +37,13 @@ fn test_has_result_state_machine() {
assert!(!i.has_result());
i.update(&a).unwrap();
assert!(i.has_result());
- assert!(i.to_sketch(true).estimate() >= 1.0);
+ assert!(i.to_sketch(true).unwrap().estimate() >= 1.0);
}
#[test]
-fn test_result_before_update_panics() {
+fn test_result_before_first_update_returns_none() {
let i = ThetaIntersection::with_seed(123);
- let result = std::panic::catch_unwind(|| {
- i.to_sketch(true);
- });
- assert!(result.is_err());
+ assert!(i.to_sketch(true).is_none());
}
#[test]
@@ -63,7 +60,7 @@ fn test_update_accepts_compact_sketch() {
i.update(&a.compact(true)).unwrap();
i.update(&b).unwrap();
- let r = i.to_sketch(true);
+ let r = i.to_sketch(true).unwrap();
assert!(r.estimate() == 1.0);
assert!(r.is_ordered());
@@ -74,7 +71,7 @@ fn test_update_accepts_compact_sketch() {
i.update(&c.compact(false)).unwrap();
- let r = i.to_sketch(false);
+ let r = i.to_sketch(false).unwrap();
assert!(r.estimate() == 0.0);
assert!(!r.is_ordered());
}
@@ -86,7 +83,7 @@ fn test_seed_mismatch_behaviour_for_empty_sketch() {
i.update(&empty_other_seed).unwrap();
assert!(i.has_result());
- let r = i.to_sketch(true);
+ let r = i.to_sketch(true).unwrap();
assert!(r.is_empty());
}
@@ -110,7 +107,7 @@ fn test_terminal_empty_state_ignores_future_updates() {
i.update(&empty).unwrap();
i.update(&non_empty).unwrap();
- let r = i.to_sketch(true);
+ let r = i.to_sketch(true).unwrap();
assert!(r.is_empty());
}
@@ -123,7 +120,7 @@ fn test_to_sketch_unordered_is_not_ordered() {
let mut i = ThetaIntersection::default();
i.update(&a).unwrap();
- let r = i.to_sketch(false);
+ let r = i.to_sketch(false).unwrap();
assert!(!r.is_ordered());
}
@@ -133,14 +130,14 @@ fn test_empty_update_twice() {
let mut i = ThetaIntersection::default();
i.update(&empty).unwrap();
- let r1 = i.to_sketch(true);
+ let r1 = i.to_sketch(true).unwrap();
assert_eq!(r1.num_retained(), 0);
assert!(r1.is_empty());
assert!(!r1.is_estimation_mode());
assert_eq!(r1.estimate(), 0.0);
i.update(&empty).unwrap();
- let r2 = i.to_sketch(true);
+ let r2 = i.to_sketch(true).unwrap();
assert_eq!(r2.num_retained(), 0);
assert!(r2.is_empty());
assert!(!r2.is_estimation_mode());
@@ -156,7 +153,7 @@ fn test_non_empty_no_retained_keys() {
let mut i = ThetaIntersection::default();
i.update(&s).unwrap();
- let r1 = i.to_sketch(true);
+ let r1 = i.to_sketch(true).unwrap();
assert_eq!(r1.num_retained(), 0);
assert!(!r1.is_empty());
assert!(r1.is_estimation_mode());
@@ -164,7 +161,7 @@ fn test_non_empty_no_retained_keys() {
assert_eq!(r1.estimate(), 0.0);
i.update(&s).unwrap();
- let r2 = i.to_sketch(true);
+ let r2 = i.to_sketch(true).unwrap();
assert_eq!(r2.num_retained(), 0);
assert!(!r2.is_empty());
assert!(r2.is_estimation_mode());
@@ -180,7 +177,7 @@ fn test_exact_half_overlap_unordered() {
let mut i = ThetaIntersection::default();
i.update(&s1).unwrap();
i.update(&s2).unwrap();
- let r = i.to_sketch(true);
+ let r = i.to_sketch(true).unwrap();
assert!(!r.is_empty());
assert!(!r.is_estimation_mode());
@@ -195,7 +192,7 @@ fn test_exact_half_overlap_ordered() {
let mut i = ThetaIntersection::default();
i.update(&s1.compact(true)).unwrap();
i.update(&s2.compact(true)).unwrap();
- let r = i.to_sketch(true);
+ let r = i.to_sketch(true).unwrap();
assert!(!r.is_empty());
assert!(!r.is_estimation_mode());
@@ -210,7 +207,7 @@ fn test_exact_disjoint_unordered() {
let mut i = ThetaIntersection::default();
i.update(&s1).unwrap();
i.update(&s2).unwrap();
- let r = i.to_sketch(true);
+ let r = i.to_sketch(true).unwrap();
assert!(r.is_empty());
assert!(!r.is_estimation_mode());
@@ -225,7 +222,7 @@ fn test_exact_disjoint_ordered() {
let mut i = ThetaIntersection::default();
i.update(&s1.compact(true)).unwrap();
i.update(&s2.compact(true)).unwrap();
- let r = i.to_sketch(true);
+ let r = i.to_sketch(true).unwrap();
assert!(r.is_empty());
assert!(!r.is_estimation_mode());
@@ -240,7 +237,7 @@ fn test_estimation_half_overlap_unordered() {
let mut i = ThetaIntersection::default();
i.update(&s1).unwrap();
i.update(&s2).unwrap();
- let r = i.to_sketch(true);
+ let r = i.to_sketch(true).unwrap();
assert!(!r.is_empty());
assert!(r.is_estimation_mode());
@@ -255,7 +252,7 @@ fn test_estimation_half_overlap_ordered() {
let mut i = ThetaIntersection::default();
i.update(&s1.compact(true)).unwrap();
i.update(&s2.compact(true)).unwrap();
- let r = i.to_sketch(true);
+ let r = i.to_sketch(true).unwrap();
assert!(!r.is_empty());
assert!(r.is_estimation_mode());
@@ -272,7 +269,7 @@ fn
test_estimation_half_overlap_ordered_deserialized_compact() {
let mut i = ThetaIntersection::default();
i.update(&c1).unwrap();
i.update(&c2).unwrap();
- let r = i.to_sketch(true);
+ let r = i.to_sketch(true).unwrap();
assert!(!r.is_empty());
assert!(r.is_estimation_mode());
@@ -287,7 +284,7 @@ fn test_estimation_disjoint_unordered() {
let mut i = ThetaIntersection::default();
i.update(&s1).unwrap();
i.update(&s2).unwrap();
- let r = i.to_sketch(true);
+ let r = i.to_sketch(true).unwrap();
assert!(!r.is_empty());
assert!(r.is_estimation_mode());
@@ -302,7 +299,7 @@ fn test_estimation_disjoint_ordered() {
let mut i = ThetaIntersection::default();
i.update(&s1.compact(true)).unwrap();
i.update(&s2.compact(true)).unwrap();
- let r = i.to_sketch(true);
+ let r = i.to_sketch(true).unwrap();
assert!(!r.is_empty());
assert!(r.is_estimation_mode());
diff --git a/datasketches/tests/tuple_test/intersection.rs
b/datasketches/tests/tuple_test/intersection.rs
index ea6e5af..9dd181f 100644
--- a/datasketches/tests/tuple_test/intersection.rs
+++ b/datasketches/tests/tuple_test/intersection.rs
@@ -49,14 +49,13 @@ fn has_result_tracks_the_first_update() {
assert!(!intersection.has_result());
intersection.update(&sketch).unwrap();
assert!(intersection.has_result());
- assert_eq!(intersection.to_sketch(true).num_retained(), 10);
+ assert_eq!(intersection.to_sketch(true).unwrap().num_retained(), 10);
}
#[test]
-#[should_panic(expected = "before first update")]
-fn result_before_first_update_panics() {
+fn result_before_first_update_returns_none() {
let intersection = TupleIntersection::new(SumPolicy);
- let _ = intersection.to_sketch(true);
+ assert!(intersection.to_sketch(true).is_none());
}
#[test]
@@ -71,7 +70,7 @@ fn overlap_combines_summaries() {
let mut intersection = TupleIntersection::new(SumPolicy);
intersection.update(&a).unwrap();
intersection.update(&b).unwrap();
- let result = intersection.to_sketch(true);
+ let result = intersection.to_sketch(true).unwrap();
assert_eq!(result.num_retained(), 1);
assert_eq!(result.iter().next().unwrap().1, &7);
@@ -86,7 +85,7 @@ fn accepts_mutable_and_compact_inputs() {
intersection.update(&a).unwrap();
intersection.update(&b.compact(true)).unwrap();
- assert_eq!(intersection.to_sketch(true).num_retained(), 500);
+ assert_eq!(intersection.to_sketch(true).unwrap().num_retained(), 500);
}
#[test]
@@ -100,7 +99,7 @@ fn disjoint_result_is_terminally_empty() {
intersection.update(&b).unwrap();
intersection.update(&later).unwrap();
- let result = intersection.to_sketch(true);
+ let result = intersection.to_sketch(true).unwrap();
assert!(result.is_empty());
assert_eq!(result.num_retained(), 0);
}
@@ -124,7 +123,7 @@ fn
logically_non_empty_input_without_retained_entries_is_preserved() {
let mut intersection = TupleIntersection::new(SumPolicy);
intersection.update(&sketch).unwrap();
- let result = intersection.to_sketch(true);
+ let result = intersection.to_sketch(true).unwrap();
assert!(!result.is_empty());
assert_eq!(result.num_retained(), 0);
@@ -151,8 +150,8 @@ fn result_ordering_follows_the_request() {
let mut intersection = TupleIntersection::new(SumPolicy);
intersection.update(&input).unwrap();
- assert!(intersection.to_sketch(true).is_ordered());
- assert!(!intersection.to_sketch(false).is_ordered());
+ assert!(intersection.to_sketch(true).unwrap().is_ordered());
+ assert!(!intersection.to_sketch(false).unwrap().is_ordered());
}
#[test]
@@ -169,7 +168,7 @@ fn estimation_bounds_cover_the_true_intersection() {
let mut intersection = TupleIntersection::new(SumPolicy);
intersection.update(&a).unwrap();
intersection.update(&b).unwrap();
- let result = intersection.to_sketch(true);
+ let result = intersection.to_sketch(true).unwrap();
let lower = result.lower_bound(NumStdDev::Three);
let upper = result.upper_bound(NumStdDev::Three);
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]