csvbinmatrix 0.8.0

Binary matrix Compressed Sparse Vector
Documentation
#![doc = include_str!("../docs/filters.md")]

use thiserror::Error;

/// A filter that accepts the given index.
pub trait DimensionFilter {
    /// Returns true if the index should be accepted.
    fn accepts(&self, index: usize) -> bool;
}

/// A dimension filter that uses a closure.
///
/// # Examples
///
/// ```rust
/// use csvbinmatrix::prelude::{ClosureDimensionFilter, DimensionFilter};
///
/// let filter = ClosureDimensionFilter::new(|i| i != 0);
/// assert!(!filter.accepts(0));
/// assert!(filter.accepts(1));
/// ```
pub struct ClosureDimensionFilter {
    filter: Box<dyn Fn(usize) -> bool>,
}

impl ClosureDimensionFilter {
    /// Creates a new closure dimension filter.
    #[must_use]
    pub fn new(filter: impl Fn(usize) -> bool + 'static) -> Self {
        Self {
            filter: Box::new(filter),
        }
    }
}

impl DimensionFilter for ClosureDimensionFilter {
    fn accepts(&self, index: usize) -> bool {
        (self.filter)(index)
    }
}

/// A dimension filter that uses a boolean vector.
///
/// # Examples
///
/// ```rust
/// use csvbinmatrix::prelude::{BoolVecDimensionFilter, DimensionFilter};
///
/// let filter = match
///     BoolVecDimensionFilter::new(vec![true, false, false, true], 4) {
///         Ok(filter) => filter,
///         Err(err) => panic!("[ERROR] {err}"),
/// };
/// assert!(filter.accepts(0));
/// assert!(!filter.accepts(1));
///
/// assert_eq!(filter.into_boolean_vector(), vec![true, false, false, true]);
/// ```
#[derive(PartialEq, Debug, Clone)]
pub struct BoolVecDimensionFilter {
    filter: Vec<bool>,
}

/// Error when the boolean vector's length does not match the given dimension.
#[derive(Debug, Error)]
#[error("The boolean vector's length ({given_dimension}) must match the given dimension ({expected_dimension})")]
pub struct DimensionMismatchError {
    given_dimension: usize,
    expected_dimension: usize,
}

impl BoolVecDimensionFilter {
    /// Creates a new boolean vector dimension filter.
    ///
    /// # Arguments
    ///
    /// * `filter` - The boolean vector.
    /// * `dimension_size` - The size of the dimension to filter.
    ///
    /// # Errors
    ///
    /// * [`DimensionMismatchError`] - if the boolean vector's length does not match the given dimension.
    ///
    pub fn new(filter: Vec<bool>, dimension_size: usize) -> Result<Self, DimensionMismatchError> {
        if filter.len() != dimension_size {
            return Err(DimensionMismatchError {
                given_dimension: filter.len(),
                expected_dimension: dimension_size,
            });
        }
        Ok(Self { filter })
    }

    /// Take the ownership of the boolean vector by consuming the filter structure.
    #[must_use]
    pub fn into_boolean_vector(self) -> Vec<bool> {
        self.filter
    }
}

impl DimensionFilter for BoolVecDimensionFilter {
    fn accepts(&self, index: usize) -> bool {
        self.filter[index]
    }
}

impl AsRef<Vec<bool>> for BoolVecDimensionFilter {
    fn as_ref(&self) -> &Vec<bool> {
        &self.filter
    }
}

#[cfg(test)]
mod tests {

    mod closure_filter {
        use super::super::ClosureDimensionFilter;
        use super::super::DimensionFilter;
        use rstest::{fixture, rstest};

        #[fixture]
        pub fn row_filter_closure() -> ClosureDimensionFilter {
            ClosureDimensionFilter {
                filter: Box::new(|i| i != 0),
            }
        }

        #[rstest]
        fn new(row_filter_closure: ClosureDimensionFilter) {
            assert_eq!(
                (ClosureDimensionFilter::new(|i| i != 0).filter)(0),
                (row_filter_closure.filter)(0)
            );
        }

        #[rstest]
        fn accepts(row_filter_closure: ClosureDimensionFilter) {
            assert!(!row_filter_closure.accepts(0));
            assert!(row_filter_closure.accepts(1));
        }
    }

    mod boolvec_filter {
        use super::super::DimensionFilter;
        use super::super::{BoolVecDimensionFilter, DimensionMismatchError};
        use pretty_assertions::{assert_eq, assert_str_eq};
        use rstest::{fixture, rstest};

        #[fixture]
        fn column_filter_boolvec() -> BoolVecDimensionFilter {
            BoolVecDimensionFilter {
                filter: vec![true, false, false, true],
            }
        }

        #[rstest]
        fn debug(column_filter_boolvec: BoolVecDimensionFilter) {
            assert_eq!(
                format!("{column_filter_boolvec:?}"),
                "BoolVecDimensionFilter { filter: [true, false, false, true] }"
            );
        }

        #[rstest]
        fn clone(column_filter_boolvec: BoolVecDimensionFilter) {
            assert_eq!(column_filter_boolvec, column_filter_boolvec.clone());
        }

        #[rstest]
        fn new(column_filter_boolvec: BoolVecDimensionFilter) {
            assert_eq!(
                BoolVecDimensionFilter::new(vec![true, false, false, true], 4).unwrap(),
                column_filter_boolvec
            );
        }

        #[rstest]
        fn new_dimensions_mismatch() {
            match BoolVecDimensionFilter::new(vec![true, false, false], 4) {
                Ok(_) => panic!("expected error"),
                Err(err) => {
                    assert!(matches!(
                        err,
                        DimensionMismatchError {
                            given_dimension: 3,
                            expected_dimension: 4
                        }
                    ));
                    assert_str_eq!(
                        format!("{err:?}"),
                        "DimensionMismatchError { given_dimension: 3, expected_dimension: 4 }"
                    );
                    assert_str_eq!(
                        format!("{err}"),
                        "The boolean vector's length (3) must match the given dimension (4)"
                    );
                }
            }
        }

        #[rstest]
        fn into_boolean_vector(column_filter_boolvec: BoolVecDimensionFilter) {
            assert_eq!(
                column_filter_boolvec.into_boolean_vector(),
                vec![true, false, false, true]
            );
        }

        #[rstest]
        fn as_ref(column_filter_boolvec: BoolVecDimensionFilter) {
            assert_eq!(
                column_filter_boolvec.as_ref(),
                &vec![true, false, false, true]
            );
        }

        #[rstest]
        fn accepts(column_filter_boolvec: BoolVecDimensionFilter) {
            assert!(column_filter_boolvec.accepts(0));
            assert!(!column_filter_boolvec.accepts(1));
            assert!(!column_filter_boolvec.accepts(2));
            assert!(column_filter_boolvec.accepts(3));
        }
    }
}