csvbinmatrix 0.8.0

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

use crate::prelude::Coordinates;
use num_traits::Euclid;
use thiserror::Error;

use super::CSVBinaryMatrix;

#[derive(Error, Debug)]
#[error("The distance implies a negative row coordinate (-{surplus_rows})")]
pub(crate) struct BackwardOverflow {
    pub(crate) surplus_rows: usize,
}

pub(crate) struct OnesCoordinatesColumnCursor {
    number_of_columns: usize,
    coordinates: Coordinates,
}

impl OnesCoordinatesColumnCursor {
    #[must_use]
    pub fn new(number_of_columns: usize, coordinates: Coordinates) -> Self {
        Self {
            number_of_columns,
            coordinates,
        }
    }

    pub fn forward(&mut self, distance: usize) -> Coordinates {
        *self.coordinates.mut_column() += distance;
        if self.coordinates.column() >= self.number_of_columns {
            let (quotient, remainder) =
                Euclid::div_rem_euclid(&self.coordinates.column(), &self.number_of_columns);
            *self.coordinates.mut_row() += quotient;
            *self.coordinates.mut_column() = remainder;
        }
        self.coordinates.clone()
    }

    pub fn unchecked_backward(&mut self, distance: usize) -> Coordinates {
        if distance > self.coordinates.column() {
            let (quotient, remainder) = Euclid::div_rem_euclid(
                &(self.number_of_columns + distance - self.coordinates.column() - 1),
                &self.number_of_columns,
            );
            *self.coordinates.mut_row() -= quotient;
            *self.coordinates.mut_column() = self.number_of_columns - remainder - 1;
        } else {
            *self.coordinates.mut_column() -= distance;
        }
        self.coordinates.clone()
    }

    pub fn try_backward(&mut self, distance: usize) -> Result<Coordinates, BackwardOverflow> {
        if distance > self.coordinates.column() {
            let (quotient, remainder) = Euclid::div_rem_euclid(
                &(self.number_of_columns + distance - self.coordinates.column() - 1),
                &self.number_of_columns,
            );
            if quotient > self.coordinates.row() {
                return Err(BackwardOverflow {
                    surplus_rows: quotient - self.coordinates.row(),
                });
            }
            *self.coordinates.mut_row() -= quotient;
            *self.coordinates.mut_column() = self.number_of_columns - remainder - 1;
        } else {
            *self.coordinates.mut_column() -= distance;
        }
        Ok(self.coordinates.clone())
    }
}

/// Iterator over the ones coordinates in the matrix.
#[allow(clippy::module_name_repetitions)]
pub struct IterOnesCoordinates<'a> {
    ones_coordinates_column_cursor: OnesCoordinatesColumnCursor,
    curr_index_distances: usize,
    distances: &'a Vec<usize>,
    distances_iter_direction_fn: fn(&mut Self) -> Option<Coordinates>,
}

impl<'a> IterOnesCoordinates<'a> {
    /// Create a new iterator over the ones coordinates in the matrix.
    #[must_use]
    pub fn new(number_of_columns: usize, distances: &'a Vec<usize>, is_reversed: bool) -> Self {
        let ones_coordinates_column_cursor =
            OnesCoordinatesColumnCursor::new(number_of_columns, Coordinates::new(0, 0));
        if is_reversed {
            return Self {
                ones_coordinates_column_cursor,
                curr_index_distances: distances.len() - 1,
                distances,
                distances_iter_direction_fn: IterOnesCoordinates::backward_distances_iter,
            };
        }
        Self {
            ones_coordinates_column_cursor,
            curr_index_distances: 0,
            distances,
            distances_iter_direction_fn: IterOnesCoordinates::forward_distances_iter,
        }
    }

    fn forward_distances_iter(&mut self) -> Option<Coordinates> {
        if self.curr_index_distances == self.distances.len() - 1 {
            return None;
        }
        let distance = unsafe { *self.distances.get_unchecked(self.curr_index_distances) };
        self.curr_index_distances += 1;
        Some(self.ones_coordinates_column_cursor.forward(distance))
    }

    fn backward_distances_iter(&mut self) -> Option<Coordinates> {
        if self.curr_index_distances == 0 {
            return None;
        }
        let distance = unsafe { *self.distances.get_unchecked(self.curr_index_distances) };
        self.curr_index_distances -= 1;
        Some(self.ones_coordinates_column_cursor.forward(distance))
    }
}

impl<'a> Iterator for IterOnesCoordinates<'a> {
    type Item = Coordinates;

    fn next(&mut self) -> Option<Self::Item> {
        (self.distances_iter_direction_fn)(self)
    }
}

/// Iterator over the ones coordinates in the matrix in the reverse order.
#[allow(clippy::module_name_repetitions)]
pub struct IterOnesCoordinatesFromEnd<'a> {
    ones_coordinates_column_cursor: OnesCoordinatesColumnCursor,
    curr_index_distances: usize,
    distances: &'a Vec<usize>,
    distances_iter_direction_fn: fn(&mut Self) -> Option<Coordinates>,
}

impl<'a> IterOnesCoordinatesFromEnd<'a> {
    /// Create a new iterator over the ones coordinates in the matrix in reverse order.
    #[must_use]
    pub fn new(
        number_of_rows: usize,
        number_of_columns: usize,
        distances: &'a Vec<usize>,
        is_reversed: bool,
    ) -> Self {
        let ones_coordinates_column_cursor = OnesCoordinatesColumnCursor::new(
            number_of_columns,
            Coordinates::new(number_of_rows - 1, number_of_columns - 1),
        );
        if is_reversed {
            return Self {
                ones_coordinates_column_cursor,
                curr_index_distances: 0,
                distances,
                distances_iter_direction_fn: IterOnesCoordinatesFromEnd::forward_distances_iter,
            };
        }
        Self {
            ones_coordinates_column_cursor,
            curr_index_distances: distances.len() - 1,
            distances,
            distances_iter_direction_fn: IterOnesCoordinatesFromEnd::backward_distances_iter,
        }
    }

    fn forward_distances_iter(&mut self) -> Option<Coordinates> {
        if self.curr_index_distances == self.distances.len() - 1 {
            return None;
        }
        let distance = unsafe { *self.distances.get_unchecked(self.curr_index_distances) };
        self.curr_index_distances += 1;
        Some(
            self.ones_coordinates_column_cursor
                .unchecked_backward(distance),
        )
    }

    fn backward_distances_iter(&mut self) -> Option<Coordinates> {
        if self.curr_index_distances == 0 {
            return None;
        }
        let distance = unsafe { *self.distances.get_unchecked(self.curr_index_distances) };
        self.curr_index_distances -= 1;
        Some(
            self.ones_coordinates_column_cursor
                .unchecked_backward(distance),
        )
    }
}

impl<'a> Iterator for IterOnesCoordinatesFromEnd<'a> {
    type Item = Coordinates;

    fn next(&mut self) -> Option<Self::Item> {
        (self.distances_iter_direction_fn)(self)
    }
}

impl<'distvec> CSVBinaryMatrix {
    /// Returns an iterator over the ones coordinates in the matrix.
    ///
    /// # Example
    ///
    /// ```rust
    /// # use csvbinmatrix::prelude::CSVBinaryMatrix;
    /// use csvbinmatrix::prelude::Coordinates;
    ///
    /// # let matrix = CSVBinaryMatrix::try_from(&[
    /// #     [0, 0, 0],
    /// #     [0, 0, 1],
    /// #     [0, 1, 1],
    /// #     [1, 1, 1],
    /// # ]).unwrap();
    /// let mut ones_coordinates = matrix.iter_ones_coordinates();
    /// assert_eq!(ones_coordinates.next(), Some(Coordinates::new(1, 2)));
    /// assert_eq!(ones_coordinates.next(), Some(Coordinates::new(2, 1)));
    /// assert_eq!(ones_coordinates.next(), Some(Coordinates::new(2, 2)));
    /// assert_eq!(ones_coordinates.next(), Some(Coordinates::new(3, 0)));
    /// assert_eq!(ones_coordinates.next(), Some(Coordinates::new(3, 1)));
    /// assert_eq!(ones_coordinates.next(), Some(Coordinates::new(3, 2)));
    /// assert_eq!(ones_coordinates.next(), None);
    /// ```
    #[must_use]
    pub fn iter_ones_coordinates(&self) -> IterOnesCoordinates {
        IterOnesCoordinates::new(self.number_of_columns, &self.distances, self.is_reversed)
    }

    /// Returns an iterator over the ones coordinates in the matrix by going from the end.
    ///
    /// # Example
    ///
    /// ```rust
    /// # use csvbinmatrix::prelude::CSVBinaryMatrix;
    /// use csvbinmatrix::prelude::Coordinates;
    ///
    /// #
    /// # let matrix = CSVBinaryMatrix::try_from(&[
    /// #     [0, 0, 0],
    /// #     [0, 0, 1],
    /// #     [0, 1, 1],
    /// #     [1, 1, 1],
    /// # ]).unwrap();
    /// let mut ones_coordinates = matrix.iter_ones_coordinates_from_end();
    /// assert_eq!(ones_coordinates.next(), Some(Coordinates::new(3, 2)));
    /// assert_eq!(ones_coordinates.next(), Some(Coordinates::new(3, 1)));
    /// assert_eq!(ones_coordinates.next(), Some(Coordinates::new(3, 0)));
    /// assert_eq!(ones_coordinates.next(), Some(Coordinates::new(2, 2)));
    /// assert_eq!(ones_coordinates.next(), Some(Coordinates::new(2, 1)));
    /// assert_eq!(ones_coordinates.next(), Some(Coordinates::new(1, 2)));
    /// assert_eq!(ones_coordinates.next(), None);
    /// ```
    #[must_use]
    pub fn iter_ones_coordinates_from_end(&'distvec self) -> IterOnesCoordinatesFromEnd {
        IterOnesCoordinatesFromEnd::<'distvec>::new(
            self.number_of_rows,
            self.number_of_columns,
            &self.distances,
            self.is_reversed,
        )
    }
}

#[cfg(test)]
mod tests {
    use super::super::tests::{matrix_a, matrix_a_bis, zeros_matrix};
    use super::BackwardOverflow;
    use crate::matrix::items::Coordinates;
    use crate::matrix::CSVBinaryMatrix;
    use rstest::rstest;

    use pretty_assertions::{assert_eq, assert_str_eq};

    #[rstest]
    fn iter_ones_coordinates(
        zeros_matrix: CSVBinaryMatrix,
        matrix_a: CSVBinaryMatrix,
        matrix_a_bis: CSVBinaryMatrix,
    ) {
        assert_eq!(zeros_matrix.iter_ones_coordinates().count(), 0);
        let mut ones_coordinates = matrix_a.iter_ones_coordinates();
        assert_eq!(ones_coordinates.next(), Some(Coordinates::new(0, 0)));
        assert_eq!(ones_coordinates.next(), Some(Coordinates::new(0, 1)));
        assert_eq!(ones_coordinates.next(), Some(Coordinates::new(0, 2)));
        assert_eq!(ones_coordinates.next(), Some(Coordinates::new(1, 0)));
        assert_eq!(ones_coordinates.next(), Some(Coordinates::new(2, 1)));
        assert_eq!(ones_coordinates.next(), None);

        let mut ones_coordinates = matrix_a_bis.iter_ones_coordinates();
        assert_eq!(ones_coordinates.next(), Some(Coordinates::new(0, 0)));
        assert_eq!(ones_coordinates.next(), Some(Coordinates::new(0, 1)));
        assert_eq!(ones_coordinates.next(), Some(Coordinates::new(0, 2)));
        assert_eq!(ones_coordinates.next(), Some(Coordinates::new(1, 0)));
        assert_eq!(ones_coordinates.next(), Some(Coordinates::new(2, 1)));
        assert_eq!(ones_coordinates.next(), None);
    }

    #[rstest]
    fn iter_ones_coordinates_from_end(
        zeros_matrix: CSVBinaryMatrix,
        matrix_a: CSVBinaryMatrix,
        matrix_a_bis: CSVBinaryMatrix,
    ) {
        assert_eq!(zeros_matrix.iter_ones_coordinates_from_end().count(), 0);
        let mut ones_coordinates = matrix_a.iter_ones_coordinates_from_end();
        assert_eq!(ones_coordinates.next(), Some(Coordinates::new(2, 1)));
        assert_eq!(ones_coordinates.next(), Some(Coordinates::new(1, 0)));
        assert_eq!(ones_coordinates.next(), Some(Coordinates::new(0, 2)));
        assert_eq!(ones_coordinates.next(), Some(Coordinates::new(0, 1)));
        assert_eq!(ones_coordinates.next(), Some(Coordinates::new(0, 0)));
        assert_eq!(ones_coordinates.next(), None);

        let mut ones_coordinates = matrix_a_bis.iter_ones_coordinates_from_end();
        assert_eq!(ones_coordinates.next(), Some(Coordinates::new(2, 1)));
        assert_eq!(ones_coordinates.next(), Some(Coordinates::new(1, 0)));
        assert_eq!(ones_coordinates.next(), Some(Coordinates::new(0, 2)));
        assert_eq!(ones_coordinates.next(), Some(Coordinates::new(0, 1)));
        assert_eq!(ones_coordinates.next(), Some(Coordinates::new(0, 0)));
        assert_eq!(ones_coordinates.next(), None);
    }

    #[rstest]
    fn backward_overflow() {
        let error = BackwardOverflow { surplus_rows: 1 };
        assert_str_eq!(
            format!("{:?}", error),
            "BackwardOverflow { surplus_rows: 1 }"
        );
        assert_str_eq!(
            format!("{}", error),
            "The distance implies a negative row coordinate (-1)"
        );
    }
}