Skip to main content

CompactThetaSketch

Struct CompactThetaSketch 

Source
pub struct CompactThetaSketch { /* private fields */ }
Expand description

An immutable, serializable snapshot of a theta sketch. Produced by super::ThetaSketch::compact, by any set operation’s result (ThetaUnion::get_result, ThetaIntersection::get_result, ThetaAnotB::compute), or by Self::deserialize.

Implementations§

Source§

impl CompactThetaSketch

Source

pub fn deserialize(bytes: &[u8]) -> Result<Self, SketchError>

Deserializes v1/v2/v3 (uncompressed) bytes. Upstream’s deserialize() auto-detects the serial version transparently, including v4 (compressed) — see Self::deserialize_compressed, which calls the exact same underlying routine; the two Rust names exist purely for call-site symmetry with serialize_compact/serialize_compressed.

Examples found in repository?
examples/bench_theta_update.rs (line 339)
314fn bench_serde(items: u64, reps: usize) {
315    let mut update_sketch = build();
316    for key in 0..items {
317        update_sketch.update_u64(key);
318    }
319    let sketch = update_sketch.compact(true);
320    let reference = sketch.serialize_compact();
321
322    let mut passes = Vec::with_capacity(reps);
323    for _ in 0..reps {
324        let start = Instant::now();
325        let mut total = 0usize;
326        for _ in 0..SER_CALLS {
327            total += black_box(sketch.serialize_compact()).len();
328        }
329        let elapsed = start.elapsed();
330        black_box(total);
331        passes.push(Pass {
332            elapsed,
333            estimate: sketch.get_estimate(),
334        });
335    }
336    report_bytes("ser", items, SER_CALLS, &passes, reference.len());
337
338    let deserialize =
339        || CompactThetaSketch::deserialize(&reference).expect("the bytes came from serialize");
340    let mut passes = Vec::with_capacity(reps);
341    for _ in 0..reps {
342        let start = Instant::now();
343        let mut total = 0.0;
344        for _ in 0..DESER_CALLS {
345            total += deserialize().get_estimate();
346        }
347        let elapsed = start.elapsed();
348        black_box(total);
349        passes.push(Pass {
350            elapsed,
351            estimate: deserialize().get_estimate(),
352        });
353    }
354    report_bytes("deser", items, DESER_CALLS, &passes, reference.len());
355}
More examples
Hide additional examples
examples/theta.rs (line 73)
11fn main() {
12    // Build two sketches representing two overlapping sets of user IDs.
13    let mut visitors_day1 = ThetaSketchBuilder::new().lg_k(12).build().unwrap();
14    for id in 0..10_000u64 {
15        visitors_day1.update_u64(id);
16    }
17
18    let mut visitors_day2 = ThetaSketchBuilder::new().lg_k(12).build().unwrap();
19    for id in 5_000..15_000u64 {
20        visitors_day2.update_u64(id);
21    }
22
23    println!(
24        "Day 1 unique visitors (estimate): {:.0}",
25        visitors_day1.get_estimate()
26    );
27    println!(
28        "Day 2 unique visitors (estimate): {:.0}",
29        visitors_day2.get_estimate()
30    );
31
32    // Union: total unique visitors across both days.
33    let mut union = ThetaUnionBuilder::new().lg_k(12).build().unwrap();
34    union.update(&visitors_day1);
35    union.update(&visitors_day2);
36    let total_unique = union.get_result(true);
37    println!(
38        "Total unique visitors (union estimate): {:.0}",
39        total_unique.get_estimate()
40    );
41
42    // Intersection: visitors who came back on day 2.
43    let mut intersection = ThetaIntersection::new();
44    intersection.update(&visitors_day1);
45    intersection.update(&visitors_day2);
46    match intersection.get_result(true) {
47        Ok(returning) => println!(
48            "Returning visitors (intersection estimate): {:.0}",
49            returning.get_estimate()
50        ),
51        Err(e) => println!("No intersection result: {e}"),
52    }
53
54    // A-not-b: visitors who only came on day 1.
55    let a_not_b = ThetaAnotB::new();
56    let day1_only = a_not_b.compute(&visitors_day1, &visitors_day2, true);
57    println!(
58        "Day-1-only visitors (a-not-b estimate): {:.0}",
59        day1_only.get_estimate()
60    );
61
62    // Jaccard similarity: how similar are the two days' visitor sets?
63    let similarity = jaccard_similarity(&visitors_day1, &visitors_day2);
64    println!(
65        "Jaccard similarity: {:.3} (range [{:.3}, {:.3}])",
66        similarity.estimate, similarity.lower_bound, similarity.upper_bound
67    );
68
69    // Serialize a compact sketch for storage/transmission, then restore it.
70    let compact = visitors_day1.compact(true);
71    let bytes = compact.serialize_compact();
72    println!("Serialized day-1 sketch: {} bytes", bytes.len());
73    let restored = apache_datasketches::theta::CompactThetaSketch::deserialize(&bytes).unwrap();
74    println!("Restored estimate: {:.0}", restored.get_estimate());
75}
Source

pub fn deserialize_compressed(bytes: &[u8]) -> Result<Self, SketchError>

Deserializes v4 (compressed) bytes. See Self::deserialize — both methods call the same upstream auto-detecting deserialize().

Source

pub fn get_estimate(&self) -> f64

Returns the current estimate of the number of distinct items in this sketch.

Examples found in repository?
examples/bench_theta_update.rs (line 333)
314fn bench_serde(items: u64, reps: usize) {
315    let mut update_sketch = build();
316    for key in 0..items {
317        update_sketch.update_u64(key);
318    }
319    let sketch = update_sketch.compact(true);
320    let reference = sketch.serialize_compact();
321
322    let mut passes = Vec::with_capacity(reps);
323    for _ in 0..reps {
324        let start = Instant::now();
325        let mut total = 0usize;
326        for _ in 0..SER_CALLS {
327            total += black_box(sketch.serialize_compact()).len();
328        }
329        let elapsed = start.elapsed();
330        black_box(total);
331        passes.push(Pass {
332            elapsed,
333            estimate: sketch.get_estimate(),
334        });
335    }
336    report_bytes("ser", items, SER_CALLS, &passes, reference.len());
337
338    let deserialize =
339        || CompactThetaSketch::deserialize(&reference).expect("the bytes came from serialize");
340    let mut passes = Vec::with_capacity(reps);
341    for _ in 0..reps {
342        let start = Instant::now();
343        let mut total = 0.0;
344        for _ in 0..DESER_CALLS {
345            total += deserialize().get_estimate();
346        }
347        let elapsed = start.elapsed();
348        black_box(total);
349        passes.push(Pass {
350            elapsed,
351            estimate: deserialize().get_estimate(),
352        });
353    }
354    report_bytes("deser", items, DESER_CALLS, &passes, reference.len());
355}
356
357/// Two operands with 50% overlap, built once outside every timed region: the
358/// operand-construction cost belongs to the setup, not to the union/
359/// intersection/jaccard call being measured.
360fn build_operands(items: u64) -> (CompactThetaSketch, CompactThetaSketch) {
361    let mut a = build();
362    for key in 0..items {
363        a.update_u64(key);
364    }
365    let mut b = build();
366    for key in (items / 2)..(items + items / 2) {
367        b.update_u64(key);
368    }
369    (a.compact(true), b.compact(true))
370}
371
372/// A fresh union is built inside the timed loop, so the figure is
373/// construct + two updates + get_result, not the merge alone -- reusing one
374/// accumulator across `OP_CALLS` iterations would have each iteration merge
375/// into an ever-growing result, measuring a different workload every time.
376fn bench_union(items: u64, reps: usize) {
377    let (a, b) = build_operands(items);
378    let mut passes = Vec::with_capacity(reps);
379    for _ in 0..reps {
380        let start = Instant::now();
381        let mut total = 0.0;
382        let mut estimate = 0.0;
383        for _ in 0..OP_CALLS {
384            let mut union = ThetaUnionBuilder::new()
385                .lg_k(LG_K)
386                .build()
387                .expect("fixed valid parameters were rejected");
388            union.update(&a);
389            union.update(&b);
390            estimate = union.get_result(true).get_estimate();
391            total += estimate;
392        }
393        let elapsed = start.elapsed();
394        black_box(total);
395        passes.push(Pass { elapsed, estimate });
396    }
397    report_line("union", items, OP_CALLS, &passes, String::new());
398}
399
400/// As [`bench_union`]: a fresh intersection per iteration, so the figure is
401/// construct + two updates + get_result.
402fn bench_intersect(items: u64, reps: usize) {
403    let (a, b) = build_operands(items);
404    let mut passes = Vec::with_capacity(reps);
405    for _ in 0..reps {
406        let start = Instant::now();
407        let mut total = 0.0;
408        let mut estimate = 0.0;
409        for _ in 0..OP_CALLS {
410            let mut intersection = ThetaIntersection::new();
411            intersection.update(&a);
412            intersection.update(&b);
413            estimate = intersection
414                .get_result(true)
415                .expect("both operands were non-empty")
416                .get_estimate();
417            total += estimate;
418        }
419        let elapsed = start.elapsed();
420        black_box(total);
421        passes.push(Pass { elapsed, estimate });
422    }
423    report_line("intersect", items, OP_CALLS, &passes, String::new());
424}
More examples
Hide additional examples
examples/theta.rs (line 39)
11fn main() {
12    // Build two sketches representing two overlapping sets of user IDs.
13    let mut visitors_day1 = ThetaSketchBuilder::new().lg_k(12).build().unwrap();
14    for id in 0..10_000u64 {
15        visitors_day1.update_u64(id);
16    }
17
18    let mut visitors_day2 = ThetaSketchBuilder::new().lg_k(12).build().unwrap();
19    for id in 5_000..15_000u64 {
20        visitors_day2.update_u64(id);
21    }
22
23    println!(
24        "Day 1 unique visitors (estimate): {:.0}",
25        visitors_day1.get_estimate()
26    );
27    println!(
28        "Day 2 unique visitors (estimate): {:.0}",
29        visitors_day2.get_estimate()
30    );
31
32    // Union: total unique visitors across both days.
33    let mut union = ThetaUnionBuilder::new().lg_k(12).build().unwrap();
34    union.update(&visitors_day1);
35    union.update(&visitors_day2);
36    let total_unique = union.get_result(true);
37    println!(
38        "Total unique visitors (union estimate): {:.0}",
39        total_unique.get_estimate()
40    );
41
42    // Intersection: visitors who came back on day 2.
43    let mut intersection = ThetaIntersection::new();
44    intersection.update(&visitors_day1);
45    intersection.update(&visitors_day2);
46    match intersection.get_result(true) {
47        Ok(returning) => println!(
48            "Returning visitors (intersection estimate): {:.0}",
49            returning.get_estimate()
50        ),
51        Err(e) => println!("No intersection result: {e}"),
52    }
53
54    // A-not-b: visitors who only came on day 1.
55    let a_not_b = ThetaAnotB::new();
56    let day1_only = a_not_b.compute(&visitors_day1, &visitors_day2, true);
57    println!(
58        "Day-1-only visitors (a-not-b estimate): {:.0}",
59        day1_only.get_estimate()
60    );
61
62    // Jaccard similarity: how similar are the two days' visitor sets?
63    let similarity = jaccard_similarity(&visitors_day1, &visitors_day2);
64    println!(
65        "Jaccard similarity: {:.3} (range [{:.3}, {:.3}])",
66        similarity.estimate, similarity.lower_bound, similarity.upper_bound
67    );
68
69    // Serialize a compact sketch for storage/transmission, then restore it.
70    let compact = visitors_day1.compact(true);
71    let bytes = compact.serialize_compact();
72    println!("Serialized day-1 sketch: {} bytes", bytes.len());
73    let restored = apache_datasketches::theta::CompactThetaSketch::deserialize(&bytes).unwrap();
74    println!("Restored estimate: {:.0}", restored.get_estimate());
75}
Source

pub fn get_lower_bound(&self, num_std_dev: u8) -> Result<f64, SketchError>

Returns the lower bound of the confidence interval around Self::get_estimate. See ThetaSketch::get_lower_bound for the meaning of num_std_dev.

Source

pub fn get_upper_bound(&self, num_std_dev: u8) -> Result<f64, SketchError>

Returns the upper bound of the confidence interval around Self::get_estimate. See ThetaSketch::get_lower_bound for the meaning of num_std_dev.

Source

pub fn is_empty(&self) -> bool

Returns true if this sketch represents an empty set.

Source

pub fn is_estimation_mode(&self) -> bool

Returns true if this sketch’s theta threshold is below 1.0 (i.e. Self::get_estimate is a statistical estimate rather than an exact count).

Source

pub fn is_ordered(&self) -> bool

Returns true if this sketch’s retained entries are sorted by hash value.

Source

pub fn get_theta(&self) -> f64

Returns the current theta threshold (1.0 if not in estimation mode).

Source

pub fn get_num_retained(&self) -> u32

Returns the number of entries currently retained by this sketch.

Source

pub fn serialize_compact(&self) -> Vec<u8>

Serializes in the v3 (uncompressed) format. Note: unlike the design spec’s initially-sketched signature, this takes no ordered parameter — upstream’s compact_theta_sketch::serialize() has none; orderedness is fixed when this sketch was created (e.g. via ThetaSketch::compact(ordered)).

Examples found in repository?
examples/bench_theta_update.rs (line 320)
314fn bench_serde(items: u64, reps: usize) {
315    let mut update_sketch = build();
316    for key in 0..items {
317        update_sketch.update_u64(key);
318    }
319    let sketch = update_sketch.compact(true);
320    let reference = sketch.serialize_compact();
321
322    let mut passes = Vec::with_capacity(reps);
323    for _ in 0..reps {
324        let start = Instant::now();
325        let mut total = 0usize;
326        for _ in 0..SER_CALLS {
327            total += black_box(sketch.serialize_compact()).len();
328        }
329        let elapsed = start.elapsed();
330        black_box(total);
331        passes.push(Pass {
332            elapsed,
333            estimate: sketch.get_estimate(),
334        });
335    }
336    report_bytes("ser", items, SER_CALLS, &passes, reference.len());
337
338    let deserialize =
339        || CompactThetaSketch::deserialize(&reference).expect("the bytes came from serialize");
340    let mut passes = Vec::with_capacity(reps);
341    for _ in 0..reps {
342        let start = Instant::now();
343        let mut total = 0.0;
344        for _ in 0..DESER_CALLS {
345            total += deserialize().get_estimate();
346        }
347        let elapsed = start.elapsed();
348        black_box(total);
349        passes.push(Pass {
350            elapsed,
351            estimate: deserialize().get_estimate(),
352        });
353    }
354    report_bytes("deser", items, DESER_CALLS, &passes, reference.len());
355}
More examples
Hide additional examples
examples/theta.rs (line 71)
11fn main() {
12    // Build two sketches representing two overlapping sets of user IDs.
13    let mut visitors_day1 = ThetaSketchBuilder::new().lg_k(12).build().unwrap();
14    for id in 0..10_000u64 {
15        visitors_day1.update_u64(id);
16    }
17
18    let mut visitors_day2 = ThetaSketchBuilder::new().lg_k(12).build().unwrap();
19    for id in 5_000..15_000u64 {
20        visitors_day2.update_u64(id);
21    }
22
23    println!(
24        "Day 1 unique visitors (estimate): {:.0}",
25        visitors_day1.get_estimate()
26    );
27    println!(
28        "Day 2 unique visitors (estimate): {:.0}",
29        visitors_day2.get_estimate()
30    );
31
32    // Union: total unique visitors across both days.
33    let mut union = ThetaUnionBuilder::new().lg_k(12).build().unwrap();
34    union.update(&visitors_day1);
35    union.update(&visitors_day2);
36    let total_unique = union.get_result(true);
37    println!(
38        "Total unique visitors (union estimate): {:.0}",
39        total_unique.get_estimate()
40    );
41
42    // Intersection: visitors who came back on day 2.
43    let mut intersection = ThetaIntersection::new();
44    intersection.update(&visitors_day1);
45    intersection.update(&visitors_day2);
46    match intersection.get_result(true) {
47        Ok(returning) => println!(
48            "Returning visitors (intersection estimate): {:.0}",
49            returning.get_estimate()
50        ),
51        Err(e) => println!("No intersection result: {e}"),
52    }
53
54    // A-not-b: visitors who only came on day 1.
55    let a_not_b = ThetaAnotB::new();
56    let day1_only = a_not_b.compute(&visitors_day1, &visitors_day2, true);
57    println!(
58        "Day-1-only visitors (a-not-b estimate): {:.0}",
59        day1_only.get_estimate()
60    );
61
62    // Jaccard similarity: how similar are the two days' visitor sets?
63    let similarity = jaccard_similarity(&visitors_day1, &visitors_day2);
64    println!(
65        "Jaccard similarity: {:.3} (range [{:.3}, {:.3}])",
66        similarity.estimate, similarity.lower_bound, similarity.upper_bound
67    );
68
69    // Serialize a compact sketch for storage/transmission, then restore it.
70    let compact = visitors_day1.compact(true);
71    let bytes = compact.serialize_compact();
72    println!("Serialized day-1 sketch: {} bytes", bytes.len());
73    let restored = apache_datasketches::theta::CompactThetaSketch::deserialize(&bytes).unwrap();
74    println!("Restored estimate: {:.0}", restored.get_estimate());
75}
Source

pub fn serialize_compressed(&self) -> Vec<u8>

Serializes in the v4 (compressed) format.

Trait Implementations§

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.