Skip to main content

apache_datasketches/hll/
sketch.rs

1use crate::error::SketchError;
2use apache_datasketches_sys::hll::ffi as sys;
3use cxx::UniquePtr;
4
5/// Controls the internal representation HLL uses to store per-bucket
6/// state, trading memory for accuracy. Mirrors upstream's
7/// `datasketches::target_hll_type`.
8///
9/// `Hll4` is the most memory-compact; `Hll8` is the least compact but
10/// fastest to update and most accurate at small cardinalities.
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum TargetHllType {
13    /// 4 bits per bucket — most memory-compact.
14    Hll4,
15    /// 6 bits per bucket.
16    Hll6,
17    /// 8 bits per bucket — least compact, fastest, most accurate at small n.
18    Hll8,
19}
20
21impl From<TargetHllType> for sys::TargetHllType {
22    fn from(t: TargetHllType) -> Self {
23        match t {
24            TargetHllType::Hll4 => sys::TargetHllType::Hll4,
25            TargetHllType::Hll6 => sys::TargetHllType::Hll6,
26            TargetHllType::Hll8 => sys::TargetHllType::Hll8,
27        }
28    }
29}
30
31impl From<sys::TargetHllType> for TargetHllType {
32    fn from(t: sys::TargetHllType) -> Self {
33        match t {
34            sys::TargetHllType::Hll4 => TargetHllType::Hll4,
35            sys::TargetHllType::Hll6 => TargetHllType::Hll6,
36            sys::TargetHllType::Hll8 => TargetHllType::Hll8,
37            _ => unreachable!("unknown TargetHllType variant from cxx bridge"),
38        }
39    }
40}
41
42/// A HyperLogLog sketch: estimates the number of distinct items added via
43/// `update_*`, using bounded memory regardless of how many items are added.
44///
45/// `lg_config_k` (passed to [`HllSketch::new`], valid range `4..=21`) trades
46/// memory for accuracy: higher values are more accurate but use more space.
47pub struct HllSketch {
48    pub(crate) inner: UniquePtr<sys::HllSketchShim>,
49}
50
51unsafe impl Send for HllSketch {}
52
53impl HllSketch {
54    /// Creates a new, empty sketch with the given `lg_config_k` (`4..=21`)
55    /// and internal representation. Returns [`SketchError::InvalidConfig`]
56    /// if `lg_config_k` is out of range.
57    pub fn new(lg_config_k: u8, tgt_type: TargetHllType) -> Result<Self, SketchError> {
58        let inner = sys::new_hll_sketch(lg_config_k, tgt_type.into())
59            .map_err(|e| SketchError::InvalidConfig(e.what().to_string()))?;
60        Ok(Self { inner })
61    }
62
63    /// Returns a copy of this sketch converted to a different
64    /// [`TargetHllType`], preserving its current state.
65    pub fn copy_as(&self, tgt_type: TargetHllType) -> Self {
66        let inner = sys::hll_sketch_copy_as(&self.inner, tgt_type.into());
67        Self { inner }
68    }
69
70    /// Reconstructs a sketch from bytes produced by
71    /// [`Self::serialize_compact`] or [`Self::serialize_updatable`].
72    pub fn deserialize(bytes: &[u8]) -> Result<Self, SketchError> {
73        let inner = sys::hll_sketch_deserialize(bytes)
74            .map_err(|e| SketchError::Deserialization(e.what().to_string()))?;
75        Ok(Self { inner })
76    }
77
78    /// Adds a `u64` value to the sketch.
79    pub fn update_u64(&mut self, value: u64) {
80        self.inner.pin_mut().update_u64(value);
81    }
82
83    /// Adds an `i64` value to the sketch.
84    pub fn update_i64(&mut self, value: i64) {
85        self.inner.pin_mut().update_i64(value);
86    }
87
88    /// Adds an `f64` value to the sketch.
89    pub fn update_f64(&mut self, value: f64) {
90        self.inner.pin_mut().update_f64(value);
91    }
92
93    /// Adds a string value to the sketch.
94    pub fn update_str(&mut self, value: &str) {
95        self.inner.pin_mut().update_str(value);
96    }
97
98    /// Adds an arbitrary byte slice to the sketch.
99    pub fn update_bytes(&mut self, value: &[u8]) {
100        self.inner.pin_mut().update_bytes(value);
101    }
102
103    /// Returns the current estimate of the number of distinct items added.
104    pub fn get_estimate(&self) -> f64 {
105        self.inner.get_estimate()
106    }
107
108    /// Returns the lower bound of the confidence interval around
109    /// [`Self::get_estimate`], for the given number of standard deviations
110    /// (`1`, `2`, or `3`, corresponding to roughly 67%, 95%, and 99%
111    /// confidence). Returns [`SketchError::InvalidConfig`] for any other
112    /// value.
113    pub fn get_lower_bound(&self, num_std_dev: u8) -> Result<f64, SketchError> {
114        self.inner
115            .get_lower_bound(num_std_dev)
116            .map_err(|e| SketchError::InvalidConfig(e.what().to_string()))
117    }
118
119    /// Returns the upper bound of the confidence interval around
120    /// [`Self::get_estimate`]. See [`Self::get_lower_bound`] for the meaning
121    /// of `num_std_dev`.
122    pub fn get_upper_bound(&self, num_std_dev: u8) -> Result<f64, SketchError> {
123        self.inner
124            .get_upper_bound(num_std_dev)
125            .map_err(|e| SketchError::InvalidConfig(e.what().to_string()))
126    }
127
128    /// Returns the `lg_config_k` this sketch was built with.
129    pub fn get_lg_config_k(&self) -> u8 {
130        self.inner.get_lg_config_k()
131    }
132
133    /// Returns the [`TargetHllType`] this sketch currently uses.
134    pub fn get_target_type(&self) -> TargetHllType {
135        self.inner.get_target_type().into()
136    }
137
138    /// Returns `true` if no items have been added to this sketch.
139    pub fn is_empty(&self) -> bool {
140        self.inner.is_empty()
141    }
142
143    /// Resets this sketch to its initial, empty state.
144    pub fn reset(&mut self) {
145        self.inner.pin_mut().reset();
146    }
147
148    /// Returns a human-readable, multi-line summary of this sketch's
149    /// internal state — useful for debugging, not for parsing.
150    pub fn to_string_summary(&self) -> String {
151        self.inner.to_string_summary()
152    }
153
154    /// Serializes this sketch in compact form (read-only once
155    /// deserialized): smaller on the wire, but a
156    /// [`Self::deserialize`]d sketch produced from these bytes cannot be
157    /// updated further. Use [`Self::serialize_updatable`] if you need to
158    /// resume adding items after deserializing.
159    pub fn serialize_compact(&self) -> Vec<u8> {
160        self.inner.serialize_compact()
161    }
162
163    /// Serializes this sketch in updatable form: larger on the wire than
164    /// [`Self::serialize_compact`], but a deserialized sketch can have more
165    /// items added to it.
166    pub fn serialize_updatable(&self) -> Vec<u8> {
167        self.inner.serialize_updatable()
168    }
169}