Skip to main content

CompactArrayOfDoublesSketch

Struct CompactArrayOfDoublesSketch 

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

An immutable, serializable snapshot of an ArrayOfDoubles Tuple sketch. Produced by super::ArrayOfDoublesSketch::compact, by any set operation’s result, or by Self::deserialize.

Implementations§

Source§

impl CompactArrayOfDoublesSketch

Source

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

Deserializes bytes produced by Self::serialize. Returns SketchError::Deserialization if the bytes are truncated, corrupt, or not an ArrayOfDoubles sketch.

Examples found in repository?
examples/bench_tuple_update.rs (line 365)
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}
More examples
Hide additional examples
examples/tuple.rs (line 95)
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 serialize(&self) -> Vec<u8>

Serializes this sketch. Unlike Theta, this family has exactly one serialization format upstream — there is no compressed variant — and no ordered parameter: orderedness is fixed when the snapshot was created (e.g. via ArrayOfDoublesSketch::compact).

Examples found in repository?
examples/bench_tuple_update.rs (line 346)
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}
More examples
Hide additional examples
examples/tuple.rs (line 93)
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_estimate(&self) -> f64

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

Examples found in repository?
examples/bench_tuple_update.rs (line 359)
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}
400
401/// A fresh union is built inside the timed loop, so the figure is
402/// construct + two updates + get_result, not the merge alone -- reusing one
403/// accumulator across `OP_CALLS` iterations would have each iteration merge
404/// into an ever-growing result, measuring a different workload every time.
405fn bench_union(items: u64, reps: usize) {
406    let (a, b) = build_operands(items);
407    let mut passes = Vec::with_capacity(reps);
408    for _ in 0..reps {
409        let start = Instant::now();
410        let mut total = 0.0;
411        let mut estimate = 0.0;
412        for _ in 0..OP_CALLS {
413            let mut union = ArrayOfDoublesUnionBuilder::new()
414                .lg_k(LG_K)
415                .num_values(NUM_VALUES)
416                .build()
417                .expect("fixed valid parameters were rejected");
418            union
419                .update(&a)
420                .expect("operands match the union's num_values");
421            union
422                .update(&b)
423                .expect("operands match the union's num_values");
424            estimate = union.get_result(true).get_estimate();
425            total += estimate;
426        }
427        let elapsed = start.elapsed();
428        black_box(total);
429        passes.push(Pass { elapsed, estimate });
430    }
431    report_line("union", items, OP_CALLS, &passes, String::new());
432}
433
434/// As [`bench_union`]: a fresh intersection per iteration, so the figure is
435/// construct + two updates + get_result.
436fn bench_intersect(items: u64, reps: usize) {
437    let (a, b) = build_operands(items);
438    let mut passes = Vec::with_capacity(reps);
439    for _ in 0..reps {
440        let start = Instant::now();
441        let mut total = 0.0;
442        let mut estimate = 0.0;
443        for _ in 0..OP_CALLS {
444            let mut intersection = ArrayOfDoublesIntersection::new(NUM_VALUES)
445                .expect("fixed valid num_values was rejected");
446            intersection
447                .update(&a)
448                .expect("operands match the intersection's num_values");
449            intersection
450                .update(&b)
451                .expect("operands match the intersection's num_values");
452            estimate = intersection
453                .get_result(true)
454                .expect("both operands were non-empty")
455                .get_estimate();
456            total += estimate;
457        }
458        let elapsed = start.elapsed();
459        black_box(total);
460        passes.push(Pass { elapsed, estimate });
461    }
462    report_line("intersect", items, OP_CALLS, &passes, String::new());
463}
More examples
Hide additional examples
examples/tuple.rs (line 50)
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. See ArrayOfDoublesSketch::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 ArrayOfDoublesSketch::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).

Examples found in repository?
examples/tuple.rs (line 59)
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_num_retained(&self) -> u32

Returns the number of entries retained by this sketch.

Examples found in repository?
examples/tuple.rs (line 60)
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_num_values(&self) -> u8

Returns the fixed number of f64 values each retained entry carries.

Examples found in repository?
examples/tuple.rs (line 99)
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(). Ordered by hash if Self::is_ordered is true.

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.

Examples found in repository?
examples/tuple.rs (line 56)
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 CompactArrayOfDoublesSketch

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 CompactArrayOfDoublesSketch

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.