diff --git a/CHANGELOG.md b/CHANGELOG.md index 89e3b79..4b80984 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 8599e46..8d14767 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 51742a3..3bcb59a 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 4e0ed71..efba7d1 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 eb98382..5c8372a 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 0000000..a7092f9 --- /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 7b98ba9..62bc7f4 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 0000000..d53fa79 --- /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 30fb7dc..954a784 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;