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
impl ArrayOfDoublesSketch
Sourcepub fn update_u64(
&mut self,
key: u64,
values: &[f64],
) -> Result<(), SketchError>
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?
58fn bench_distinct(items: u64) {
59 let mut sketch = ArrayOfDoublesSketchBuilder::new()
60 .lg_k(LG_K)
61 .num_values(NUM_VALUES)
62 .build()
63 .expect("builder rejected fixed valid parameters");
64
65 let start = Instant::now();
66 for key in 0..items {
67 sketch
68 .update_u64(key, &VALUES)
69 .expect("update rejected a correctly-sized value slice");
70 }
71 let elapsed = start.elapsed();
72
73 // Reading the estimate keeps the loop above from being optimised out.
74 report("distinct", items, elapsed, sketch.get_estimate());
75}
76
77fn bench_hot(items: u64) {
78 let mut sketch = ArrayOfDoublesSketchBuilder::new()
79 .lg_k(LG_K)
80 .num_values(NUM_VALUES)
81 .build()
82 .expect("builder rejected fixed valid parameters");
83
84 let start = Instant::now();
85 for i in 0..items {
86 sketch
87 .update_u64(i % HOT_KEY_SPACE, &VALUES)
88 .expect("update rejected a correctly-sized value slice");
89 }
90 let elapsed = start.elapsed();
91
92 report("hot", items, elapsed, sketch.get_estimate());
93}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 update_i64(
&mut self,
key: i64,
values: &[f64],
) -> Result<(), SketchError>
pub fn update_i64( &mut self, key: i64, values: &[f64], ) -> Result<(), SketchError>
Adds an i64 key with its associated values. See Self::update_u64.
Sourcepub fn update_u32(
&mut self,
key: u32,
values: &[f64],
) -> Result<(), SketchError>
pub fn update_u32( &mut self, key: u32, values: &[f64], ) -> Result<(), SketchError>
Adds a u32 key with its associated values. See Self::update_u64.
Sourcepub fn update_i32(
&mut self,
key: i32,
values: &[f64],
) -> Result<(), SketchError>
pub fn update_i32( &mut self, key: i32, values: &[f64], ) -> Result<(), SketchError>
Adds an i32 key with its associated values. See Self::update_u64.
Sourcepub fn update_u16(
&mut self,
key: u16,
values: &[f64],
) -> Result<(), SketchError>
pub fn update_u16( &mut self, key: u16, values: &[f64], ) -> Result<(), SketchError>
Adds a u16 key with its associated values. See Self::update_u64.
Sourcepub fn update_i16(
&mut self,
key: i16,
values: &[f64],
) -> Result<(), SketchError>
pub fn update_i16( &mut self, key: i16, values: &[f64], ) -> Result<(), SketchError>
Adds an i16 key with its associated values. See Self::update_u64.
Sourcepub fn update_u8(&mut self, key: u8, values: &[f64]) -> Result<(), SketchError>
pub fn update_u8(&mut self, key: u8, values: &[f64]) -> Result<(), SketchError>
Adds a u8 key with its associated values. See Self::update_u64.
Sourcepub fn update_i8(&mut self, key: i8, values: &[f64]) -> Result<(), SketchError>
pub fn update_i8(&mut self, key: i8, values: &[f64]) -> Result<(), SketchError>
Adds an i8 key with its associated values. See Self::update_u64.
Sourcepub fn update_f64(
&mut self,
key: f64,
values: &[f64],
) -> Result<(), SketchError>
pub fn update_f64( &mut self, key: f64, values: &[f64], ) -> Result<(), SketchError>
Adds an f64 key with its associated values. See Self::update_u64.
Sourcepub fn update_str(
&mut self,
key: &str,
values: &[f64],
) -> Result<(), SketchError>
pub fn update_str( &mut self, key: &str, values: &[f64], ) -> Result<(), SketchError>
Adds a string key with its associated values. See Self::update_u64.
Sourcepub fn update_bytes(
&mut self,
key: &[u8],
values: &[f64],
) -> Result<(), SketchError>
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.
Sourcepub fn trim(&mut self)
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.
Sourcepub fn reset(&mut self)
pub fn reset(&mut self)
Resets this sketch to its initial, empty state. num_values is
preserved.
Sourcepub fn get_estimate(&self) -> f64
pub fn get_estimate(&self) -> f64
Returns the current estimate of the number of distinct keys added.
Examples found in repository?
58fn bench_distinct(items: u64) {
59 let mut sketch = ArrayOfDoublesSketchBuilder::new()
60 .lg_k(LG_K)
61 .num_values(NUM_VALUES)
62 .build()
63 .expect("builder rejected fixed valid parameters");
64
65 let start = Instant::now();
66 for key in 0..items {
67 sketch
68 .update_u64(key, &VALUES)
69 .expect("update rejected a correctly-sized value slice");
70 }
71 let elapsed = start.elapsed();
72
73 // Reading the estimate keeps the loop above from being optimised out.
74 report("distinct", items, elapsed, sketch.get_estimate());
75}
76
77fn bench_hot(items: u64) {
78 let mut sketch = ArrayOfDoublesSketchBuilder::new()
79 .lg_k(LG_K)
80 .num_values(NUM_VALUES)
81 .build()
82 .expect("builder rejected fixed valid parameters");
83
84 let start = Instant::now();
85 for i in 0..items {
86 sketch
87 .update_u64(i % HOT_KEY_SPACE, &VALUES)
88 .expect("update rejected a correctly-sized value slice");
89 }
90 let elapsed = start.elapsed();
91
92 report("hot", items, elapsed, sketch.get_estimate());
93}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, 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.
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 Self::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. it has begun sampling and 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 until sampling begins).
Sourcepub fn get_num_retained(&self) -> u32
pub fn get_num_retained(&self) -> u32
Returns the number of entries currently retained by this sketch.
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,
as configured at build time.
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().
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.
Sourcepub fn compact(&self, ordered: bool) -> CompactArrayOfDoublesSketch
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?
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
impl ArrayOfDoublesInput for ArrayOfDoublesSketch
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.