pub struct ArrayOfDoublesIntersection { /* private fields */ }Expand description
Computes the intersection of ArrayOfDoubles sketches fed via
Self::update. Values are summed per index for keys present in every
input.
Unlike super::ArrayOfDoublesUnion there is no builder — upstream’s
array_of_doubles_intersection has a plain constructor, and the
intersecting universe is defined entirely by the sketches passed to
update. Only num_values (which the combine policy needs at
construction time) must be supplied up front.
Upstream ships no default combine policy for this type; v1 uses sum-on-collision, mirroring the union’s policy. Additional policies (min/max, etc.) can be added later without changing this type’s shape.
Implementations§
Source§impl ArrayOfDoublesIntersection
impl ArrayOfDoublesIntersection
Sourcepub fn new(num_values: u8) -> Result<Self, SketchError>
pub fn new(num_values: u8) -> Result<Self, SketchError>
Creates a new intersection accumulator for sketches carrying
num_values values per entry, with no result yet — call
Self::update at least once before Self::get_result.
Returns SketchError::InvalidConfig if num_values is 0.
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 update(
&mut self,
input: &impl ArrayOfDoublesInput,
) -> Result<(), SketchError>
pub fn update( &mut self, input: &impl ArrayOfDoublesInput, ) -> Result<(), SketchError>
Narrows this intersection’s running result to also require membership in the given sketch. The first call establishes the initial universe; each subsequent call intersects further.
Returns SketchError::InvalidConfig if the sketch’s num_values
differs from this intersection’s — upstream does not validate this
itself, and mismatched widths would read and write out of bounds.
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_result(
&self,
ordered: bool,
) -> Result<CompactArrayOfDoublesSketch, SketchError>
pub fn get_result( &self, ordered: bool, ) -> Result<CompactArrayOfDoublesSketch, SketchError>
Returns the current intersection result as a
CompactArrayOfDoublesSketch, or
SketchError::EmptyIntersection if Self::update has never been
called. If ordered is true, the result’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}Sourcepub fn has_result(&self) -> bool
pub fn has_result(&self) -> bool
Returns true if Self::update has been called at least once.
Sourcepub fn get_num_values(&self) -> u8
pub fn get_num_values(&self) -> u8
Returns the fixed number of f64 values per entry this intersection
was created with. Every input passed to Self::update must match it.