#![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())
}
}
#[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> {
#[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)
}
}
#[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> {
#[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 {
#[must_use]
pub fn iter_ones_coordinates(&self) -> IterOnesCoordinates {
IterOnesCoordinates::new(self.number_of_columns, &self.distances, self.is_reversed)
}
#[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)"
);
}
}