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
impl CompactArrayOfDoublesSketch
Sourcepub fn deserialize(bytes: &[u8]) -> Result<Self, SketchError>
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?
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
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}Sourcepub fn serialize(&self) -> Vec<u8> ⓘ
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?
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
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}Sourcepub fn get_estimate(&self) -> f64
pub fn get_estimate(&self) -> f64
Returns the current estimate of the number of distinct keys in this sketch.
Examples found in repository?
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
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}Sourcepub fn get_lower_bound(&self, num_std_dev: u8) -> Result<f64, SketchError>
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.
Sourcepub fn get_upper_bound(&self, num_std_dev: u8) -> Result<f64, SketchError>
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.
Sourcepub fn is_estimation_mode(&self) -> bool
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).
Sourcepub fn is_ordered(&self) -> bool
pub fn is_ordered(&self) -> bool
Returns true if this sketch’s retained entries are sorted by hash
value.
Sourcepub fn get_theta(&self) -> f64
pub fn get_theta(&self) -> f64
Returns the current theta threshold (1.0 if not in estimation mode).
Examples found in repository?
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}Sourcepub fn get_num_retained(&self) -> u32
pub fn get_num_retained(&self) -> u32
Returns the number of entries retained by this sketch.
Examples found in repository?
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}Sourcepub fn get_num_values(&self) -> u8
pub fn get_num_values(&self) -> u8
Returns the fixed number of f64 values each retained entry carries.
Examples found in repository?
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}Sourcepub fn entries(&self) -> impl Iterator<Item = (u64, Vec<f64>)>
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?
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
impl ArrayOfDoublesInput for CompactArrayOfDoublesSketch
Source§fn get_num_values(&self) -> u8
fn get_num_values(&self) -> u8
f64 values each of this sketch’s retained
entries carries. Set operations require all operands to agree.