diff --git a/CHANGELOG.md b/CHANGELOG.md index eba1dae0..9e15e870 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ All significant changes to this project will be documented in this file. ### New features +* Add human-readable `Display` summaries for HLL and CPC sketches and unions. * Add Relative Error Quantiles (REQ) sketches behind the `req` feature, including configurable high- or low-rank accuracy, rank, quantile, PMF, and CDF queries, merging and unions, and C++/Java-compatible serialization. ### Performance improvements diff --git a/datasketches/src/cpc/sketch.rs b/datasketches/src/cpc/sketch.rs index 96a48fbf..ab3ed8a3 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; @@ -471,6 +472,35 @@ impl CpcSketch { } } +impl fmt::Display for CpcSketch { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let flavor = match self.flavor() { + Flavor::Empty => "Empty", + Flavor::Sparse => "Sparse", + Flavor::Hybrid => "Hybrid", + Flavor::Pinned => "Pinned", + Flavor::Sliding => "Sliding", + }; + + writeln!(f, "CPC Sketch Summary:")?; + writeln!(f, " flavor : {flavor}")?; + writeln!(f, " lg k : {}", self.lg_k())?; + writeln!(f, " merged : {}", self.merge_flag)?; + writeln!( + f, + " lower bound : {}", + self.lower_bound(NumStdDev::One) + )?; + writeln!(f, " estimate : {}", self.estimate())?; + writeln!( + f, + " upper bound : {}", + self.upper_bound(NumStdDev::One) + )?; + 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 63198ca3..893fba35 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; @@ -347,6 +349,20 @@ 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", + }; + + writeln!(f, "CPC Union Summary:")?; + writeln!(f, " lg k : {}", self.lg_k())?; + writeln!(f, " state : {state}")?; + writeln!(f, " num coupons : {}", self.num_coupons()) + } +} + // testing methods impl CpcUnion { /// Returns the number of coupons in the union. diff --git a/datasketches/src/hll/sketch.rs b/datasketches/src/hll/sketch.rs index befcc81c..58e09aad 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; @@ -458,6 +459,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 df86272d..fa4cd1ae 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; @@ -342,6 +343,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..c5ba3f75 --- /dev/null +++ b/tests-integration/tests/cpc_test/display.rs @@ -0,0 +1,67 @@ +// 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; + +#[test] +fn display_empty_sketch() { + let sketch = CpcSketch::new(11); + + assert_eq!( + sketch.to_string(), + concat!( + "CPC Sketch Summary:\n", + " flavor : Empty\n", + " lg k : 11\n", + " merged : false\n", + " lower bound : 0\n", + " estimate : 0\n", + " upper bound : 0\n", + " num coupons : 0\n", + ) + ); +} + +#[test] +fn display_populated_sketch() { + let mut sketch = CpcSketch::new(11); + 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); + sketch.update("apple"); + let mut union = CpcUnion::new(11); + union.update(&sketch); + + assert_eq!( + union.to_string(), + concat!( + "CPC Union Summary:\n", + " lg k : 11\n", + " state : Accumulator\n", + " num coupons : 1\n", + ) + ); +} 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..eb74dc50 --- /dev/null +++ b/tests-integration/tests/hll_test/display.rs @@ -0,0 +1,63 @@ +// 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; + +#[test] +fn display_empty_sketch() { + let sketch = HllSketch::new(12, HllType::Hll8); + + assert_eq!( + sketch.to_string(), + concat!( + "HLL Sketch Summary:\n", + " lg config k : 12\n", + " target type : Hll8\n", + " current mode : List\n", + " lower bound : 0\n", + " estimate : 0\n", + " upper bound : 0\n", + ) + ); +} + +#[test] +fn display_populated_sketch() { + let mut sketch = HllSketch::new(10, HllType::Hll4); + 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); + 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 e56cecc9..fc324576 100644 --- a/tests-integration/tests/hll_test/main.rs +++ b/tests-integration/tests/hll_test/main.rs @@ -15,5 +15,6 @@ // specific language governing permissions and limitations // under the License. +mod display; mod union; mod update;