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}