Skip to content

Commit 6e3efa3

Browse files
committed
feat(tuple): add tuple union operation
1 parent ef68181 commit 6e3efa3

6 files changed

Lines changed: 511 additions & 23 deletions

File tree

datasketches/src/thetacommon/hash_table.rs

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,6 @@ use crate::thetacommon::constants::STRIDE_MASK;
3232
///
3333
/// The entry type supplies the retained hash and any sketch-specific payload. The table owns all
3434
/// theta screening, probing, resizing, rebuilding, trimming, and logical-empty state.
35-
///
36-
/// It maintains an array with capacity up to 2^lg_max_size:
37-
/// * Before it reaches the max capacity, it will extend the array based on resize_factor.
38-
/// * After it reaches the capacity bigger than 2^lg_nom_size, every time the number of entries
39-
/// exceeds the threshold, it will rebuild the table: only keep the min 2^lg_nom_size entries and
40-
/// update the theta to the k-th smallest entry.
4135
#[derive(Debug)]
4236
pub struct RawHashTable<E> {
4337
lg_cur_size: u8,
@@ -132,16 +126,6 @@ where
132126

133127
/// Inserts or updates the entry slot for a pre-hashed key.
134128
///
135-
/// The callback `f` is invoked with the current entry for `hash`:
136-
/// * `Some(existing)` if the key is already retained. The callback should modify it in place;
137-
/// its return value is ignored.
138-
/// * `None` if the key is new. The callback returns `Some(entry)` to insert it, or `None` to
139-
/// decline insertion.
140-
///
141-
/// Using a single callback ensures any captured update value is consumed exactly once, so it
142-
/// works for both the update sketch (folding an update value) and set operations (merging an
143-
/// incoming entry) without requiring the value to be `Copy` or `Clone`.
144-
///
145129
/// Returns true if a new entry was created, false otherwise (existing key, declined insertion,
146130
/// or a hash screened out by theta).
147131
pub fn upsert_entry<F>(&mut self, hash: u64, f: F) -> bool
@@ -302,7 +286,6 @@ where
302286
}
303287

304288
/// Set empty flag.
305-
#[allow(dead_code)] // used by the set operations; tuple set operations arrive in follow-ups
306289
pub fn set_empty(&mut self, is_empty: bool) {
307290
self.is_empty = is_empty;
308291
}

datasketches/src/thetacommon/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
pub(crate) mod binomial_bounds;
2121
pub(crate) mod constants;
2222
pub(crate) mod hash_table;
23-
#[cfg(feature = "theta")]
23+
#[cfg(any(feature = "theta", feature = "tuple"))]
2424
pub(crate) mod union;
2525

2626
/// An entry retained by a Theta sketch family hash table.

datasketches/src/tuple/hash_table.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,11 @@ impl<S> TupleEntry<S> {
5252
pub fn summary(&self) -> &S {
5353
&self.summary
5454
}
55+
56+
/// Returns a mutable reference to the summary stored in this entry.
57+
pub(super) fn summary_mut(&mut self) -> &mut S {
58+
&mut self.summary
59+
}
5560
}
5661

5762
/// Specific hash table for tuple sketch.

datasketches/src/tuple/mod.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@
2222
//! mechanics (theta screening, resize, rebuild to nominal size k) mirror the Theta sketch, with the
2323
//! added requirement that colliding keys merge their summaries.
2424
//!
25-
//! The behavior of a summary (how to create and update it) is supplied externally through
26-
//! policy objects ([`SummaryUpdatePolicy`]) rather than being baked into the summary type.
25+
//! The behavior of a summary (how to create, update, and combine it) is supplied externally through
26+
//! policy objects ([`SummaryUpdatePolicy`] and [`SummaryCombinePolicy`]) rather than being baked
27+
//! into the summary type.
2728
//!
2829
//! # Usage
2930
//!
@@ -39,13 +40,18 @@ mod policy;
3940
mod serde;
4041
mod serialization;
4142
mod sketch;
43+
mod union;
4244

4345
pub use self::hash_table::TupleEntry;
46+
pub use self::policy::DefaultUnionPolicy;
4447
pub use self::policy::DefaultUpdatePolicy;
48+
pub use self::policy::SummaryCombinePolicy;
4549
pub use self::policy::SummaryUpdatePolicy;
4650
pub use self::serde::PrimitiveSummarySerde;
4751
pub use self::serde::SummarySerde;
4852
pub use self::sketch::CompactTupleSketch;
4953
pub use self::sketch::TupleSketch;
5054
pub use self::sketch::TupleSketchBuilder;
5155
pub use self::sketch::TupleSketchView;
56+
pub use self::union::TupleUnion;
57+
pub use self::union::TupleUnionBuilder;

datasketches/src/tuple/policy.rs

Lines changed: 72 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,22 @@
2525
2626
use std::ops::AddAssign;
2727

28+
use crate::thetacommon::union::RawThetaUnionPolicy;
29+
use crate::tuple::hash_table::TupleEntry;
30+
31+
/// Adapts a [`SummaryCombinePolicy`] to the raw union's entry-merge policy.
32+
#[derive(Debug)]
33+
pub(crate) struct CombinePolicyAdapter<P>(pub(crate) P);
34+
35+
impl<S, P> RawThetaUnionPolicy<TupleEntry<S>> for CombinePolicyAdapter<P>
36+
where
37+
P: SummaryCombinePolicy<S>,
38+
{
39+
fn merge(&self, existing: &mut TupleEntry<S>, incoming: TupleEntry<S>) {
40+
self.0.combine(existing.summary_mut(), incoming.summary());
41+
}
42+
}
43+
2844
/// Defines how a summary is created and how update values are folded into it.
2945
///
3046
/// This is used by the update tuple sketch. `S` is the stored summary type and `U` is the type of
@@ -40,11 +56,20 @@ pub trait SummaryUpdatePolicy<S, U> {
4056
fn update(&self, summary: &mut S, value: U);
4157
}
4258

59+
/// Defines how two summaries that share the same key are combined.
60+
///
61+
/// This is used by both union and intersection. Each operator is given its own policy instance,
62+
/// because the two operations may combine summaries differently for the same summary type.
63+
pub trait SummaryCombinePolicy<S> {
64+
/// Combines `other` into `summary` in place.
65+
fn combine(&self, summary: &mut S, other: &S);
66+
}
67+
4368
/// Default update policy for summaries that are default-constructible and additive.
4469
///
45-
/// This is the convenience policy used when no custom policy is supplied, equivalent to C++
46-
/// `default_tuple_update_policy` (which folds updates with `summary += update`). It is available
47-
/// for any summary type `S` and update type `U` where `S: Default + AddAssign<U>`.
70+
/// This is the convenience policy used when no custom policy is supplied; it folds updates with
71+
/// `summary += update`. It is available for any summary type `S` and update type `U` where
72+
/// `S: Default + AddAssign<U>`.
4873
#[derive(Debug, Default, Clone, Copy)]
4974
pub struct DefaultUpdatePolicy;
5075

@@ -61,6 +86,26 @@ where
6186
}
6287
}
6388

89+
/// Default combine policy for additive summaries, used by the union when no custom policy is given.
90+
///
91+
/// It combines two summaries with `summary += other` and is available for any summary type `S`
92+
/// where `S: AddAssign<&S>`.
93+
///
94+
/// There is intentionally no default combine policy for the intersection: how to combine summaries
95+
/// of the keys present in both inputs is application-specific, so the intersection always requires
96+
/// an explicit policy.
97+
#[derive(Debug, Default, Clone, Copy)]
98+
pub struct DefaultUnionPolicy;
99+
100+
impl<S> SummaryCombinePolicy<S> for DefaultUnionPolicy
101+
where
102+
for<'a> S: AddAssign<&'a S>,
103+
{
104+
fn combine(&self, summary: &mut S, other: &S) {
105+
*summary += other;
106+
}
107+
}
108+
64109
#[cfg(test)]
65110
mod tests {
66111
use super::*;
@@ -89,6 +134,12 @@ mod tests {
89134
}
90135
}
91136

137+
impl SummaryCombinePolicy<u64> for MaxPolicy {
138+
fn combine(&self, summary: &mut u64, other: &u64) {
139+
self.update(summary, *other);
140+
}
141+
}
142+
92143
#[test]
93144
fn custom_update_policy_keeps_max() {
94145
let policy = MaxPolicy;
@@ -98,4 +149,22 @@ mod tests {
98149
policy.update(&mut summary, 2);
99150
assert_eq!(summary, 7);
100151
}
152+
153+
#[test]
154+
fn custom_combine_policy_keeps_max() {
155+
let policy = MaxPolicy;
156+
let mut summary = 5u64;
157+
policy.combine(&mut summary, &10);
158+
policy.combine(&mut summary, &7);
159+
assert_eq!(summary, 10);
160+
}
161+
162+
#[test]
163+
fn default_union_policy_combines_additively() {
164+
let policy = DefaultUnionPolicy;
165+
let mut summary = 5u64;
166+
policy.combine(&mut summary, &10);
167+
policy.combine(&mut summary, &7);
168+
assert_eq!(summary, 22);
169+
}
101170
}

0 commit comments

Comments
 (0)