From 1c3e45b6007bca9cf50b3d8c894102aad0b72607 Mon Sep 17 00:00:00 2001 From: Xinyao Zhang <43081360+zhangxinyao88@users.noreply.github.com> Date: Wed, 26 Aug 2026 23:09:54 -0400 Subject: [PATCH 1/2] feat: add Display summaries for HLL and CPC --- CHANGELOG.md | 1 + datasketches/src/cpc/sketch.rs | 12 ++++ datasketches/src/cpc/union.rs | 20 +++++++ datasketches/src/hll/sketch.rs | 32 +++++++++++ datasketches/src/hll/union.rs | 20 +++++++ tests-integration/tests/cpc_test/display.rs | 60 ++++++++++++++++++++ tests-integration/tests/cpc_test/main.rs | 1 + tests-integration/tests/hll_test/display.rs | 61 +++++++++++++++++++++ tests-integration/tests/hll_test/main.rs | 1 + 9 files changed, 208 insertions(+) create mode 100644 tests-integration/tests/cpc_test/display.rs create mode 100644 tests-integration/tests/hll_test/display.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 89e3b797..4b809847 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ All significant changes to this project will be documented in this file. * `TDigest` can now be serialized and deserialized directly without converting through `TDigestMut` at the call site. * Add Relative Error Quantiles (REQ) sketches behind the `req` feature, including configurable high- or low-rank accuracy, rank, quantile, PMF, and CDF queries, typed rank confidence bounds, merging, totally ordered custom item types, the `ReqFloat` adapter for non-NaN floating-point values, and C++/Java-compatible serialization. +* Add human-readable `Display` summaries for HLL and CPC sketches and unions. ### Performance improvements diff --git a/datasketches/src/cpc/sketch.rs b/datasketches/src/cpc/sketch.rs index 8599e46d..8d147677 100644 --- a/datasketches/src/cpc/sketch.rs +++ b/datasketches/src/cpc/sketch.rs @@ -15,6 +15,7 @@ // specific language governing permissions and limitations // under the License. +use std::fmt; use std::hash::Hash; use crate::codec::SketchBytes; @@ -472,6 +473,17 @@ impl CpcSketch { } } +impl fmt::Display for CpcSketch { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + writeln!(f, "CPC Sketch Summary:")?; + writeln!(f, " flavor : {:?}", self.flavor())?; + writeln!(f, " lg k : {}", self.lg_k())?; + writeln!(f, " merged : {}", self.merge_flag)?; + writeln!(f, " estimate : {}", self.estimate())?; + writeln!(f, " num coupons : {}", self.num_coupons) + } +} + impl CpcSketch { /// Serializes this `CpcSketch` to bytes. pub fn serialize(&self) -> Vec { diff --git a/datasketches/src/cpc/union.rs b/datasketches/src/cpc/union.rs index 51742a30..3bcb59ae 100644 --- a/datasketches/src/cpc/union.rs +++ b/datasketches/src/cpc/union.rs @@ -61,6 +61,8 @@ //! which requires doing some extra work to figure out the values of num_coupons, offset, //! first_interesting_column, and kxp. +use std::fmt; + use crate::cpc::CpcSketch; use crate::cpc::DEFAULT_LG_K; use crate::cpc::Flavor; @@ -356,6 +358,24 @@ impl CpcUnion { } } +impl fmt::Display for CpcUnion { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let state = match &self.state { + UnionState::Accumulator(_) => "Accumulator", + UnionState::BitMatrix(_) => "BitMatrix", + }; + let num_coupons = match &self.state { + UnionState::Accumulator(sketch) => sketch.num_coupons, + UnionState::BitMatrix(matrix) => count_bits_set_in_matrix(matrix), + }; + + writeln!(f, "CPC Union Summary:")?; + writeln!(f, " lg k : {}", self.lg_k())?; + writeln!(f, " state : {state}")?; + writeln!(f, " num coupons : {num_coupons}") + } +} + fn or_window_into_matrix( dst_matrix: &mut [u64], dst_lg_k: u8, diff --git a/datasketches/src/hll/sketch.rs b/datasketches/src/hll/sketch.rs index 4e0ed719..efba7d19 100644 --- a/datasketches/src/hll/sketch.rs +++ b/datasketches/src/hll/sketch.rs @@ -20,6 +20,7 @@ //! This module provides the main [`HllSketch`] struct, which is the primary interface //! for creating and using HLL sketches for cardinality estimation. +use std::fmt; use std::hash::Hash; use crate::codec::SketchSlice; @@ -459,6 +460,37 @@ impl HllSketch { } } +impl fmt::Display for HllSketch { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let target_type = match self.target_type() { + HllType::Hll4 => "Hll4", + HllType::Hll6 => "Hll6", + HllType::Hll8 => "Hll8", + }; + let current_mode = match &self.mode { + Mode::List { .. } => "List", + Mode::Set { .. } => "Set", + Mode::Array4(_) | Mode::Array6(_) | Mode::Array8(_) => "Hll", + }; + + writeln!(f, "HLL Sketch Summary:")?; + writeln!(f, " lg config k : {}", self.lg_config_k())?; + writeln!(f, " target type : {target_type}")?; + writeln!(f, " current mode : {current_mode}")?; + writeln!( + f, + " lower bound : {}", + self.lower_bound(NumStdDev::One) + )?; + writeln!(f, " estimate : {}", self.estimate())?; + writeln!( + f, + " upper bound : {}", + self.upper_bound(NumStdDev::One) + ) + } +} + fn promote_container_to_set(container: &Container, hll_type: HllType) -> Mode { let mut set = HashSet::default(); for coupon in container.iter() { diff --git a/datasketches/src/hll/union.rs b/datasketches/src/hll/union.rs index eb983829..5c8372ad 100644 --- a/datasketches/src/hll/union.rs +++ b/datasketches/src/hll/union.rs @@ -28,6 +28,7 @@ //! * Different modes (List, Set, Array4/6/8) //! * Different target HLL types +use std::fmt; use std::hash::Hash; use crate::common::NumStdDev; @@ -338,6 +339,25 @@ impl HllUnion { } } +impl fmt::Display for HllUnion { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + writeln!(f, "HLL Union Summary:")?; + writeln!(f, " lg max k : {}", self.lg_max_k())?; + writeln!(f, " lg config k : {}", self.lg_config_k())?; + writeln!( + f, + " lower bound : {}", + self.lower_bound(NumStdDev::One) + )?; + writeln!(f, " estimate : {}", self.estimate())?; + writeln!( + f, + " upper bound : {}", + self.upper_bound(NumStdDev::One) + ) + } +} + /// Convert a coupon mode (List or Set) to Hll8 target type fn convert_coupon_mode_to_hll8(src_mode: &Mode, src_lg_k: u8) -> HllSketch { match src_mode { diff --git a/tests-integration/tests/cpc_test/display.rs b/tests-integration/tests/cpc_test/display.rs new file mode 100644 index 00000000..a7092f99 --- /dev/null +++ b/tests-integration/tests/cpc_test/display.rs @@ -0,0 +1,60 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +use datasketches::cpc::CpcSketch; +use datasketches::cpc::CpcUnion; +use insta::assert_snapshot; + +#[test] +fn display_empty_sketch() { + let sketch = CpcSketch::new(11).unwrap(); + + assert_snapshot!(sketch, @r" + CPC Sketch Summary: + flavor : Empty + lg k : 11 + merged : false + estimate : 0 + num coupons : 0 + "); +} + +#[test] +fn display_populated_sketch() { + let mut sketch = CpcSketch::new(11).unwrap(); + sketch.update("apple"); + + let summary = sketch.to_string(); + assert!(summary.contains("flavor : Sparse\n")); + assert!(summary.contains("num coupons : 1\n")); + assert!(!summary.contains("estimate : 0\n")); +} + +#[test] +fn display_union() { + let mut sketch = CpcSketch::new(11).unwrap(); + sketch.update("apple"); + let mut union = CpcUnion::new(11).unwrap(); + union.update(&sketch).unwrap(); + + assert_snapshot!(union, @r" + CPC Union Summary: + lg k : 11 + state : Accumulator + num coupons : 1 + "); +} diff --git a/tests-integration/tests/cpc_test/main.rs b/tests-integration/tests/cpc_test/main.rs index 7b98ba98..62bc7f40 100644 --- a/tests-integration/tests/cpc_test/main.rs +++ b/tests-integration/tests/cpc_test/main.rs @@ -16,6 +16,7 @@ // under the License. mod deserialize; +mod display; mod union; mod update; mod wrapper; diff --git a/tests-integration/tests/hll_test/display.rs b/tests-integration/tests/hll_test/display.rs new file mode 100644 index 00000000..d53fa794 --- /dev/null +++ b/tests-integration/tests/hll_test/display.rs @@ -0,0 +1,61 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +use datasketches::hll::HllSketch; +use datasketches::hll::HllType; +use datasketches::hll::HllUnion; +use insta::assert_snapshot; + +#[test] +fn display_empty_sketch() { + let sketch = HllSketch::new(12, HllType::Hll8).unwrap(); + + assert_snapshot!(sketch, @r" + HLL Sketch Summary: + lg config k : 12 + target type : Hll8 + current mode : List + lower bound : 0 + estimate : 0 + upper bound : 0 + "); +} + +#[test] +fn display_populated_sketch() { + let mut sketch = HllSketch::new(10, HllType::Hll4).unwrap(); + for value in 0..1_000 { + sketch.update(value); + } + + let summary = sketch.to_string(); + assert!(summary.contains("target type : Hll4\n")); + assert!(summary.contains("current mode : Hll\n")); + assert!(!summary.contains("estimate : 0\n")); +} + +#[test] +fn display_union() { + let mut union = HllUnion::new(12).unwrap(); + union.update_value("apple"); + + let summary = union.to_string(); + assert!(summary.starts_with("HLL Union Summary:\n")); + assert!(summary.contains("lg max k : 12\n")); + assert!(summary.contains("lg config k : 12\n")); + assert!(!summary.contains("estimate : 0\n")); +} diff --git a/tests-integration/tests/hll_test/main.rs b/tests-integration/tests/hll_test/main.rs index 30fb7dce..954a7847 100644 --- a/tests-integration/tests/hll_test/main.rs +++ b/tests-integration/tests/hll_test/main.rs @@ -16,5 +16,6 @@ // under the License. mod bounds; +mod display; mod union; mod update; From 923213c97e18341bc3ae1b4235740b25ab61ba78 Mon Sep 17 00:00:00 2001 From: Xinyao Zhang <43081360+zhangxinyao88@users.noreply.github.com> Date: Wed, 23 Sep 2026 15:53:27 -0400 Subject: [PATCH 2/2] feat: add diagnostic sketch summaries --- CHANGELOG.md | 2 +- datasketches/src/cpc/sketch.rs | 30 ++++++++++------ datasketches/src/cpc/union.rs | 21 ++++++----- datasketches/src/hll/sketch.rs | 35 +++++++++---------- datasketches/src/hll/union.rs | 34 +++++++++--------- tests-integration/tests/cpc_test/main.rs | 2 +- .../tests/cpc_test/{display.rs => summary.rs} | 12 +++---- tests-integration/tests/hll_test/main.rs | 2 +- .../tests/hll_test/{display.rs => summary.rs} | 12 +++---- 9 files changed, 80 insertions(+), 70 deletions(-) rename tests-integration/tests/cpc_test/{display.rs => summary.rs} (89%) rename tests-integration/tests/hll_test/{display.rs => summary.rs} (90%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b809847..895f7188 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,7 +26,7 @@ All significant changes to this project will be documented in this file. * `TDigest` can now be serialized and deserialized directly without converting through `TDigestMut` at the call site. * Add Relative Error Quantiles (REQ) sketches behind the `req` feature, including configurable high- or low-rank accuracy, rank, quantile, PMF, and CDF queries, typed rank confidence bounds, merging, totally ordered custom item types, the `ReqFloat` adapter for non-NaN floating-point values, and C++/Java-compatible serialization. -* Add human-readable `Display` summaries for HLL and CPC sketches and unions. +* Add diagnostic `summary()` methods for HLL and CPC sketches and unions. ### Performance improvements diff --git a/datasketches/src/cpc/sketch.rs b/datasketches/src/cpc/sketch.rs index 8d147677..3c59820f 100644 --- a/datasketches/src/cpc/sketch.rs +++ b/datasketches/src/cpc/sketch.rs @@ -15,7 +15,6 @@ // specific language governing permissions and limitations // under the License. -use std::fmt; use std::hash::Hash; use crate::codec::SketchBytes; @@ -473,18 +472,27 @@ impl CpcSketch { } } -impl fmt::Display for CpcSketch { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - writeln!(f, "CPC Sketch Summary:")?; - writeln!(f, " flavor : {:?}", self.flavor())?; - writeln!(f, " lg k : {}", self.lg_k())?; - writeln!(f, " merged : {}", self.merge_flag)?; - writeln!(f, " estimate : {}", self.estimate())?; - writeln!(f, " num coupons : {}", self.num_coupons) +impl CpcSketch { + /// Returns a human-readable diagnostic summary. + /// + /// The output is for inspection and debugging. Its format may change and + /// should not be parsed. + pub fn summary(&self) -> String { + format!( + "CPC Sketch Summary:\n\ + \x20\x20flavor : {:?}\n\ + \x20\x20lg k : {}\n\ + \x20\x20merged : {}\n\ + \x20\x20estimate : {}\n\ + \x20\x20num coupons : {}", + self.flavor(), + self.lg_k(), + self.merge_flag, + self.estimate(), + self.num_coupons, + ) } -} -impl CpcSketch { /// Serializes this `CpcSketch` to bytes. pub fn serialize(&self) -> Vec { let flavor = self.flavor(); diff --git a/datasketches/src/cpc/union.rs b/datasketches/src/cpc/union.rs index 3bcb59ae..460b1b28 100644 --- a/datasketches/src/cpc/union.rs +++ b/datasketches/src/cpc/union.rs @@ -61,8 +61,6 @@ //! which requires doing some extra work to figure out the values of num_coupons, offset, //! first_interesting_column, and kxp. -use std::fmt; - use crate::cpc::CpcSketch; use crate::cpc::DEFAULT_LG_K; use crate::cpc::Flavor; @@ -356,10 +354,12 @@ impl CpcUnion { }; size_of::() + heap_size } -} -impl fmt::Display for CpcUnion { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + /// Returns a human-readable diagnostic summary. + /// + /// The output is for inspection and debugging. Its format may change and + /// should not be parsed. + pub fn summary(&self) -> String { let state = match &self.state { UnionState::Accumulator(_) => "Accumulator", UnionState::BitMatrix(_) => "BitMatrix", @@ -369,10 +369,13 @@ impl fmt::Display for CpcUnion { UnionState::BitMatrix(matrix) => count_bits_set_in_matrix(matrix), }; - writeln!(f, "CPC Union Summary:")?; - writeln!(f, " lg k : {}", self.lg_k())?; - writeln!(f, " state : {state}")?; - writeln!(f, " num coupons : {num_coupons}") + format!( + "CPC Union Summary:\n\ + \x20\x20lg k : {}\n\ + \x20\x20state : {state}\n\ + \x20\x20num coupons : {num_coupons}", + self.lg_k(), + ) } } diff --git a/datasketches/src/hll/sketch.rs b/datasketches/src/hll/sketch.rs index efba7d19..ea820a09 100644 --- a/datasketches/src/hll/sketch.rs +++ b/datasketches/src/hll/sketch.rs @@ -20,7 +20,6 @@ //! This module provides the main [`HllSketch`] struct, which is the primary interface //! for creating and using HLL sketches for cardinality estimation. -use std::fmt; use std::hash::Hash; use crate::codec::SketchSlice; @@ -458,10 +457,12 @@ impl HllSketch { size_of::() + heap_size } -} -impl fmt::Display for HllSketch { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + /// Returns a human-readable diagnostic summary. + /// + /// The output is for inspection and debugging. Its format may change and + /// should not be parsed. + pub fn summary(&self) -> String { let target_type = match self.target_type() { HllType::Hll4 => "Hll4", HllType::Hll6 => "Hll6", @@ -473,20 +474,18 @@ impl fmt::Display for HllSketch { Mode::Array4(_) | Mode::Array6(_) | Mode::Array8(_) => "Hll", }; - writeln!(f, "HLL Sketch Summary:")?; - writeln!(f, " lg config k : {}", self.lg_config_k())?; - writeln!(f, " target type : {target_type}")?; - writeln!(f, " current mode : {current_mode}")?; - writeln!( - f, - " lower bound : {}", - self.lower_bound(NumStdDev::One) - )?; - writeln!(f, " estimate : {}", self.estimate())?; - writeln!( - f, - " upper bound : {}", - self.upper_bound(NumStdDev::One) + format!( + "HLL Sketch Summary:\n\ + \x20\x20lg config k : {}\n\ + \x20\x20target type : {target_type}\n\ + \x20\x20current mode : {current_mode}\n\ + \x20\x20lower bound : {}\n\ + \x20\x20estimate : {}\n\ + \x20\x20upper bound : {}", + self.lg_config_k(), + self.lower_bound(NumStdDev::One), + self.estimate(), + self.upper_bound(NumStdDev::One), ) } } diff --git a/datasketches/src/hll/union.rs b/datasketches/src/hll/union.rs index 5c8372ad..955c9c1b 100644 --- a/datasketches/src/hll/union.rs +++ b/datasketches/src/hll/union.rs @@ -28,7 +28,6 @@ //! * Different modes (List, Set, Array4/6/8) //! * Different target HLL types -use std::fmt; use std::hash::Hash; use crate::common::NumStdDev; @@ -337,23 +336,24 @@ impl HllUnion { // The gadget's inline size is already covered by size_of::(). size_of::() - size_of::() + self.gadget.estimated_size() } -} -impl fmt::Display for HllUnion { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - writeln!(f, "HLL Union Summary:")?; - writeln!(f, " lg max k : {}", self.lg_max_k())?; - writeln!(f, " lg config k : {}", self.lg_config_k())?; - writeln!( - f, - " lower bound : {}", - self.lower_bound(NumStdDev::One) - )?; - writeln!(f, " estimate : {}", self.estimate())?; - writeln!( - f, - " upper bound : {}", - self.upper_bound(NumStdDev::One) + /// Returns a human-readable diagnostic summary. + /// + /// The output is for inspection and debugging. Its format may change and + /// should not be parsed. + pub fn summary(&self) -> String { + format!( + "HLL Union Summary:\n\ + \x20\x20lg max k : {}\n\ + \x20\x20lg config k : {}\n\ + \x20\x20lower bound : {}\n\ + \x20\x20estimate : {}\n\ + \x20\x20upper bound : {}", + self.lg_max_k(), + self.lg_config_k(), + self.lower_bound(NumStdDev::One), + self.estimate(), + self.upper_bound(NumStdDev::One), ) } } diff --git a/tests-integration/tests/cpc_test/main.rs b/tests-integration/tests/cpc_test/main.rs index 62bc7f40..254c6d24 100644 --- a/tests-integration/tests/cpc_test/main.rs +++ b/tests-integration/tests/cpc_test/main.rs @@ -16,7 +16,7 @@ // under the License. mod deserialize; -mod display; +mod summary; mod union; mod update; mod wrapper; diff --git a/tests-integration/tests/cpc_test/display.rs b/tests-integration/tests/cpc_test/summary.rs similarity index 89% rename from tests-integration/tests/cpc_test/display.rs rename to tests-integration/tests/cpc_test/summary.rs index a7092f99..02b17d5a 100644 --- a/tests-integration/tests/cpc_test/display.rs +++ b/tests-integration/tests/cpc_test/summary.rs @@ -20,10 +20,10 @@ use datasketches::cpc::CpcUnion; use insta::assert_snapshot; #[test] -fn display_empty_sketch() { +fn summary_empty_sketch() { let sketch = CpcSketch::new(11).unwrap(); - assert_snapshot!(sketch, @r" + assert_snapshot!(sketch.summary(), @r" CPC Sketch Summary: flavor : Empty lg k : 11 @@ -34,24 +34,24 @@ fn display_empty_sketch() { } #[test] -fn display_populated_sketch() { +fn summary_populated_sketch() { let mut sketch = CpcSketch::new(11).unwrap(); sketch.update("apple"); - let summary = sketch.to_string(); + let summary = sketch.summary(); assert!(summary.contains("flavor : Sparse\n")); assert!(summary.contains("num coupons : 1\n")); assert!(!summary.contains("estimate : 0\n")); } #[test] -fn display_union() { +fn summary_union() { let mut sketch = CpcSketch::new(11).unwrap(); sketch.update("apple"); let mut union = CpcUnion::new(11).unwrap(); union.update(&sketch).unwrap(); - assert_snapshot!(union, @r" + assert_snapshot!(union.summary(), @r" CPC Union Summary: lg k : 11 state : Accumulator diff --git a/tests-integration/tests/hll_test/main.rs b/tests-integration/tests/hll_test/main.rs index 954a7847..ffa065a5 100644 --- a/tests-integration/tests/hll_test/main.rs +++ b/tests-integration/tests/hll_test/main.rs @@ -16,6 +16,6 @@ // under the License. mod bounds; -mod display; +mod summary; mod union; mod update; diff --git a/tests-integration/tests/hll_test/display.rs b/tests-integration/tests/hll_test/summary.rs similarity index 90% rename from tests-integration/tests/hll_test/display.rs rename to tests-integration/tests/hll_test/summary.rs index d53fa794..81396d0f 100644 --- a/tests-integration/tests/hll_test/display.rs +++ b/tests-integration/tests/hll_test/summary.rs @@ -21,10 +21,10 @@ use datasketches::hll::HllUnion; use insta::assert_snapshot; #[test] -fn display_empty_sketch() { +fn summary_empty_sketch() { let sketch = HllSketch::new(12, HllType::Hll8).unwrap(); - assert_snapshot!(sketch, @r" + assert_snapshot!(sketch.summary(), @r" HLL Sketch Summary: lg config k : 12 target type : Hll8 @@ -36,24 +36,24 @@ fn display_empty_sketch() { } #[test] -fn display_populated_sketch() { +fn summary_populated_sketch() { let mut sketch = HllSketch::new(10, HllType::Hll4).unwrap(); for value in 0..1_000 { sketch.update(value); } - let summary = sketch.to_string(); + let summary = sketch.summary(); assert!(summary.contains("target type : Hll4\n")); assert!(summary.contains("current mode : Hll\n")); assert!(!summary.contains("estimate : 0\n")); } #[test] -fn display_union() { +fn summary_union() { let mut union = HllUnion::new(12).unwrap(); union.update_value("apple"); - let summary = union.to_string(); + let summary = union.summary(); assert!(summary.starts_with("HLL Union Summary:\n")); assert!(summary.contains("lg max k : 12\n")); assert!(summary.contains("lg config k : 12\n"));