Skip to main content

apache_datasketches/tuple/
sketch.rs

1use crate::error::SketchError;
2use crate::tuple::builder::ResizeFactor;
3use apache_datasketches_sys::array_of_doubles_sketch::ffi as sys;
4use cxx::UniquePtr;
5
6/// A mutable, update-only ArrayOfDoubles Tuple sketch: estimates the number
7/// of distinct keys added via `update_*`, and carries a fixed-width array of
8/// `f64` values per retained key, summed on collision. Build one with
9/// [`ArrayOfDoublesSketchBuilder`](super::ArrayOfDoublesSketchBuilder).
10///
11/// Call [`Self::compact`] to produce an immutable, serializable
12/// [`super::CompactArrayOfDoublesSketch`] snapshot for storage, transmission,
13/// or use as input to a set operation.
14pub struct ArrayOfDoublesSketch {
15    pub(crate) inner: UniquePtr<sys::ArrayOfDoublesSketchShim>,
16}
17
18unsafe impl Send for ArrayOfDoublesSketch {}
19
20impl ArrayOfDoublesSketch {
21    pub(crate) fn from_parts(
22        lg_k: u8,
23        rf: ResizeFactor,
24        p: f32,
25        num_values: u8,
26    ) -> Result<Self, SketchError> {
27        if num_values == 0 {
28            return Err(SketchError::InvalidConfig(
29                "num_values must be at least 1".to_string(),
30            ));
31        }
32        let inner = sys::new_array_of_doubles_sketch(lg_k, rf.into(), p, num_values)
33            .map_err(|e| SketchError::InvalidConfig(e.what().to_string()))?;
34        Ok(Self { inner })
35    }
36
37    /// Validates that `values` has exactly [`Self::get_num_values`] elements.
38    ///
39    /// This check cannot be delegated to the C++ layer's exceptions the way
40    /// `lg_k`/`num_std_dev` validation is: upstream's update policy indexes
41    /// the supplied values blindly for `i in 0..num_values`, so a short slice
42    /// would be an out-of-bounds read rather than a graceful failure.
43    fn check_values(&self, values: &[f64]) -> Result<(), SketchError> {
44        let expected = self.inner.get_num_values() as usize;
45        if values.len() != expected {
46            return Err(SketchError::InvalidConfig(format!(
47                "expected {expected} values, got {}",
48                values.len()
49            )));
50        }
51        Ok(())
52    }
53
54    /// Adds a `u64` key with its associated values. Returns
55    /// [`SketchError::InvalidConfig`] unless
56    /// `values.len() == self.get_num_values()`.
57    pub fn update_u64(&mut self, key: u64, values: &[f64]) -> Result<(), SketchError> {
58        self.check_values(values)?;
59        self.inner.pin_mut().update_u64(key, values);
60        Ok(())
61    }
62
63    /// Adds an `i64` key with its associated values. See [`Self::update_u64`].
64    pub fn update_i64(&mut self, key: i64, values: &[f64]) -> Result<(), SketchError> {
65        self.check_values(values)?;
66        self.inner.pin_mut().update_i64(key, values);
67        Ok(())
68    }
69
70    /// Adds a `u32` key with its associated values. See [`Self::update_u64`].
71    pub fn update_u32(&mut self, key: u32, values: &[f64]) -> Result<(), SketchError> {
72        self.check_values(values)?;
73        self.inner.pin_mut().update_u32(key, values);
74        Ok(())
75    }
76
77    /// Adds an `i32` key with its associated values. See [`Self::update_u64`].
78    pub fn update_i32(&mut self, key: i32, values: &[f64]) -> Result<(), SketchError> {
79        self.check_values(values)?;
80        self.inner.pin_mut().update_i32(key, values);
81        Ok(())
82    }
83
84    /// Adds a `u16` key with its associated values. See [`Self::update_u64`].
85    pub fn update_u16(&mut self, key: u16, values: &[f64]) -> Result<(), SketchError> {
86        self.check_values(values)?;
87        self.inner.pin_mut().update_u16(key, values);
88        Ok(())
89    }
90
91    /// Adds an `i16` key with its associated values. See [`Self::update_u64`].
92    pub fn update_i16(&mut self, key: i16, values: &[f64]) -> Result<(), SketchError> {
93        self.check_values(values)?;
94        self.inner.pin_mut().update_i16(key, values);
95        Ok(())
96    }
97
98    /// Adds a `u8` key with its associated values. See [`Self::update_u64`].
99    pub fn update_u8(&mut self, key: u8, values: &[f64]) -> Result<(), SketchError> {
100        self.check_values(values)?;
101        self.inner.pin_mut().update_u8(key, values);
102        Ok(())
103    }
104
105    /// Adds an `i8` key with its associated values. See [`Self::update_u64`].
106    pub fn update_i8(&mut self, key: i8, values: &[f64]) -> Result<(), SketchError> {
107        self.check_values(values)?;
108        self.inner.pin_mut().update_i8(key, values);
109        Ok(())
110    }
111
112    /// Adds an `f64` key with its associated values. See [`Self::update_u64`].
113    pub fn update_f64(&mut self, key: f64, values: &[f64]) -> Result<(), SketchError> {
114        self.check_values(values)?;
115        self.inner.pin_mut().update_f64(key, values);
116        Ok(())
117    }
118
119    /// Adds a string key with its associated values. See [`Self::update_u64`].
120    pub fn update_str(&mut self, key: &str, values: &[f64]) -> Result<(), SketchError> {
121        self.check_values(values)?;
122        self.inner.pin_mut().update_str(key, values);
123        Ok(())
124    }
125
126    /// Adds an arbitrary byte-slice key with its associated values. See
127    /// [`Self::update_u64`].
128    pub fn update_bytes(&mut self, key: &[u8], values: &[f64]) -> Result<(), SketchError> {
129        self.check_values(values)?;
130        self.inner.pin_mut().update_bytes(key, values);
131        Ok(())
132    }
133
134    /// Removes retained entries in excess of the nominal size `k`, lowering
135    /// the theta threshold to do so.
136    ///
137    /// Note that this *does* shift [`Self::get_estimate`] — trimming lowers
138    /// theta, and the estimate is derived from the retained count and theta
139    /// together. Upstream only guarantees the excess entries are dropped.
140    pub fn trim(&mut self) {
141        self.inner.pin_mut().trim();
142    }
143
144    /// Resets this sketch to its initial, empty state. `num_values` is
145    /// preserved.
146    pub fn reset(&mut self) {
147        self.inner.pin_mut().reset();
148    }
149
150    /// Returns the current estimate of the number of distinct keys added.
151    pub fn get_estimate(&self) -> f64 {
152        self.inner.get_estimate()
153    }
154
155    /// Returns the lower bound of the confidence interval around
156    /// [`Self::get_estimate`], for the given number of standard deviations
157    /// (`1`, `2`, or `3`, corresponding to roughly 67%, 95%, and 99%
158    /// confidence). Returns [`SketchError::InvalidConfig`] for any other
159    /// value.
160    pub fn get_lower_bound(&self, num_std_dev: u8) -> Result<f64, SketchError> {
161        self.inner
162            .get_lower_bound(num_std_dev)
163            .map_err(|e| SketchError::InvalidConfig(e.what().to_string()))
164    }
165
166    /// Returns the upper bound of the confidence interval around
167    /// [`Self::get_estimate`]. See [`Self::get_lower_bound`] for the meaning
168    /// of `num_std_dev`.
169    pub fn get_upper_bound(&self, num_std_dev: u8) -> Result<f64, SketchError> {
170        self.inner
171            .get_upper_bound(num_std_dev)
172            .map_err(|e| SketchError::InvalidConfig(e.what().to_string()))
173    }
174
175    /// Returns `true` if no keys have been added to this sketch.
176    pub fn is_empty(&self) -> bool {
177        self.inner.is_empty()
178    }
179
180    /// Returns `true` if this sketch's theta threshold is below `1.0`
181    /// (i.e. it has begun sampling and [`Self::get_estimate`] is a
182    /// statistical estimate rather than an exact count).
183    pub fn is_estimation_mode(&self) -> bool {
184        self.inner.is_estimation_mode()
185    }
186
187    /// Returns `true` if this sketch's retained entries are sorted by hash
188    /// value.
189    pub fn is_ordered(&self) -> bool {
190        self.inner.is_ordered()
191    }
192
193    /// Returns the current theta threshold (`1.0` until sampling begins).
194    pub fn get_theta(&self) -> f64 {
195        self.inner.get_theta()
196    }
197
198    /// Returns the number of entries currently retained by this sketch.
199    pub fn get_num_retained(&self) -> u32 {
200        self.inner.get_num_retained()
201    }
202
203    /// Returns the fixed number of `f64` values each retained entry carries,
204    /// as configured at build time.
205    pub fn get_num_values(&self) -> u8 {
206        self.inner.get_num_values()
207    }
208
209    /// Iterates the retained entries as `(hash, values)` pairs, where
210    /// `values.len() == self.get_num_values()`.
211    ///
212    /// The entries are copied out of C++ in two FFI calls up front (cxx
213    /// cannot hand back a live C++ iterator), so each item owns its `Vec`
214    /// rather than borrowing from the sketch. Iteration order is unspecified
215    /// for an update sketch; compact it with `ordered = true` for
216    /// hash-ordered iteration.
217    pub fn entries(&self) -> impl Iterator<Item = (u64, Vec<f64>)> {
218        let num_values = self.inner.get_num_values() as usize;
219        let hashes: Vec<u64> = self.inner.entry_hashes().into_iter().collect();
220        let values: Vec<f64> = self.inner.entry_values().into_iter().collect();
221        let grouped: Vec<Vec<f64>> = if num_values == 0 {
222            Vec::new()
223        } else {
224            values.chunks(num_values).map(|c| c.to_vec()).collect()
225        };
226        hashes.into_iter().zip(grouped)
227    }
228
229    /// Produces an immutable, serializable
230    /// [`super::CompactArrayOfDoublesSketch`] snapshot of this sketch's
231    /// current state. If `ordered` is `true`, the snapshot's entries are
232    /// sorted by hash value.
233    pub fn compact(&self, ordered: bool) -> super::CompactArrayOfDoublesSketch {
234        super::CompactArrayOfDoublesSketch::from_shim(self.inner.compact(ordered))
235    }
236}