reqsketch 0.2.1

Relative Error Quantiles sketch
Documentation
//! Builder pattern implementation for REQ sketch construction.

use crate::{RankAccuracy, ReqError, ReqSketch, Result, TotalOrd};
use std::marker::PhantomData;

/// Builder for constructing REQ sketches with custom parameters.
///
/// Provides a fluent interface for configuring sketch parameters with validation
/// and sensible defaults.
///
/// # Examples
///
/// ```rust
/// use reqsketch::{ReqSketch, RankAccuracy};
///
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// // Use defaults
/// let sketch: ReqSketch<f64> = ReqSketch::builder().build()?;
///
/// // Custom configuration
/// let sketch: ReqSketch<f64> = ReqSketch::builder()
///     .k(16)?
///     .rank_accuracy(RankAccuracy::LowRank)
///     .build()?;
/// # Ok(())
/// # }
/// ```
#[derive(Debug, Clone)]
pub struct ReqSketchBuilder<T> {
    k: u16,
    rank_accuracy: RankAccuracy,
    _phantom: PhantomData<T>,
}

impl<T> ReqSketchBuilder<T>
where
    T: Clone + TotalOrd + PartialEq,
{
    /// Creates a new builder with default parameters.
    ///
    /// Defaults:
    /// - k = 12 (roughly 1% relative error at 95% confidence)
    /// - rank_accuracy = HighRank
    pub fn new() -> Self {
        Self {
            k: 12,
            rank_accuracy: RankAccuracy::HighRank,
            _phantom: PhantomData,
        }
    }

    /// Sets the k parameter.
    ///
    /// # Arguments
    /// * `k` - Controls size and error of the sketch. Must be even and in the
    ///   range [4, 1024], matching the DataSketches Java implementation (and
    ///   keeping sketches within the limits of the cross-language wire format).
    ///
    /// # Errors
    /// Returns `ReqError::InvalidK` if k is odd or outside [4, 1024].
    pub fn k(mut self, k: u16) -> Result<Self> {
        validate_k(k)?;
        self.k = k;
        Ok(self)
    }

    /// Sets the rank accuracy mode.
    ///
    /// # Arguments
    /// * `accuracy` - Whether to optimize for high ranks or low ranks
    pub fn rank_accuracy(mut self, accuracy: RankAccuracy) -> Self {
        self.rank_accuracy = accuracy;
        self
    }

    /// Builds the REQ sketch with the configured parameters.
    pub fn build(self) -> Result<ReqSketch<T>> {
        Ok(ReqSketch {
            k: self.k,
            rank_accuracy: self.rank_accuracy,
            total_n: 0,
            max_nom_size: 0,
            num_retained: 0,
            compactors: Vec::new(),
            promotion_buf: Vec::with_capacity(self.k as usize), // Pre-allocate based on k
            min_item: None,
            max_item: None,
        })
    }
}

impl<T> Default for ReqSketchBuilder<T>
where
    T: Clone + TotalOrd + PartialEq,
{
    fn default() -> Self {
        Self::new()
    }
}

/// Validates the k parameter: even and in [4, 1024] (DataSketches Java parity).
pub(crate) fn validate_k(k: u16) -> Result<()> {
    if !(4..=1024).contains(&k) || !k.is_multiple_of(2) {
        Err(ReqError::InvalidK(k))
    } else {
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_default_builder() {
        let builder: ReqSketchBuilder<f64> = ReqSketchBuilder::new();
        assert_eq!(builder.k, 12);
        assert_eq!(builder.rank_accuracy, RankAccuracy::HighRank);
    }

    #[test]
    fn test_builder_k_validation() {
        // Valid k values
        assert!(ReqSketchBuilder::<f64>::new().k(4).is_ok());
        assert!(ReqSketchBuilder::<f64>::new().k(12).is_ok());
        assert!(ReqSketchBuilder::<f64>::new().k(1024).is_ok()); // upper bound

        // Invalid k values
        assert!(ReqSketchBuilder::<f64>::new().k(3).is_err()); // too small
        assert!(ReqSketchBuilder::<f64>::new().k(5).is_err()); // odd
        assert!(ReqSketchBuilder::<f64>::new().k(1026).is_err()); // above upper bound
        assert!(ReqSketchBuilder::<f64>::new().k(2048).is_err()); // above upper bound
        assert!(ReqSketchBuilder::<f64>::new().k(u16::MAX).is_err()); // odd and too large
    }

    #[test]
    fn test_builder_fluent_interface() -> Result<()> {
        let sketch = ReqSketchBuilder::<i32>::new()
            .k(16)?
            .rank_accuracy(RankAccuracy::LowRank)
            .build()?;

        assert_eq!(sketch.k(), 16);
        assert_eq!(sketch.rank_accuracy(), RankAccuracy::LowRank);
        Ok(())
    }

    #[test]
    fn test_validate_k() {
        assert!(validate_k(4).is_ok());
        assert!(validate_k(12).is_ok());
        assert!(validate_k(1024).is_ok());

        assert!(validate_k(3).is_err());
        assert!(validate_k(5).is_err());
        assert!(validate_k(1026).is_err()); // above upper bound
        assert!(validate_k(2048).is_err()); // above upper bound
        assert!(validate_k(u16::MAX).is_err());
    }
}