Skip to main content

ArrayOfDoublesSketch

Struct ArrayOfDoublesSketch 

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

A mutable, update-only ArrayOfDoubles Tuple sketch: estimates the number of distinct keys added via update_*, and carries a fixed-width array of f64 values per retained key, summed on collision. Build one with ArrayOfDoublesSketchBuilder.

Call Self::compact to produce an immutable, serializable super::CompactArrayOfDoublesSketch snapshot for storage, transmission, or use as input to a set operation.

Implementations§

Source§

impl ArrayOfDoublesSketch

Source

pub fn update_u64( &mut self, key: u64, values: &[f64], ) -> Result<(), SketchError>

Adds a u64 key with its associated values. Returns SketchError::InvalidConfig unless values.len() == self.get_num_values().

Examples found in repository?
examples/bench_tuple_update.rs (line 280)
273fn bench_distinct(items: u64, reps: usize) {
274    let mut passes = Vec::with_capacity(reps);
275    for _ in 0..reps {
276        let mut sketch = build();
277        let start = Instant::now();
278        for key in 0..items {
279            sketch
280                .update_u64(key, &VALUES)
281                .expect("update rejected a correctly-sized value slice");
282        }
283        let elapsed = start.elapsed();
284        passes.push(Pass {
285            elapsed,
286            estimate: sketch.get_estimate(),
287        });
288    }
289    report("distinct", items, &passes);
290}
291
292fn bench_hot(items: u64, reps: usize) {
293    let mut passes = Vec::with_capacity(reps);
294    for _ in 0..reps {
295        let mut sketch = build();
296        let start = Instant::now();
297        for i in 0..items {
298            sketch
299                .update_u64(i % HOT_KEY_SPACE, &VALUES)
300                .expect("update rejected a correctly-sized value slice");
301        }
302        let elapsed = start.elapsed();
303        passes.push(Pass {
304            elapsed,
305            estimate: sketch.get_estimate(),
306        });
307    }
308    report("hot", items, &passes);
309}
310
311fn bench_str(items: u64, reps: usize) {
312    let keys = string_keys();
313    let mut passes = Vec::with_capacity(reps);
314    for _ in 0..reps {
315        let mut sketch = build();
316        let start = Instant::now();
317        for i in 0..items {
318            sketch
319                .update_str(&keys[(i % STR_KEY_SPACE) as usize], &VALUES)
320                .expect("update rejected a correctly-sized value slice");
321        }
322        let elapsed = start.elapsed();
323        passes.push(Pass {
324            elapsed,
325            estimate: sketch.get_estimate(),
326        });
327    }
328    report("str", items, &passes);
329}
330
331/// Serialization, measured per call rather than per item: its cost tracks the
332/// serialized size, which at `lg_k = 12` is the same at every ladder rung.
333///
334/// The sketch is built once and shared by both directions and every rep.
335/// Serializing does not mutate it, so unlike the update scenarios there is no
336/// state that a second rep would find already dirtied -- and rebuilding at the
337/// 100M rung would cost more than the measurement itself.
338fn bench_serde(items: u64, reps: usize) {
339    let mut update_sketch = build();
340    for key in 0..items {
341        update_sketch
342            .update_u64(key, &VALUES)
343            .expect("update rejected a correctly-sized value slice");
344    }
345    let sketch = update_sketch.compact(true);
346    let reference = sketch.serialize();
347
348    let mut passes = Vec::with_capacity(reps);
349    for _ in 0..reps {
350        let start = Instant::now();
351        let mut total = 0usize;
352        for _ in 0..SER_CALLS {
353            total += black_box(sketch.serialize()).len();
354        }
355        let elapsed = start.elapsed();
356        black_box(total);
357        passes.push(Pass {
358            elapsed,
359            estimate: sketch.get_estimate(),
360        });
361    }
362    report_bytes("ser", items, SER_CALLS, &passes, reference.len());
363
364    let deserialize = || {
365        CompactArrayOfDoublesSketch::deserialize(&reference).expect("the bytes came from serialize")
366    };
367    let mut passes = Vec::with_capacity(reps);
368    for _ in 0..reps {
369        let start = Instant::now();
370        let mut total = 0.0;
371        for _ in 0..DESER_CALLS {
372            total += deserialize().get_estimate();
373        }
374        let elapsed = start.elapsed();
375        black_box(total);
376        passes.push(Pass {
377            elapsed,
378            estimate: deserialize().get_estimate(),
379        });
380    }
381    report_bytes("deser", items, DESER_CALLS, &passes, reference.len());
382}
383
384/// Two operands with 50% overlap, built once outside every timed region: the
385/// operand-construction cost belongs to the setup, not to the union/
386/// intersection/jaccard call being measured.
387fn build_operands(items: u64) -> (CompactArrayOfDoublesSketch, CompactArrayOfDoublesSketch) {
388    let mut a = build();
389    for key in 0..items {
390        a.update_u64(key, &VALUES)
391            .expect("update rejected a correctly-sized value slice");
392    }
393    let mut b = build();
394    for key in (items / 2)..(items + items / 2) {
395        b.update_u64(key, &VALUES)
396            .expect("update rejected a correctly-sized value slice");
397    }
398    (a.compact(true), b.compact(true))
399}
More examples
Hide additional examples
examples/tuple.rs (line 22)
14fn main() {
15    // Two sketches of user IDs, each carrying [sessions, revenue] per user.
16    let mut day1 = ArrayOfDoublesSketchBuilder::new()
17        .lg_k(12)
18        .num_values(2)
19        .build()
20        .unwrap();
21    for id in 0..10_000u64 {
22        day1.update_u64(id, &[1.0, 2.50]).unwrap();
23    }
24
25    let mut day2 = ArrayOfDoublesSketchBuilder::new()
26        .lg_k(12)
27        .num_values(2)
28        .build()
29        .unwrap();
30    for id in 5_000..15_000u64 {
31        day2.update_u64(id, &[1.0, 4.00]).unwrap();
32    }
33
34    println!("Day 1 unique users (estimate): {:.0}", day1.get_estimate());
35    println!("Day 2 unique users (estimate): {:.0}", day2.get_estimate());
36    println!("Values per entry: {}", day1.get_num_values());
37
38    // Union: unique users across both days, with per-user values summed for
39    // anyone who appeared on both.
40    let mut union = ArrayOfDoublesUnionBuilder::new()
41        .lg_k(12)
42        .num_values(2)
43        .build()
44        .unwrap();
45    union.update(&day1).unwrap();
46    union.update(&day2).unwrap();
47    let combined = union.get_result(true);
48    println!(
49        "Total unique users (union estimate): {:.0}",
50        combined.get_estimate()
51    );
52
53    // Per-entry access is what distinguishes Tuple sketches from HLL/Theta/CPC:
54    // scale the retained sample's revenue back up by 1/theta to estimate the
55    // full population total.
56    let retained_revenue: f64 = combined.entries().map(|(_, values)| values[1]).sum();
57    println!(
58        "Estimated total revenue: {:.2} (from {} retained entries, theta = {:.4})",
59        retained_revenue / combined.get_theta(),
60        combined.get_num_retained(),
61        combined.get_theta()
62    );
63
64    // Intersection: users who came back on day 2.
65    let mut intersection = ArrayOfDoublesIntersection::new(2).unwrap();
66    intersection.update(&day1).unwrap();
67    intersection.update(&day2).unwrap();
68    match intersection.get_result(true) {
69        Ok(returning) => println!(
70            "Returning users (intersection estimate): {:.0}",
71            returning.get_estimate()
72        ),
73        Err(e) => println!("No intersection result: {e}"),
74    }
75
76    // A-not-b: users who only came on day 1.
77    let a_not_b = ArrayOfDoublesAnotB::new();
78    let day1_only = a_not_b.compute(&day1, &day2, true).unwrap();
79    println!(
80        "Day-1-only users (a-not-b estimate): {:.0}",
81        day1_only.get_estimate()
82    );
83
84    // Jaccard similarity of the two days' audiences.
85    let similarity = array_of_doubles_jaccard_similarity(&day1, &day2).unwrap();
86    println!(
87        "Jaccard similarity: {:.3} (range [{:.3}, {:.3}])",
88        similarity.estimate, similarity.lower_bound, similarity.upper_bound
89    );
90
91    // Serialize a compact sketch for storage/transmission, then restore it.
92    let compact = day1.compact(true);
93    let bytes = compact.serialize();
94    println!("Serialized day-1 sketch: {} bytes", bytes.len());
95    let restored = CompactArrayOfDoublesSketch::deserialize(&bytes).unwrap();
96    println!(
97        "Restored estimate: {:.0} ({} values per entry)",
98        restored.get_estimate(),
99        restored.get_num_values()
100    );
101}
Source

pub fn update_i64( &mut self, key: i64, values: &[f64], ) -> Result<(), SketchError>

Adds an i64 key with its associated values. See Self::update_u64.

Source

pub fn update_u32( &mut self, key: u32, values: &[f64], ) -> Result<(), SketchError>

Adds a u32 key with its associated values. See Self::update_u64.

Source

pub fn update_i32( &mut self, key: i32, values: &[f64], ) -> Result<(), SketchError>

Adds an i32 key with its associated values. See Self::update_u64.

Source

pub fn update_u16( &mut self, key: u16, values: &[f64], ) -> Result<(), SketchError>

Adds a u16 key with its associated values. See Self::update_u64.

Source

pub fn update_i16( &mut self, key: i16, values: &[f64], ) -> Result<(), SketchError>

Adds an i16 key with its associated values. See Self::update_u64.

Source

pub fn update_u8(&mut self, key: u8, values: &[f64]) -> Result<(), SketchError>

Adds a u8 key with its associated values. See Self::update_u64.

Source

pub fn update_i8(&mut self, key: i8, values: &[f64]) -> Result<(), SketchError>

Adds an i8 key with its associated values. See Self::update_u64.

Source

pub fn update_f64( &mut self, key: f64, values: &[f64], ) -> Result<(), SketchError>

Adds an f64 key with its associated values. See Self::update_u64.

Source

pub fn update_str( &mut self, key: &str, values: &[f64], ) -> Result<(), SketchError>

Adds a string key with its associated values. See Self::update_u64.

Examples found in repository?
examples/bench_tuple_update.rs (line 319)
311fn bench_str(items: u64, reps: usize) {
312    let keys = string_keys();
313    let mut passes = Vec::with_capacity(reps);
314    for _ in 0..reps {
315        let mut sketch = build();
316        let start = Instant::now();
317        for i in 0..items {
318            sketch
319                .update_str(&keys[(i % STR_KEY_SPACE) as usize], &VALUES)
320                .expect("update rejected a correctly-sized value slice");
321        }
322        let elapsed = start.elapsed();
323        passes.push(Pass {
324            elapsed,
325            estimate: sketch.get_estimate(),
326        });
327    }
328    report("str", items, &passes);
329}
Source

pub fn update_bytes( &mut self, key: &[u8], values: &[f64], ) -> Result<(), SketchError>

Adds an arbitrary byte-slice key with its associated values. See Self::update_u64.

Source

pub fn trim(&mut self)

Removes retained entries in excess of the nominal size k, lowering the theta threshold to do so.

Note that this does shift Self::get_estimate — trimming lowers theta, and the estimate is derived from the retained count and theta together. Upstream only guarantees the excess entries are dropped.

Source

pub fn reset(&mut self)

Resets this sketch to its initial, empty state. num_values is preserved.

Source

pub fn get_estimate(&self) -> f64

Returns the current estimate of the number of distinct keys added.

Examples found in repository?
examples/bench_tuple_update.rs (line 286)
273fn bench_distinct(items: u64, reps: usize) {
274    let mut passes = Vec::with_capacity(reps);
275    for _ in 0..reps {
276        let mut sketch = build();
277        let start = Instant::now();
278        for key in 0..items {
279            sketch
280                .update_u64(key, &VALUES)
281                .expect("update rejected a correctly-sized value slice");
282        }
283        let elapsed = start.elapsed();
284        passes.push(Pass {
285            elapsed,
286            estimate: sketch.get_estimate(),
287        });
288    }
289    report("distinct", items, &passes);
290}
291
292fn bench_hot(items: u64, reps: usize) {
293    let mut passes = Vec::with_capacity(reps);
294    for _ in 0..reps {
295        let mut sketch = build();
296        let start = Instant::now();
297        for i in 0..items {
298            sketch
299                .update_u64(i % HOT_KEY_SPACE, &VALUES)
300                .expect("update rejected a correctly-sized value slice");
301        }
302        let elapsed = start.elapsed();
303        passes.push(Pass {
304            elapsed,
305            estimate: sketch.get_estimate(),
306        });
307    }
308    report("hot", items, &passes);
309}
310
311fn bench_str(items: u64, reps: usize) {
312    let keys = string_keys();
313    let mut passes = Vec::with_capacity(reps);
314    for _ in 0..reps {
315        let mut sketch = build();
316        let start = Instant::now();
317        for i in 0..items {
318            sketch
319                .update_str(&keys[(i % STR_KEY_SPACE) as usize], &VALUES)
320                .expect("update rejected a correctly-sized value slice");
321        }
322        let elapsed = start.elapsed();
323        passes.push(Pass {
324            elapsed,
325            estimate: sketch.get_estimate(),
326        });
327    }
328    report("str", items, &passes);
329}
More examples
Hide additional examples
examples/tuple.rs (line 34)
14fn main() {
15    // Two sketches of user IDs, each carrying [sessions, revenue] per user.
16    let mut day1 = ArrayOfDoublesSketchBuilder::new()
17        .lg_k(12)
18        .num_values(2)
19        .build()
20        .unwrap();
21    for id in 0..10_000u64 {
22        day1.update_u64(id, &[1.0, 2.50]).unwrap();
23    }
24
25    let mut day2 = ArrayOfDoublesSketchBuilder::new()
26        .lg_k(12)
27        .num_values(2)
28        .build()
29        .unwrap();
30    for id in 5_000..15_000u64 {
31        day2.update_u64(id, &[1.0, 4.00]).unwrap();
32    }
33
34    println!("Day 1 unique users (estimate): {:.0}", day1.get_estimate());
35    println!("Day 2 unique users (estimate): {:.0}", day2.get_estimate());
36    println!("Values per entry: {}", day1.get_num_values());
37
38    // Union: unique users across both days, with per-user values summed for
39    // anyone who appeared on both.
40    let mut union = ArrayOfDoublesUnionBuilder::new()
41        .lg_k(12)
42        .num_values(2)
43        .build()
44        .unwrap();
45    union.update(&day1).unwrap();
46    union.update(&day2).unwrap();
47    let combined = union.get_result(true);
48    println!(
49        "Total unique users (union estimate): {:.0}",
50        combined.get_estimate()
51    );
52
53    // Per-entry access is what distinguishes Tuple sketches from HLL/Theta/CPC:
54    // scale the retained sample's revenue back up by 1/theta to estimate the
55    // full population total.
56    let retained_revenue: f64 = combined.entries().map(|(_, values)| values[1]).sum();
57    println!(
58        "Estimated total revenue: {:.2} (from {} retained entries, theta = {:.4})",
59        retained_revenue / combined.get_theta(),
60        combined.get_num_retained(),
61        combined.get_theta()
62    );
63
64    // Intersection: users who came back on day 2.
65    let mut intersection = ArrayOfDoublesIntersection::new(2).unwrap();
66    intersection.update(&day1).unwrap();
67    intersection.update(&day2).unwrap();
68    match intersection.get_result(true) {
69        Ok(returning) => println!(
70            "Returning users (intersection estimate): {:.0}",
71            returning.get_estimate()
72        ),
73        Err(e) => println!("No intersection result: {e}"),
74    }
75
76    // A-not-b: users who only came on day 1.
77    let a_not_b = ArrayOfDoublesAnotB::new();
78    let day1_only = a_not_b.compute(&day1, &day2, true).unwrap();
79    println!(
80        "Day-1-only users (a-not-b estimate): {:.0}",
81        day1_only.get_estimate()
82    );
83
84    // Jaccard similarity of the two days' audiences.
85    let similarity = array_of_doubles_jaccard_similarity(&day1, &day2).unwrap();
86    println!(
87        "Jaccard similarity: {:.3} (range [{:.3}, {:.3}])",
88        similarity.estimate, similarity.lower_bound, similarity.upper_bound
89    );
90
91    // Serialize a compact sketch for storage/transmission, then restore it.
92    let compact = day1.compact(true);
93    let bytes = compact.serialize();
94    println!("Serialized day-1 sketch: {} bytes", bytes.len());
95    let restored = CompactArrayOfDoublesSketch::deserialize(&bytes).unwrap();
96    println!(
97        "Restored estimate: {:.0} ({} values per entry)",
98        restored.get_estimate(),
99        restored.get_num_values()
100    );
101}
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, for the given number of standard deviations (1, 2, or 3, corresponding to roughly 67%, 95%, and 99% confidence). Returns SketchError::InvalidConfig for any other value.

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 Self::get_lower_bound for the meaning of num_std_dev.

Source

pub fn is_empty(&self) -> bool

Returns true if no keys have been added to this sketch.

Source

pub fn is_estimation_mode(&self) -> bool

Returns true if this sketch’s theta threshold is below 1.0 (i.e. it has begun sampling and 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 until sampling begins).

Source

pub fn get_num_retained(&self) -> u32

Returns the number of entries currently retained by this sketch.

Source

pub fn get_num_values(&self) -> u8

Returns the fixed number of f64 values each retained entry carries, as configured at build time.

Examples found in repository?
examples/tuple.rs (line 36)
14fn main() {
15    // Two sketches of user IDs, each carrying [sessions, revenue] per user.
16    let mut day1 = ArrayOfDoublesSketchBuilder::new()
17        .lg_k(12)
18        .num_values(2)
19        .build()
20        .unwrap();
21    for id in 0..10_000u64 {
22        day1.update_u64(id, &[1.0, 2.50]).unwrap();
23    }
24
25    let mut day2 = ArrayOfDoublesSketchBuilder::new()
26        .lg_k(12)
27        .num_values(2)
28        .build()
29        .unwrap();
30    for id in 5_000..15_000u64 {
31        day2.update_u64(id, &[1.0, 4.00]).unwrap();
32    }
33
34    println!("Day 1 unique users (estimate): {:.0}", day1.get_estimate());
35    println!("Day 2 unique users (estimate): {:.0}", day2.get_estimate());
36    println!("Values per entry: {}", day1.get_num_values());
37
38    // Union: unique users across both days, with per-user values summed for
39    // anyone who appeared on both.
40    let mut union = ArrayOfDoublesUnionBuilder::new()
41        .lg_k(12)
42        .num_values(2)
43        .build()
44        .unwrap();
45    union.update(&day1).unwrap();
46    union.update(&day2).unwrap();
47    let combined = union.get_result(true);
48    println!(
49        "Total unique users (union estimate): {:.0}",
50        combined.get_estimate()
51    );
52
53    // Per-entry access is what distinguishes Tuple sketches from HLL/Theta/CPC:
54    // scale the retained sample's revenue back up by 1/theta to estimate the
55    // full population total.
56    let retained_revenue: f64 = combined.entries().map(|(_, values)| values[1]).sum();
57    println!(
58        "Estimated total revenue: {:.2} (from {} retained entries, theta = {:.4})",
59        retained_revenue / combined.get_theta(),
60        combined.get_num_retained(),
61        combined.get_theta()
62    );
63
64    // Intersection: users who came back on day 2.
65    let mut intersection = ArrayOfDoublesIntersection::new(2).unwrap();
66    intersection.update(&day1).unwrap();
67    intersection.update(&day2).unwrap();
68    match intersection.get_result(true) {
69        Ok(returning) => println!(
70            "Returning users (intersection estimate): {:.0}",
71            returning.get_estimate()
72        ),
73        Err(e) => println!("No intersection result: {e}"),
74    }
75
76    // A-not-b: users who only came on day 1.
77    let a_not_b = ArrayOfDoublesAnotB::new();
78    let day1_only = a_not_b.compute(&day1, &day2, true).unwrap();
79    println!(
80        "Day-1-only users (a-not-b estimate): {:.0}",
81        day1_only.get_estimate()
82    );
83
84    // Jaccard similarity of the two days' audiences.
85    let similarity = array_of_doubles_jaccard_similarity(&day1, &day2).unwrap();
86    println!(
87        "Jaccard similarity: {:.3} (range [{:.3}, {:.3}])",
88        similarity.estimate, similarity.lower_bound, similarity.upper_bound
89    );
90
91    // Serialize a compact sketch for storage/transmission, then restore it.
92    let compact = day1.compact(true);
93    let bytes = compact.serialize();
94    println!("Serialized day-1 sketch: {} bytes", bytes.len());
95    let restored = CompactArrayOfDoublesSketch::deserialize(&bytes).unwrap();
96    println!(
97        "Restored estimate: {:.0} ({} values per entry)",
98        restored.get_estimate(),
99        restored.get_num_values()
100    );
101}
Source

pub fn entries(&self) -> impl Iterator<Item = (u64, Vec<f64>)>

Iterates the retained entries as (hash, values) pairs, where values.len() == self.get_num_values().

The entries are copied out of C++ in two FFI calls up front (cxx cannot hand back a live C++ iterator), so each item owns its Vec rather than borrowing from the sketch. Iteration order is unspecified for an update sketch; compact it with ordered = true for hash-ordered iteration.

Source

pub fn compact(&self, ordered: bool) -> CompactArrayOfDoublesSketch

Produces an immutable, serializable super::CompactArrayOfDoublesSketch snapshot of this sketch’s current state. If ordered is true, the snapshot’s entries are sorted by hash value.

Examples found in repository?
examples/bench_tuple_update.rs (line 345)
338fn bench_serde(items: u64, reps: usize) {
339    let mut update_sketch = build();
340    for key in 0..items {
341        update_sketch
342            .update_u64(key, &VALUES)
343            .expect("update rejected a correctly-sized value slice");
344    }
345    let sketch = update_sketch.compact(true);
346    let reference = sketch.serialize();
347
348    let mut passes = Vec::with_capacity(reps);
349    for _ in 0..reps {
350        let start = Instant::now();
351        let mut total = 0usize;
352        for _ in 0..SER_CALLS {
353            total += black_box(sketch.serialize()).len();
354        }
355        let elapsed = start.elapsed();
356        black_box(total);
357        passes.push(Pass {
358            elapsed,
359            estimate: sketch.get_estimate(),
360        });
361    }
362    report_bytes("ser", items, SER_CALLS, &passes, reference.len());
363
364    let deserialize = || {
365        CompactArrayOfDoublesSketch::deserialize(&reference).expect("the bytes came from serialize")
366    };
367    let mut passes = Vec::with_capacity(reps);
368    for _ in 0..reps {
369        let start = Instant::now();
370        let mut total = 0.0;
371        for _ in 0..DESER_CALLS {
372            total += deserialize().get_estimate();
373        }
374        let elapsed = start.elapsed();
375        black_box(total);
376        passes.push(Pass {
377            elapsed,
378            estimate: deserialize().get_estimate(),
379        });
380    }
381    report_bytes("deser", items, DESER_CALLS, &passes, reference.len());
382}
383
384/// Two operands with 50% overlap, built once outside every timed region: the
385/// operand-construction cost belongs to the setup, not to the union/
386/// intersection/jaccard call being measured.
387fn build_operands(items: u64) -> (CompactArrayOfDoublesSketch, CompactArrayOfDoublesSketch) {
388    let mut a = build();
389    for key in 0..items {
390        a.update_u64(key, &VALUES)
391            .expect("update rejected a correctly-sized value slice");
392    }
393    let mut b = build();
394    for key in (items / 2)..(items + items / 2) {
395        b.update_u64(key, &VALUES)
396            .expect("update rejected a correctly-sized value slice");
397    }
398    (a.compact(true), b.compact(true))
399}
More examples
Hide additional examples
examples/tuple.rs (line 92)
14fn main() {
15    // Two sketches of user IDs, each carrying [sessions, revenue] per user.
16    let mut day1 = ArrayOfDoublesSketchBuilder::new()
17        .lg_k(12)
18        .num_values(2)
19        .build()
20        .unwrap();
21    for id in 0..10_000u64 {
22        day1.update_u64(id, &[1.0, 2.50]).unwrap();
23    }
24
25    let mut day2 = ArrayOfDoublesSketchBuilder::new()
26        .lg_k(12)
27        .num_values(2)
28        .build()
29        .unwrap();
30    for id in 5_000..15_000u64 {
31        day2.update_u64(id, &[1.0, 4.00]).unwrap();
32    }
33
34    println!("Day 1 unique users (estimate): {:.0}", day1.get_estimate());
35    println!("Day 2 unique users (estimate): {:.0}", day2.get_estimate());
36    println!("Values per entry: {}", day1.get_num_values());
37
38    // Union: unique users across both days, with per-user values summed for
39    // anyone who appeared on both.
40    let mut union = ArrayOfDoublesUnionBuilder::new()
41        .lg_k(12)
42        .num_values(2)
43        .build()
44        .unwrap();
45    union.update(&day1).unwrap();
46    union.update(&day2).unwrap();
47    let combined = union.get_result(true);
48    println!(
49        "Total unique users (union estimate): {:.0}",
50        combined.get_estimate()
51    );
52
53    // Per-entry access is what distinguishes Tuple sketches from HLL/Theta/CPC:
54    // scale the retained sample's revenue back up by 1/theta to estimate the
55    // full population total.
56    let retained_revenue: f64 = combined.entries().map(|(_, values)| values[1]).sum();
57    println!(
58        "Estimated total revenue: {:.2} (from {} retained entries, theta = {:.4})",
59        retained_revenue / combined.get_theta(),
60        combined.get_num_retained(),
61        combined.get_theta()
62    );
63
64    // Intersection: users who came back on day 2.
65    let mut intersection = ArrayOfDoublesIntersection::new(2).unwrap();
66    intersection.update(&day1).unwrap();
67    intersection.update(&day2).unwrap();
68    match intersection.get_result(true) {
69        Ok(returning) => println!(
70            "Returning users (intersection estimate): {:.0}",
71            returning.get_estimate()
72        ),
73        Err(e) => println!("No intersection result: {e}"),
74    }
75
76    // A-not-b: users who only came on day 1.
77    let a_not_b = ArrayOfDoublesAnotB::new();
78    let day1_only = a_not_b.compute(&day1, &day2, true).unwrap();
79    println!(
80        "Day-1-only users (a-not-b estimate): {:.0}",
81        day1_only.get_estimate()
82    );
83
84    // Jaccard similarity of the two days' audiences.
85    let similarity = array_of_doubles_jaccard_similarity(&day1, &day2).unwrap();
86    println!(
87        "Jaccard similarity: {:.3} (range [{:.3}, {:.3}])",
88        similarity.estimate, similarity.lower_bound, similarity.upper_bound
89    );
90
91    // Serialize a compact sketch for storage/transmission, then restore it.
92    let compact = day1.compact(true);
93    let bytes = compact.serialize();
94    println!("Serialized day-1 sketch: {} bytes", bytes.len());
95    let restored = CompactArrayOfDoublesSketch::deserialize(&bytes).unwrap();
96    println!(
97        "Restored estimate: {:.0} ({} values per entry)",
98        restored.get_estimate(),
99        restored.get_num_values()
100    );
101}

Trait Implementations§

Source§

impl ArrayOfDoublesInput for ArrayOfDoublesSketch

Source§

fn get_num_values(&self) -> u8

The fixed number of f64 values each of this sketch’s retained entries carries. Set operations require all operands to agree.
Source§

impl Send for ArrayOfDoublesSketch

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.