pub struct CpcSketch { /* private fields */ }Expand description
A CPC (Compressed Probabilistic Counting) sketch: estimates the number
of distinct items added via update_*. Unlike Theta’s ThetaSketch,
there is no separate compact/wrapped variant — this single type is
both the mutable/update type and the serializable type, since CPC’s
serialized form is always compressed by construction. Build one with
CpcSketchBuilder.
Implementations§
Source§impl CpcSketch
impl CpcSketch
Sourcepub fn deserialize(bytes: &[u8]) -> Result<Self, SketchError>
pub fn deserialize(bytes: &[u8]) -> Result<Self, SketchError>
Reconstructs a sketch from bytes produced by Self::serialize.
Examples found in repository?
257fn bench_serde(items: u64, reps: usize) {
258 let mut sketch = build();
259 for key in 0..items {
260 sketch.update_u64(key);
261 }
262 let reference = sketch.serialize();
263
264 let mut passes = Vec::with_capacity(reps);
265 for _ in 0..reps {
266 let start = Instant::now();
267 let mut total = 0usize;
268 for _ in 0..SER_CALLS {
269 total += black_box(sketch.serialize()).len();
270 }
271 let elapsed = start.elapsed();
272 black_box(total);
273 passes.push(Pass {
274 elapsed,
275 estimate: sketch.get_estimate(),
276 });
277 }
278 report_bytes("ser", items, SER_CALLS, &passes, reference.len());
279
280 let deserialize = || CpcSketch::deserialize(&reference).expect("the bytes came from serialize");
281 let mut passes = Vec::with_capacity(reps);
282 for _ in 0..reps {
283 let start = Instant::now();
284 let mut total = 0.0;
285 for _ in 0..DESER_CALLS {
286 total += deserialize().get_estimate();
287 }
288 let elapsed = start.elapsed();
289 black_box(total);
290 passes.push(Pass {
291 elapsed,
292 estimate: deserialize().get_estimate(),
293 });
294 }
295 report_bytes("deser", items, DESER_CALLS, &passes, reference.len());
296}More examples
8fn main() {
9 // A sketch estimates the number of distinct items seen, using bounded
10 // memory regardless of how many items are added. `lg_k` (4..=26)
11 // trades memory for accuracy: higher values are more accurate but use
12 // more space.
13 let mut visitors_day1 = CpcSketchBuilder::new()
14 .lg_k(11)
15 .build()
16 .expect("valid lg_k");
17 for id in 0..10_000u64 {
18 visitors_day1.update_u64(id);
19 }
20
21 let mut visitors_day2 = CpcSketchBuilder::new()
22 .lg_k(11)
23 .build()
24 .expect("valid lg_k");
25 for id in 5_000..15_000u64 {
26 visitors_day2.update_u64(id);
27 }
28
29 println!(
30 "Day 1 unique visitors (estimate): {:.0}",
31 visitors_day1.get_estimate()
32 );
33 println!(
34 "Day 2 unique visitors (estimate): {:.0}",
35 visitors_day2.get_estimate()
36 );
37 println!(
38 "Day 1, 95% confidence interval: [{:.0}, {:.0}]",
39 visitors_day1.get_lower_bound(2).unwrap(),
40 visitors_day1.get_upper_bound(2).unwrap()
41 );
42
43 // CpcUnion merges multiple sketches into one, e.g. combining per-day
44 // counts into a total distinct count across both days.
45 let mut union = CpcUnionBuilder::new().lg_k(11).build().expect("valid lg_k");
46 union.update(&visitors_day1);
47 union.update(&visitors_day2);
48 let total_unique = union.get_result();
49 println!(
50 "Total unique visitors across both days (true count = 15000): {:.0}",
51 total_unique.get_estimate()
52 );
53
54 // Sketches can be serialized to bytes (e.g. to store or send over the
55 // network) and reconstructed later. CPC's serialized form is always
56 // compressed, so there's a single serialize()/deserialize() pair
57 // (unlike Theta, which has separate compressed/uncompressed formats).
58 let bytes = visitors_day1.serialize();
59 let restored = CpcSketch::deserialize(&bytes).expect("valid sketch bytes");
60 println!(
61 "serialized day-1 sketch to {} bytes and restored successfully (estimate {:.0})",
62 bytes.len(),
63 restored.get_estimate()
64 );
65}Sourcepub fn update_u64(&mut self, value: u64)
pub fn update_u64(&mut self, value: u64)
Adds a u64 value to the sketch.
Examples found in repository?
198fn bench_distinct(items: u64, reps: usize) {
199 let mut passes = Vec::with_capacity(reps);
200 for _ in 0..reps {
201 let mut sketch = build();
202 let start = Instant::now();
203 for key in 0..items {
204 sketch.update_u64(key);
205 }
206 let elapsed = start.elapsed();
207 passes.push(Pass {
208 elapsed,
209 estimate: sketch.get_estimate(),
210 });
211 }
212 report("distinct", items, &passes);
213}
214
215fn bench_hot(items: u64, reps: usize) {
216 let mut passes = Vec::with_capacity(reps);
217 for _ in 0..reps {
218 let mut sketch = build();
219 let start = Instant::now();
220 for i in 0..items {
221 sketch.update_u64(i % HOT_KEY_SPACE);
222 }
223 let elapsed = start.elapsed();
224 passes.push(Pass {
225 elapsed,
226 estimate: sketch.get_estimate(),
227 });
228 }
229 report("hot", items, &passes);
230}
231
232fn bench_str(items: u64, reps: usize) {
233 let keys = string_keys();
234 let mut passes = Vec::with_capacity(reps);
235 for _ in 0..reps {
236 let mut sketch = build();
237 let start = Instant::now();
238 for i in 0..items {
239 sketch.update_str(&keys[(i % STR_KEY_SPACE) as usize]);
240 }
241 let elapsed = start.elapsed();
242 passes.push(Pass {
243 elapsed,
244 estimate: sketch.get_estimate(),
245 });
246 }
247 report("str", items, &passes);
248}
249
250/// Serialization, measured per call rather than per item: its cost tracks the
251/// serialized size, which at `lg_k = 12` is the same at every ladder rung.
252///
253/// The sketch is built once and shared by both directions and every rep.
254/// Serializing does not mutate it, so unlike the update scenarios there is no
255/// state that a second rep would find already dirtied -- and rebuilding at the
256/// 100M rung would cost more than the measurement itself.
257fn bench_serde(items: u64, reps: usize) {
258 let mut sketch = build();
259 for key in 0..items {
260 sketch.update_u64(key);
261 }
262 let reference = sketch.serialize();
263
264 let mut passes = Vec::with_capacity(reps);
265 for _ in 0..reps {
266 let start = Instant::now();
267 let mut total = 0usize;
268 for _ in 0..SER_CALLS {
269 total += black_box(sketch.serialize()).len();
270 }
271 let elapsed = start.elapsed();
272 black_box(total);
273 passes.push(Pass {
274 elapsed,
275 estimate: sketch.get_estimate(),
276 });
277 }
278 report_bytes("ser", items, SER_CALLS, &passes, reference.len());
279
280 let deserialize = || CpcSketch::deserialize(&reference).expect("the bytes came from serialize");
281 let mut passes = Vec::with_capacity(reps);
282 for _ in 0..reps {
283 let start = Instant::now();
284 let mut total = 0.0;
285 for _ in 0..DESER_CALLS {
286 total += deserialize().get_estimate();
287 }
288 let elapsed = start.elapsed();
289 black_box(total);
290 passes.push(Pass {
291 elapsed,
292 estimate: deserialize().get_estimate(),
293 });
294 }
295 report_bytes("deser", items, DESER_CALLS, &passes, reference.len());
296}More examples
8fn main() {
9 // A sketch estimates the number of distinct items seen, using bounded
10 // memory regardless of how many items are added. `lg_k` (4..=26)
11 // trades memory for accuracy: higher values are more accurate but use
12 // more space.
13 let mut visitors_day1 = CpcSketchBuilder::new()
14 .lg_k(11)
15 .build()
16 .expect("valid lg_k");
17 for id in 0..10_000u64 {
18 visitors_day1.update_u64(id);
19 }
20
21 let mut visitors_day2 = CpcSketchBuilder::new()
22 .lg_k(11)
23 .build()
24 .expect("valid lg_k");
25 for id in 5_000..15_000u64 {
26 visitors_day2.update_u64(id);
27 }
28
29 println!(
30 "Day 1 unique visitors (estimate): {:.0}",
31 visitors_day1.get_estimate()
32 );
33 println!(
34 "Day 2 unique visitors (estimate): {:.0}",
35 visitors_day2.get_estimate()
36 );
37 println!(
38 "Day 1, 95% confidence interval: [{:.0}, {:.0}]",
39 visitors_day1.get_lower_bound(2).unwrap(),
40 visitors_day1.get_upper_bound(2).unwrap()
41 );
42
43 // CpcUnion merges multiple sketches into one, e.g. combining per-day
44 // counts into a total distinct count across both days.
45 let mut union = CpcUnionBuilder::new().lg_k(11).build().expect("valid lg_k");
46 union.update(&visitors_day1);
47 union.update(&visitors_day2);
48 let total_unique = union.get_result();
49 println!(
50 "Total unique visitors across both days (true count = 15000): {:.0}",
51 total_unique.get_estimate()
52 );
53
54 // Sketches can be serialized to bytes (e.g. to store or send over the
55 // network) and reconstructed later. CPC's serialized form is always
56 // compressed, so there's a single serialize()/deserialize() pair
57 // (unlike Theta, which has separate compressed/uncompressed formats).
58 let bytes = visitors_day1.serialize();
59 let restored = CpcSketch::deserialize(&bytes).expect("valid sketch bytes");
60 println!(
61 "serialized day-1 sketch to {} bytes and restored successfully (estimate {:.0})",
62 bytes.len(),
63 restored.get_estimate()
64 );
65}Sourcepub fn update_i64(&mut self, value: i64)
pub fn update_i64(&mut self, value: i64)
Adds an i64 value to the sketch.
Sourcepub fn update_u32(&mut self, value: u32)
pub fn update_u32(&mut self, value: u32)
Adds a u32 value to the sketch.
Sourcepub fn update_i32(&mut self, value: i32)
pub fn update_i32(&mut self, value: i32)
Adds an i32 value to the sketch.
Sourcepub fn update_u16(&mut self, value: u16)
pub fn update_u16(&mut self, value: u16)
Adds a u16 value to the sketch.
Sourcepub fn update_i16(&mut self, value: i16)
pub fn update_i16(&mut self, value: i16)
Adds an i16 value to the sketch.
Sourcepub fn update_f64(&mut self, value: f64)
pub fn update_f64(&mut self, value: f64)
Adds an f64 value to the sketch.
Sourcepub fn update_f32(&mut self, value: f32)
pub fn update_f32(&mut self, value: f32)
Adds an f32 value to the sketch.
Sourcepub fn update_str(&mut self, value: &str)
pub fn update_str(&mut self, value: &str)
Adds a string value to the sketch.
Examples found in repository?
232fn bench_str(items: u64, reps: usize) {
233 let keys = string_keys();
234 let mut passes = Vec::with_capacity(reps);
235 for _ in 0..reps {
236 let mut sketch = build();
237 let start = Instant::now();
238 for i in 0..items {
239 sketch.update_str(&keys[(i % STR_KEY_SPACE) as usize]);
240 }
241 let elapsed = start.elapsed();
242 passes.push(Pass {
243 elapsed,
244 estimate: sketch.get_estimate(),
245 });
246 }
247 report("str", items, &passes);
248}Sourcepub fn update_bytes(&mut self, value: &[u8])
pub fn update_bytes(&mut self, value: &[u8])
Adds an arbitrary byte slice to the sketch.
Sourcepub fn get_estimate(&self) -> f64
pub fn get_estimate(&self) -> f64
Returns the current estimate of the number of distinct items added.
Examples found in repository?
198fn bench_distinct(items: u64, reps: usize) {
199 let mut passes = Vec::with_capacity(reps);
200 for _ in 0..reps {
201 let mut sketch = build();
202 let start = Instant::now();
203 for key in 0..items {
204 sketch.update_u64(key);
205 }
206 let elapsed = start.elapsed();
207 passes.push(Pass {
208 elapsed,
209 estimate: sketch.get_estimate(),
210 });
211 }
212 report("distinct", items, &passes);
213}
214
215fn bench_hot(items: u64, reps: usize) {
216 let mut passes = Vec::with_capacity(reps);
217 for _ in 0..reps {
218 let mut sketch = build();
219 let start = Instant::now();
220 for i in 0..items {
221 sketch.update_u64(i % HOT_KEY_SPACE);
222 }
223 let elapsed = start.elapsed();
224 passes.push(Pass {
225 elapsed,
226 estimate: sketch.get_estimate(),
227 });
228 }
229 report("hot", items, &passes);
230}
231
232fn bench_str(items: u64, reps: usize) {
233 let keys = string_keys();
234 let mut passes = Vec::with_capacity(reps);
235 for _ in 0..reps {
236 let mut sketch = build();
237 let start = Instant::now();
238 for i in 0..items {
239 sketch.update_str(&keys[(i % STR_KEY_SPACE) as usize]);
240 }
241 let elapsed = start.elapsed();
242 passes.push(Pass {
243 elapsed,
244 estimate: sketch.get_estimate(),
245 });
246 }
247 report("str", items, &passes);
248}
249
250/// Serialization, measured per call rather than per item: its cost tracks the
251/// serialized size, which at `lg_k = 12` is the same at every ladder rung.
252///
253/// The sketch is built once and shared by both directions and every rep.
254/// Serializing does not mutate it, so unlike the update scenarios there is no
255/// state that a second rep would find already dirtied -- and rebuilding at the
256/// 100M rung would cost more than the measurement itself.
257fn bench_serde(items: u64, reps: usize) {
258 let mut sketch = build();
259 for key in 0..items {
260 sketch.update_u64(key);
261 }
262 let reference = sketch.serialize();
263
264 let mut passes = Vec::with_capacity(reps);
265 for _ in 0..reps {
266 let start = Instant::now();
267 let mut total = 0usize;
268 for _ in 0..SER_CALLS {
269 total += black_box(sketch.serialize()).len();
270 }
271 let elapsed = start.elapsed();
272 black_box(total);
273 passes.push(Pass {
274 elapsed,
275 estimate: sketch.get_estimate(),
276 });
277 }
278 report_bytes("ser", items, SER_CALLS, &passes, reference.len());
279
280 let deserialize = || CpcSketch::deserialize(&reference).expect("the bytes came from serialize");
281 let mut passes = Vec::with_capacity(reps);
282 for _ in 0..reps {
283 let start = Instant::now();
284 let mut total = 0.0;
285 for _ in 0..DESER_CALLS {
286 total += deserialize().get_estimate();
287 }
288 let elapsed = start.elapsed();
289 black_box(total);
290 passes.push(Pass {
291 elapsed,
292 estimate: deserialize().get_estimate(),
293 });
294 }
295 report_bytes("deser", items, DESER_CALLS, &passes, reference.len());
296}More examples
8fn main() {
9 // A sketch estimates the number of distinct items seen, using bounded
10 // memory regardless of how many items are added. `lg_k` (4..=26)
11 // trades memory for accuracy: higher values are more accurate but use
12 // more space.
13 let mut visitors_day1 = CpcSketchBuilder::new()
14 .lg_k(11)
15 .build()
16 .expect("valid lg_k");
17 for id in 0..10_000u64 {
18 visitors_day1.update_u64(id);
19 }
20
21 let mut visitors_day2 = CpcSketchBuilder::new()
22 .lg_k(11)
23 .build()
24 .expect("valid lg_k");
25 for id in 5_000..15_000u64 {
26 visitors_day2.update_u64(id);
27 }
28
29 println!(
30 "Day 1 unique visitors (estimate): {:.0}",
31 visitors_day1.get_estimate()
32 );
33 println!(
34 "Day 2 unique visitors (estimate): {:.0}",
35 visitors_day2.get_estimate()
36 );
37 println!(
38 "Day 1, 95% confidence interval: [{:.0}, {:.0}]",
39 visitors_day1.get_lower_bound(2).unwrap(),
40 visitors_day1.get_upper_bound(2).unwrap()
41 );
42
43 // CpcUnion merges multiple sketches into one, e.g. combining per-day
44 // counts into a total distinct count across both days.
45 let mut union = CpcUnionBuilder::new().lg_k(11).build().expect("valid lg_k");
46 union.update(&visitors_day1);
47 union.update(&visitors_day2);
48 let total_unique = union.get_result();
49 println!(
50 "Total unique visitors across both days (true count = 15000): {:.0}",
51 total_unique.get_estimate()
52 );
53
54 // Sketches can be serialized to bytes (e.g. to store or send over the
55 // network) and reconstructed later. CPC's serialized form is always
56 // compressed, so there's a single serialize()/deserialize() pair
57 // (unlike Theta, which has separate compressed/uncompressed formats).
58 let bytes = visitors_day1.serialize();
59 let restored = CpcSketch::deserialize(&bytes).expect("valid sketch bytes");
60 println!(
61 "serialized day-1 sketch to {} bytes and restored successfully (estimate {:.0})",
62 bytes.len(),
63 restored.get_estimate()
64 );
65}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 — upstream calls this parameter kappa). Returns
SketchError::InvalidConfig for any other value.
Examples found in repository?
8fn main() {
9 // A sketch estimates the number of distinct items seen, using bounded
10 // memory regardless of how many items are added. `lg_k` (4..=26)
11 // trades memory for accuracy: higher values are more accurate but use
12 // more space.
13 let mut visitors_day1 = CpcSketchBuilder::new()
14 .lg_k(11)
15 .build()
16 .expect("valid lg_k");
17 for id in 0..10_000u64 {
18 visitors_day1.update_u64(id);
19 }
20
21 let mut visitors_day2 = CpcSketchBuilder::new()
22 .lg_k(11)
23 .build()
24 .expect("valid lg_k");
25 for id in 5_000..15_000u64 {
26 visitors_day2.update_u64(id);
27 }
28
29 println!(
30 "Day 1 unique visitors (estimate): {:.0}",
31 visitors_day1.get_estimate()
32 );
33 println!(
34 "Day 2 unique visitors (estimate): {:.0}",
35 visitors_day2.get_estimate()
36 );
37 println!(
38 "Day 1, 95% confidence interval: [{:.0}, {:.0}]",
39 visitors_day1.get_lower_bound(2).unwrap(),
40 visitors_day1.get_upper_bound(2).unwrap()
41 );
42
43 // CpcUnion merges multiple sketches into one, e.g. combining per-day
44 // counts into a total distinct count across both days.
45 let mut union = CpcUnionBuilder::new().lg_k(11).build().expect("valid lg_k");
46 union.update(&visitors_day1);
47 union.update(&visitors_day2);
48 let total_unique = union.get_result();
49 println!(
50 "Total unique visitors across both days (true count = 15000): {:.0}",
51 total_unique.get_estimate()
52 );
53
54 // Sketches can be serialized to bytes (e.g. to store or send over the
55 // network) and reconstructed later. CPC's serialized form is always
56 // compressed, so there's a single serialize()/deserialize() pair
57 // (unlike Theta, which has separate compressed/uncompressed formats).
58 let bytes = visitors_day1.serialize();
59 let restored = CpcSketch::deserialize(&bytes).expect("valid sketch bytes");
60 println!(
61 "serialized day-1 sketch to {} bytes and restored successfully (estimate {:.0})",
62 bytes.len(),
63 restored.get_estimate()
64 );
65}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.
Examples found in repository?
8fn main() {
9 // A sketch estimates the number of distinct items seen, using bounded
10 // memory regardless of how many items are added. `lg_k` (4..=26)
11 // trades memory for accuracy: higher values are more accurate but use
12 // more space.
13 let mut visitors_day1 = CpcSketchBuilder::new()
14 .lg_k(11)
15 .build()
16 .expect("valid lg_k");
17 for id in 0..10_000u64 {
18 visitors_day1.update_u64(id);
19 }
20
21 let mut visitors_day2 = CpcSketchBuilder::new()
22 .lg_k(11)
23 .build()
24 .expect("valid lg_k");
25 for id in 5_000..15_000u64 {
26 visitors_day2.update_u64(id);
27 }
28
29 println!(
30 "Day 1 unique visitors (estimate): {:.0}",
31 visitors_day1.get_estimate()
32 );
33 println!(
34 "Day 2 unique visitors (estimate): {:.0}",
35 visitors_day2.get_estimate()
36 );
37 println!(
38 "Day 1, 95% confidence interval: [{:.0}, {:.0}]",
39 visitors_day1.get_lower_bound(2).unwrap(),
40 visitors_day1.get_upper_bound(2).unwrap()
41 );
42
43 // CpcUnion merges multiple sketches into one, e.g. combining per-day
44 // counts into a total distinct count across both days.
45 let mut union = CpcUnionBuilder::new().lg_k(11).build().expect("valid lg_k");
46 union.update(&visitors_day1);
47 union.update(&visitors_day2);
48 let total_unique = union.get_result();
49 println!(
50 "Total unique visitors across both days (true count = 15000): {:.0}",
51 total_unique.get_estimate()
52 );
53
54 // Sketches can be serialized to bytes (e.g. to store or send over the
55 // network) and reconstructed later. CPC's serialized form is always
56 // compressed, so there's a single serialize()/deserialize() pair
57 // (unlike Theta, which has separate compressed/uncompressed formats).
58 let bytes = visitors_day1.serialize();
59 let restored = CpcSketch::deserialize(&bytes).expect("valid sketch bytes");
60 println!(
61 "serialized day-1 sketch to {} bytes and restored successfully (estimate {:.0})",
62 bytes.len(),
63 restored.get_estimate()
64 );
65}Sourcepub fn to_string_summary(&self) -> String
pub fn to_string_summary(&self) -> String
Returns a human-readable, multi-line summary of this sketch’s internal state — useful for debugging, not for parsing.
Sourcepub fn serialize(&self) -> Vec<u8> ⓘ
pub fn serialize(&self) -> Vec<u8> ⓘ
Serializes this sketch to bytes. CPC’s on-wire format is always compressed, so there is only this one serialization method (unlike Theta’s separate compressed/uncompressed formats).
Examples found in repository?
257fn bench_serde(items: u64, reps: usize) {
258 let mut sketch = build();
259 for key in 0..items {
260 sketch.update_u64(key);
261 }
262 let reference = sketch.serialize();
263
264 let mut passes = Vec::with_capacity(reps);
265 for _ in 0..reps {
266 let start = Instant::now();
267 let mut total = 0usize;
268 for _ in 0..SER_CALLS {
269 total += black_box(sketch.serialize()).len();
270 }
271 let elapsed = start.elapsed();
272 black_box(total);
273 passes.push(Pass {
274 elapsed,
275 estimate: sketch.get_estimate(),
276 });
277 }
278 report_bytes("ser", items, SER_CALLS, &passes, reference.len());
279
280 let deserialize = || CpcSketch::deserialize(&reference).expect("the bytes came from serialize");
281 let mut passes = Vec::with_capacity(reps);
282 for _ in 0..reps {
283 let start = Instant::now();
284 let mut total = 0.0;
285 for _ in 0..DESER_CALLS {
286 total += deserialize().get_estimate();
287 }
288 let elapsed = start.elapsed();
289 black_box(total);
290 passes.push(Pass {
291 elapsed,
292 estimate: deserialize().get_estimate(),
293 });
294 }
295 report_bytes("deser", items, DESER_CALLS, &passes, reference.len());
296}More examples
8fn main() {
9 // A sketch estimates the number of distinct items seen, using bounded
10 // memory regardless of how many items are added. `lg_k` (4..=26)
11 // trades memory for accuracy: higher values are more accurate but use
12 // more space.
13 let mut visitors_day1 = CpcSketchBuilder::new()
14 .lg_k(11)
15 .build()
16 .expect("valid lg_k");
17 for id in 0..10_000u64 {
18 visitors_day1.update_u64(id);
19 }
20
21 let mut visitors_day2 = CpcSketchBuilder::new()
22 .lg_k(11)
23 .build()
24 .expect("valid lg_k");
25 for id in 5_000..15_000u64 {
26 visitors_day2.update_u64(id);
27 }
28
29 println!(
30 "Day 1 unique visitors (estimate): {:.0}",
31 visitors_day1.get_estimate()
32 );
33 println!(
34 "Day 2 unique visitors (estimate): {:.0}",
35 visitors_day2.get_estimate()
36 );
37 println!(
38 "Day 1, 95% confidence interval: [{:.0}, {:.0}]",
39 visitors_day1.get_lower_bound(2).unwrap(),
40 visitors_day1.get_upper_bound(2).unwrap()
41 );
42
43 // CpcUnion merges multiple sketches into one, e.g. combining per-day
44 // counts into a total distinct count across both days.
45 let mut union = CpcUnionBuilder::new().lg_k(11).build().expect("valid lg_k");
46 union.update(&visitors_day1);
47 union.update(&visitors_day2);
48 let total_unique = union.get_result();
49 println!(
50 "Total unique visitors across both days (true count = 15000): {:.0}",
51 total_unique.get_estimate()
52 );
53
54 // Sketches can be serialized to bytes (e.g. to store or send over the
55 // network) and reconstructed later. CPC's serialized form is always
56 // compressed, so there's a single serialize()/deserialize() pair
57 // (unlike Theta, which has separate compressed/uncompressed formats).
58 let bytes = visitors_day1.serialize();
59 let restored = CpcSketch::deserialize(&bytes).expect("valid sketch bytes");
60 println!(
61 "serialized day-1 sketch to {} bytes and restored successfully (estimate {:.0})",
62 bytes.len(),
63 restored.get_estimate()
64 );
65}