Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,6 @@

# Build artifacts
**/target

# Generated files
*.sk
10 changes: 6 additions & 4 deletions datasketches/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,14 @@ pub mod frequencies;
pub mod hll;
#[cfg(feature = "tdigest")]
pub mod tdigest;
#[cfg(feature = "theta")]
pub mod theta;
#[cfg(any(feature = "theta", feature = "tuple"))]
pub mod thetacommon;
mod theta_family;
#[cfg(any(feature = "theta", feature = "tuple"))]
pub use self::theta_family::common as thetacommon;
#[cfg(feature = "theta")]
pub use self::theta_family::theta;
#[cfg(feature = "tuple")]
pub mod tuple;
pub use self::theta_family::tuple;

// common modules
pub mod codec;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use crate::error::Error;
use crate::hash::compute_seed_hash;
use crate::thetacommon::RetainedEntry;
use crate::thetacommon::ThetaFamilySketchView;
use crate::thetacommon::ThetaKeySketchView;
use crate::thetacommon::constants::MAX_THETA;
use crate::thetacommon::hash_table::CompactSketchParts;

Expand Down Expand Up @@ -60,7 +61,7 @@ impl ANotBOperator {
) -> Result<CompactSketchParts<A::Entry>, Error>
where
A: ThetaFamilySketchView,
B: ThetaFamilySketchView,
B: ThetaKeySketchView,
{
// If A is empty the result is an (empty) copy of A. As with the union and intersection, an
// empty input carries no keys, so its seed is not validated.
Expand Down Expand Up @@ -104,7 +105,7 @@ impl ANotBOperator {
// Both inputs are sorted ascending by hash: merge-scan without a hash set. Only
// B hashes below theta can exclude an A entry (A entries are all < theta), so
// unexamined B entries at or above theta are harmless.
let mut b_hashes = b.iter().map(|entry| entry.hash()).peekable();
let mut b_hashes = b.iter_hashes().peekable();
let mut entries = vec![];
for entry in a.iter() {
let hash = entry.hash();
Expand All @@ -125,8 +126,7 @@ impl ANotBOperator {
entries
} else {
let mut b_keys: HashSet<u64> = HashSet::with_capacity(b.num_retained());
for entry in b.iter() {
let hash = entry.hash();
for hash in b.iter_hashes() {
if hash < theta {
b_keys.insert(hash);
} else if b.is_ordered() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ where
mod tests {
use super::*;
use crate::hash::DEFAULT_UPDATE_SEED;
use crate::thetacommon::ThetaKeySketchView;

#[derive(Clone, Debug, Eq, PartialEq)]
struct TestEntry {
Expand Down Expand Up @@ -280,9 +281,7 @@ mod tests {
}
}

impl ThetaFamilySketchView for TestSketch {
type Entry = TestEntry;

impl ThetaKeySketchView for TestSketch {
fn seed_hash(&self) -> u16 {
crate::hash::compute_seed_hash(DEFAULT_UPDATE_SEED)
}
Expand All @@ -299,15 +298,23 @@ mod tests {
false
}

fn iter(&self) -> impl Iterator<Item = TestEntry> + '_ {
self.entries.iter().cloned()
fn iter_hashes(&self) -> impl Iterator<Item = u64> + '_ {
self.entries.iter().map(RetainedEntry::hash)
}

fn num_retained(&self) -> usize {
self.entries.len()
}
}

impl ThetaFamilySketchView for TestSketch {
type Entry = TestEntry;

fn iter(&self) -> impl Iterator<Item = TestEntry> + '_ {
self.entries.iter().cloned()
}
}

struct SumPolicy;

impl IntersectionMergePolicy<TestEntry> for SumPolicy {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use crate::error::Error;
use crate::hash::compute_seed_hash;
use crate::thetacommon::RetainedEntry;
use crate::thetacommon::ThetaFamilySketchView;
use crate::thetacommon::ThetaKeySketchView;
use crate::thetacommon::bounds_binomial_proportions;
use crate::thetacommon::constants::MAX_LG_K;
use crate::thetacommon::constants::MAX_THETA;
Expand Down Expand Up @@ -127,9 +128,7 @@ impl<'a, S> KeySketchView<'a, S> {
}
}

impl<S: ThetaFamilySketchView> ThetaFamilySketchView for KeySketchView<'_, S> {
type Entry = KeyEntry;

impl<S: ThetaKeySketchView> ThetaKeySketchView for KeySketchView<'_, S> {
fn seed_hash(&self) -> u16 {
self.sketch.seed_hash()
}
Expand All @@ -146,15 +145,23 @@ impl<S: ThetaFamilySketchView> ThetaFamilySketchView for KeySketchView<'_, S> {
self.sketch.is_ordered()
}

fn iter(&self) -> impl Iterator<Item = KeyEntry> + '_ {
self.sketch.iter_hashes().map(|hash| KeyEntry { hash })
fn iter_hashes(&self) -> impl Iterator<Item = u64> + '_ {
self.sketch.iter_hashes()
}

fn num_retained(&self) -> usize {
self.sketch.num_retained()
}
}

impl<S: ThetaKeySketchView> ThetaFamilySketchView for KeySketchView<'_, S> {
type Entry = KeyEntry;

fn iter(&self) -> impl Iterator<Item = KeyEntry> + '_ {
self.sketch.iter_hashes().map(|hash| KeyEntry { hash })
}
}

#[derive(Clone, Copy, Debug)]
struct NoopMergePolicy;

Expand All @@ -174,9 +181,7 @@ struct CompactKeySketchView {
empty: bool,
}

impl ThetaFamilySketchView for CompactKeySketchView {
type Entry = KeyEntry;

impl ThetaKeySketchView for CompactKeySketchView {
fn seed_hash(&self) -> u16 {
self.seed_hash
}
Expand All @@ -193,15 +198,23 @@ impl ThetaFamilySketchView for CompactKeySketchView {
self.ordered
}

fn iter(&self) -> impl Iterator<Item = KeyEntry> + '_ {
self.entries.iter().copied()
fn iter_hashes(&self) -> impl Iterator<Item = u64> + '_ {
self.entries.iter().map(RetainedEntry::hash)
}

fn num_retained(&self) -> usize {
self.entries.len()
}
}

impl ThetaFamilySketchView for CompactKeySketchView {
type Entry = KeyEntry;

fn iter(&self) -> impl Iterator<Item = KeyEntry> + '_ {
self.entries.iter().copied()
}
}

/// Configured Jaccard operator shared by Theta and Tuple public wrappers.
#[derive(Clone, Copy, Debug)]
pub(crate) struct JaccardSimilarityOperator {
Expand All @@ -219,8 +232,8 @@ impl JaccardSimilarityOperator {
sketch_b: &B,
) -> Result<JaccardSimilarity, Error>
where
A: ThetaFamilySketchView,
B: ThetaFamilySketchView,
A: ThetaKeySketchView,
B: ThetaKeySketchView,
{
if sketch_a.is_empty() && sketch_b.is_empty() {
return Ok(JaccardSimilarity::exact(1.0));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,11 @@ pub trait RetainedEntry {
fn hash(&self) -> u64;
}

/// Read-only input accepted by Theta-family set operations.
/// Read-only hash-key view shared by Theta-family sketches.
///
/// This trait carries complete retained entries, so Tuple union, intersection, and A-not-B
/// operations can share the Theta-family state machines while preserving per-key summaries.
pub trait ThetaFamilySketchView {
/// The retained entry representation yielded by this view.
type Entry: RetainedEntry;

/// Key-only operations use this interface without requiring access to, or cloning, payloads such
/// as Tuple summaries.
pub trait ThetaKeySketchView {
/// Return the 16-bit seed hash.
fn seed_hash(&self) -> u16;

Expand All @@ -54,16 +51,21 @@ pub trait ThetaFamilySketchView {
/// Return whether retained entries are ordered by ascending hash.
fn is_ordered(&self) -> bool;

/// Return an iterator over retained entries.
fn iter(&self) -> impl Iterator<Item = Self::Entry> + '_;

/// Return an iterator over retained hash keys without requiring callers to inspect payloads.
///
/// Tuple sketches override this method so key-only operations do not clone summary values.
fn iter_hashes(&self) -> impl Iterator<Item = u64> + '_ {
self.iter().map(|entry| entry.hash())
}
/// Return an iterator over retained hash keys.
fn iter_hashes(&self) -> impl Iterator<Item = u64> + '_;

/// Return the number of retained entries.
fn num_retained(&self) -> usize;
}

/// Read-only retained-entry view accepted by Theta-family set operations.
///
/// This trait extends [`ThetaKeySketchView`] with complete retained entries, so operations such as
/// union and intersection can preserve and combine Tuple summaries.
pub trait ThetaFamilySketchView: ThetaKeySketchView {
/// The retained entry representation yielded by this view.
type Entry: RetainedEntry;

/// Return an iterator over retained entries.
fn iter(&self) -> impl Iterator<Item = Self::Entry> + '_;
}
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ where
mod tests {
use super::*;
use crate::hash::DEFAULT_UPDATE_SEED;
use crate::thetacommon::ThetaKeySketchView;

#[derive(Clone, Debug, Eq, PartialEq)]
struct TestEntry {
Expand All @@ -175,9 +176,7 @@ mod tests {
entries: Vec<TestEntry>,
}

impl ThetaFamilySketchView for TestSketch {
type Entry = TestEntry;

impl ThetaKeySketchView for TestSketch {
fn seed_hash(&self) -> u16 {
crate::hash::compute_seed_hash(DEFAULT_UPDATE_SEED)
}
Expand All @@ -194,15 +193,23 @@ mod tests {
false
}

fn iter(&self) -> impl Iterator<Item = TestEntry> + '_ {
self.entries.iter().cloned()
fn iter_hashes(&self) -> impl Iterator<Item = u64> + '_ {
self.entries.iter().map(RetainedEntry::hash)
}

fn num_retained(&self) -> usize {
self.entries.len()
}
}

impl ThetaFamilySketchView for TestSketch {
type Entry = TestEntry;

fn iter(&self) -> impl Iterator<Item = TestEntry> + '_ {
self.entries.iter().cloned()
}
}

struct SumPolicy;

impl UnionMergePolicy<TestEntry> for SumPolicy {
Expand Down
22 changes: 22 additions & 0 deletions datasketches/src/theta_family/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

pub mod common;
#[cfg(feature = "theta")]
pub mod theta;
#[cfg(feature = "tuple")]
pub mod tuple;
Loading