Skip to main content

apache_datasketches/tuple/
compact.rs

1use crate::error::SketchError;
2use apache_datasketches_sys::array_of_doubles_compact::ffi as sys;
3use cxx::UniquePtr;
4
5/// An immutable, serializable snapshot of an ArrayOfDoubles Tuple sketch.
6/// Produced by [`super::ArrayOfDoublesSketch::compact`], by any set
7/// operation's result, or by [`Self::deserialize`].
8pub struct CompactArrayOfDoublesSketch {
9    pub(crate) inner: UniquePtr<sys::CompactArrayOfDoublesSketchShim>,
10}
11
12unsafe impl Send for CompactArrayOfDoublesSketch {}
13
14impl CompactArrayOfDoublesSketch {
15    pub(crate) fn from_shim(inner: UniquePtr<sys::CompactArrayOfDoublesSketchShim>) -> Self {
16        Self { inner }
17    }
18
19    /// Deserializes bytes produced by [`Self::serialize`]. Returns
20    /// [`SketchError::Deserialization`] if the bytes are truncated, corrupt,
21    /// or not an ArrayOfDoubles sketch.
22    pub fn deserialize(bytes: &[u8]) -> Result<Self, SketchError> {
23        let inner = sys::compact_array_of_doubles_sketch_deserialize(bytes)
24            .map_err(|e| SketchError::Deserialization(e.what().to_string()))?;
25        Ok(Self { inner })
26    }
27
28    /// Serializes this sketch. Unlike Theta, this family has exactly one
29    /// serialization format upstream — there is no compressed variant — and
30    /// no `ordered` parameter: orderedness is fixed when the snapshot was
31    /// created (e.g. via
32    /// [`ArrayOfDoublesSketch::compact`](super::ArrayOfDoublesSketch::compact)).
33    pub fn serialize(&self) -> Vec<u8> {
34        self.inner.serialize()
35    }
36
37    /// Returns the current estimate of the number of distinct keys in this
38    /// sketch.
39    pub fn get_estimate(&self) -> f64 {
40        self.inner.get_estimate()
41    }
42
43    /// Returns the lower bound of the confidence interval around
44    /// [`Self::get_estimate`]. See
45    /// [`ArrayOfDoublesSketch::get_lower_bound`](super::ArrayOfDoublesSketch::get_lower_bound)
46    /// for the meaning of `num_std_dev`.
47    pub fn get_lower_bound(&self, num_std_dev: u8) -> Result<f64, SketchError> {
48        self.inner
49            .get_lower_bound(num_std_dev)
50            .map_err(|e| SketchError::InvalidConfig(e.what().to_string()))
51    }
52
53    /// Returns the upper bound of the confidence interval around
54    /// [`Self::get_estimate`]. See
55    /// [`ArrayOfDoublesSketch::get_lower_bound`](super::ArrayOfDoublesSketch::get_lower_bound)
56    /// for the meaning of `num_std_dev`.
57    pub fn get_upper_bound(&self, num_std_dev: u8) -> Result<f64, SketchError> {
58        self.inner
59            .get_upper_bound(num_std_dev)
60            .map_err(|e| SketchError::InvalidConfig(e.what().to_string()))
61    }
62
63    /// Returns `true` if this sketch represents an empty set.
64    pub fn is_empty(&self) -> bool {
65        self.inner.is_empty()
66    }
67
68    /// Returns `true` if this sketch's theta threshold is below `1.0`
69    /// (i.e. [`Self::get_estimate`] is a statistical estimate rather than an
70    /// exact count).
71    pub fn is_estimation_mode(&self) -> bool {
72        self.inner.is_estimation_mode()
73    }
74
75    /// Returns `true` if this sketch's retained entries are sorted by hash
76    /// value.
77    pub fn is_ordered(&self) -> bool {
78        self.inner.is_ordered()
79    }
80
81    /// Returns the current theta threshold (`1.0` if not in estimation mode).
82    pub fn get_theta(&self) -> f64 {
83        self.inner.get_theta()
84    }
85
86    /// Returns the number of entries retained by this sketch.
87    pub fn get_num_retained(&self) -> u32 {
88        self.inner.get_num_retained()
89    }
90
91    /// Returns the fixed number of `f64` values each retained entry carries.
92    pub fn get_num_values(&self) -> u8 {
93        self.inner.get_num_values()
94    }
95
96    /// Iterates the retained entries as `(hash, values)` pairs, where
97    /// `values.len() == self.get_num_values()`. Ordered by hash if
98    /// [`Self::is_ordered`] is `true`.
99    ///
100    /// The entries are copied out of C++ in two FFI calls up front (cxx
101    /// cannot hand back a live C++ iterator), so each item owns its `Vec`
102    /// rather than borrowing from the sketch.
103    pub fn entries(&self) -> impl Iterator<Item = (u64, Vec<f64>)> {
104        let num_values = self.inner.get_num_values() as usize;
105        let hashes: Vec<u64> = self.inner.entry_hashes().into_iter().collect();
106        let values: Vec<f64> = self.inner.entry_values().into_iter().collect();
107        let grouped: Vec<Vec<f64>> = if num_values == 0 {
108            Vec::new()
109        } else {
110            values.chunks(num_values).map(|c| c.to_vec()).collect()
111        };
112        hashes.into_iter().zip(grouped)
113    }
114}