Skip to main content

apache_datasketches/tuple/
union.rs

1use super::input::ArrayOfDoublesInput;
2use super::{CompactArrayOfDoublesSketch, ResizeFactor};
3use crate::error::SketchError;
4use apache_datasketches_sys::array_of_doubles_input::ArrayOfDoublesInputRef;
5use apache_datasketches_sys::array_of_doubles_union::ffi as sys;
6use cxx::UniquePtr;
7
8/// Builder for [`ArrayOfDoublesUnion`], mirroring upstream's
9/// `array_of_doubles_union::builder`. `lg_k` defaults to `12`,
10/// `resize_factor` to [`ResizeFactor::X8`], `p` to `1.0` (no sampling), and
11/// `num_values` to `1`. As with
12/// [`ArrayOfDoublesSketchBuilder`](super::ArrayOfDoublesSketchBuilder), the
13/// seed is never exposed.
14#[derive(Debug, Clone, Copy)]
15pub struct ArrayOfDoublesUnionBuilder {
16    lg_k: u8,
17    resize_factor: ResizeFactor,
18    p: f32,
19    num_values: u8,
20}
21
22impl Default for ArrayOfDoublesUnionBuilder {
23    fn default() -> Self {
24        Self {
25            lg_k: 12,
26            resize_factor: ResizeFactor::default(),
27            p: 1.0,
28            num_values: 1,
29        }
30    }
31}
32
33impl ArrayOfDoublesUnionBuilder {
34    /// Creates a new builder with default settings (`lg_k = 12`,
35    /// `resize_factor = X8`, `p = 1.0`, `num_values = 1`).
36    pub fn new() -> Self {
37        Self::default()
38    }
39
40    /// Sets the base-2 log of the target number of retained entries in the
41    /// union's result.
42    pub fn lg_k(mut self, lg_k: u8) -> Self {
43        self.lg_k = lg_k;
44        self
45    }
46
47    /// Sets the hash table's growth [`ResizeFactor`].
48    pub fn resize_factor(mut self, resize_factor: ResizeFactor) -> Self {
49        self.resize_factor = resize_factor;
50        self
51    }
52
53    /// Sets the sampling probability. `1.0` (the default) disables sampling.
54    pub fn p(mut self, p: f32) -> Self {
55        self.p = p;
56        self
57    }
58
59    /// Sets the fixed number of `f64` values per entry. Must be at least `1`,
60    /// and must match every sketch later passed to
61    /// [`ArrayOfDoublesUnion::update`].
62    pub fn num_values(mut self, num_values: u8) -> Self {
63        self.num_values = num_values;
64        self
65    }
66
67    /// Builds the union. Returns [`SketchError::InvalidConfig`] if `lg_k` is
68    /// out of range, `p` is outside `(0, 1]`, or `num_values` is `0`.
69    pub fn build(self) -> Result<ArrayOfDoublesUnion, SketchError> {
70        if self.num_values == 0 {
71            return Err(SketchError::InvalidConfig(
72                "num_values must be at least 1".to_string(),
73            ));
74        }
75        let inner = sys::new_array_of_doubles_union(
76            self.lg_k,
77            self.resize_factor.into(),
78            self.p,
79            self.num_values,
80        )
81        .map_err(|e| SketchError::InvalidConfig(e.what().to_string()))?;
82        Ok(ArrayOfDoublesUnion {
83            inner,
84            num_values: self.num_values,
85        })
86    }
87}
88
89/// A streaming union accumulator over ArrayOfDoubles sketches. Values are
90/// summed per index when the same key appears in more than one input, using
91/// upstream's `default_array_of_doubles_union_policy`.
92///
93/// Accepts either [`super::ArrayOfDoublesSketch`] or
94/// [`CompactArrayOfDoublesSketch`] via the sealed [`ArrayOfDoublesInput`]
95/// trait.
96pub struct ArrayOfDoublesUnion {
97    inner: UniquePtr<sys::ArrayOfDoublesUnionShim>,
98    num_values: u8,
99}
100
101unsafe impl Send for ArrayOfDoublesUnion {}
102
103impl ArrayOfDoublesUnion {
104    /// Merges the given sketch into this union's running result.
105    ///
106    /// Returns [`SketchError::InvalidConfig`] if the sketch's `num_values`
107    /// differs from this union's. Upstream does not validate this itself —
108    /// merging mismatched array widths would read and write past the shorter
109    /// array's bounds rather than error — so the check happens here, before
110    /// the sketch crosses the FFI boundary.
111    pub fn update(&mut self, input: &impl ArrayOfDoublesInput) -> Result<(), SketchError> {
112        let actual = input.get_num_values();
113        if actual != self.num_values {
114            return Err(SketchError::InvalidConfig(format!(
115                "num_values mismatch: union has {}, input has {actual}",
116                self.num_values
117            )));
118        }
119        match input.as_input() {
120            ArrayOfDoublesInputRef::Sketch(s) => self.inner.pin_mut().update_with_sketch(s),
121            ArrayOfDoublesInputRef::Compact(c) => self.inner.pin_mut().update_with_compact(c),
122        }
123        Ok(())
124    }
125
126    /// Returns the union's current result as a
127    /// [`CompactArrayOfDoublesSketch`]. If `ordered` is `true`, the result's
128    /// entries are sorted by hash value.
129    pub fn get_result(&self, ordered: bool) -> CompactArrayOfDoublesSketch {
130        CompactArrayOfDoublesSketch::from_shim(self.inner.get_result(ordered))
131    }
132
133    /// Resets this union to its initial, empty state. `num_values` is
134    /// preserved.
135    pub fn reset(&mut self) {
136        self.inner.pin_mut().reset();
137    }
138
139    /// Returns the fixed number of `f64` values per entry this union was
140    /// built with. Every input passed to [`Self::update`] must match it.
141    pub fn get_num_values(&self) -> u8 {
142        self.num_values
143    }
144}