#![doc = include_str!("../../docs/matrix/ops.md")]
use thiserror::Error;
use super::items::Coordinates;
use super::iter::OnesCoordinatesColumnCursor;
use super::CSVBinaryMatrix;
use crate::filters::DimensionFilter;
#[derive(Error, Debug, PartialEq)]
#[error("The resulting matrix is degenerated ({number_of_rows}x{number_of_columns})")]
pub struct DegeneratedMatrixError {
number_of_rows: usize,
number_of_columns: usize,
}
struct SubCSVBMFactory<'filter, R: DimensionFilter, C: DimensionFilter> {
number_of_rows: usize,
number_of_columns: usize,
coordinates_prev: Coordinates,
row_number_correction: usize,
col_number_correction: usize,
distances: Vec<usize>,
supermatrix_is_reversed: bool,
row_filter: &'filter R,
column_filter: &'filter C,
}
impl<'filter, R, C> SubCSVBMFactory<'filter, R, C>
where
R: DimensionFilter,
C: DimensionFilter,
{
fn new(
super_number_of_rows: usize,
super_number_of_cols: usize,
super_is_reversed: bool,
row_filter: &'filter R,
column_filter: &'filter C,
) -> Self {
Self {
number_of_rows: (0..super_number_of_rows)
.filter(|&i| row_filter.accepts(i))
.count(),
number_of_columns: (0..super_number_of_cols)
.filter(|&j| column_filter.accepts(j))
.count(),
coordinates_prev: Coordinates::new(0, 0),
row_number_correction: 0,
col_number_correction: 0,
distances: Vec::new(),
supermatrix_is_reversed: super_is_reversed,
row_filter,
column_filter,
}
}
fn new_reverse(
super_number_of_rows: usize,
super_number_of_cols: usize,
super_is_reversed: bool,
row_filter: &'filter R,
column_filter: &'filter C,
) -> Self {
let sub_number_of_rows = (0..super_number_of_rows)
.filter(|&i| row_filter.accepts(i))
.count();
let sub_number_of_cols = (0..super_number_of_cols)
.filter(|&j| column_filter.accepts(j))
.count();
let curr_row = match sub_number_of_rows {
0 => 0,
_ => sub_number_of_rows - 1,
};
let curr_col = match sub_number_of_cols {
0 => 0,
_ => sub_number_of_cols - 1,
};
Self {
number_of_rows: sub_number_of_rows,
number_of_columns: sub_number_of_cols,
coordinates_prev: Coordinates::new(curr_row, curr_col),
row_number_correction: super_number_of_rows - sub_number_of_rows,
col_number_correction: super_number_of_cols - sub_number_of_cols,
distances: Vec::new(),
supermatrix_is_reversed: super_is_reversed,
row_filter,
column_filter,
}
}
fn is_degenerated(&self) -> bool {
self.number_of_rows == 0 || self.number_of_columns == 0
}
fn update_row_correction(
&mut self,
coordinates_prev: &Coordinates,
coordinates_curr: &Coordinates,
) {
if coordinates_prev.row() < coordinates_curr.row() {
self.row_number_correction += (coordinates_prev.row()..coordinates_curr.row())
.filter(|&i| !self.row_filter.accepts(i))
.count();
}
}
fn update_col_correction(
&mut self,
coordinates_prev: &Coordinates,
coordinates_curr: &Coordinates,
) {
match coordinates_prev.column().cmp(&coordinates_curr.column()) {
std::cmp::Ordering::Greater => {
self.col_number_correction = (0..coordinates_curr.column())
.filter(|&j| !self.column_filter.accepts(j))
.count();
}
std::cmp::Ordering::Less => {
self.col_number_correction += (coordinates_prev.column()
..coordinates_curr.column())
.filter(|&j| !self.column_filter.accepts(j))
.count();
}
std::cmp::Ordering::Equal => {}
}
}
fn update_row_correction_backward(
&mut self,
coordinates_prev: &Coordinates,
coordinates_curr: &Coordinates,
) {
if coordinates_curr.row() < coordinates_prev.row() {
self.row_number_correction -= (coordinates_curr.row()..coordinates_prev.row())
.filter(|&i| !self.row_filter.accepts(i))
.count();
}
}
fn update_col_correction_backward(
&mut self,
coordinates_prev: &Coordinates,
coordinates_curr: &Coordinates,
super_number_of_cols: usize,
) {
match coordinates_prev.column().cmp(&coordinates_curr.column()) {
std::cmp::Ordering::Greater => {
self.col_number_correction -= (coordinates_curr.column()
..coordinates_prev.column())
.filter(|&j| !self.column_filter.accepts(j))
.count();
}
std::cmp::Ordering::Less => {
self.col_number_correction = super_number_of_cols
- self.number_of_columns
- (coordinates_curr.column()..super_number_of_cols)
.filter(|&j| !self.column_filter.accepts(j))
.count();
}
std::cmp::Ordering::Equal => {}
}
}
fn process_the_one(&mut self, super_coordinates_curr: &Coordinates) {
if self.row_filter.accepts(super_coordinates_curr.row())
&& self.column_filter.accepts(super_coordinates_curr.column())
{
let coordinates_curr = Coordinates::new(
super_coordinates_curr.row() - self.row_number_correction,
super_coordinates_curr.column() - self.col_number_correction,
);
let mut sub_distance =
(coordinates_curr.row() - self.coordinates_prev.row()) * self.number_of_columns;
if coordinates_curr.column() < self.coordinates_prev.column() {
sub_distance -= self.coordinates_prev.column() - coordinates_curr.column();
} else {
sub_distance += coordinates_curr.column() - self.coordinates_prev.column();
}
self.distances.push(sub_distance);
self.coordinates_prev = coordinates_curr;
}
}
fn process_the_one_backward(&mut self, super_coordinates_curr: &Coordinates) {
if self.row_filter.accepts(super_coordinates_curr.row())
&& self.column_filter.accepts(super_coordinates_curr.column())
{
let coordinates_curr = Coordinates::new(
super_coordinates_curr.row() - self.row_number_correction,
super_coordinates_curr.column() - self.col_number_correction,
);
let mut sub_distance =
(self.coordinates_prev.row() - coordinates_curr.row()) * self.number_of_columns;
if coordinates_curr.column() >= self.coordinates_prev.column() {
sub_distance -= coordinates_curr.column() - self.coordinates_prev.column();
} else {
sub_distance += self.coordinates_prev.column() - coordinates_curr.column();
}
self.distances.push(sub_distance);
self.coordinates_prev = coordinates_curr;
}
}
fn build_csvbm(mut self) -> Result<CSVBinaryMatrix, DegeneratedMatrixError> {
if self.is_degenerated() {
Err(DegeneratedMatrixError {
number_of_rows: self.number_of_rows,
number_of_columns: self.number_of_columns,
})
} else {
self.distances.push(
(self.number_of_rows - 1 - self.coordinates_prev.row()) * self.number_of_columns
+ (self.number_of_columns - 1 - self.coordinates_prev.column()),
);
Ok(CSVBinaryMatrix {
number_of_rows: self.number_of_rows,
number_of_columns: self.number_of_columns,
distances: self.distances,
is_reversed: self.supermatrix_is_reversed,
})
}
}
fn build_csvbm_backward(mut self) -> Result<CSVBinaryMatrix, DegeneratedMatrixError> {
if self.is_degenerated() {
Err(DegeneratedMatrixError {
number_of_rows: self.number_of_rows,
number_of_columns: self.number_of_columns,
})
} else {
self.distances.push(
self.coordinates_prev.row() * self.number_of_columns
+ self.coordinates_prev.column(),
);
Ok(CSVBinaryMatrix {
number_of_rows: self.number_of_rows,
number_of_columns: self.number_of_columns,
distances: self.distances,
is_reversed: !self.supermatrix_is_reversed,
})
}
}
}
impl CSVBinaryMatrix {
pub fn reverse(&mut self) {
self.is_reversed = !self.is_reversed;
}
#[must_use]
pub fn into_reversed(self) -> Self {
Self {
number_of_rows: self.number_of_rows,
number_of_columns: self.number_of_columns,
distances: self.distances,
is_reversed: !self.is_reversed,
}
}
pub fn to_submatrix<R, C>(
&self,
row_filter: &R,
column_filter: &C,
) -> Result<CSVBinaryMatrix, DegeneratedMatrixError>
where
R: DimensionFilter,
C: DimensionFilter,
{
let mut factory = SubCSVBMFactory::new(
self.number_of_rows,
self.number_of_columns,
self.is_reversed,
row_filter,
column_filter,
);
if factory.is_degenerated() {
return factory.build_csvbm();
}
let mut coordinates_prev = Coordinates::new(self.number_of_rows, self.number_of_columns);
let mut coordinates_curr: Coordinates;
let mut ones_coordinates_column_cursor_curr =
OnesCoordinatesColumnCursor::new(self.number_of_columns, Coordinates::new(0, 0));
let mut distance_index = 0;
let mut distance: usize;
while distance_index < self.distances.len() - 1 {
unsafe {
distance = *self.distances.get_unchecked(distance_index);
}
coordinates_curr = ones_coordinates_column_cursor_curr.forward(distance);
factory.update_row_correction(&coordinates_prev, &coordinates_curr);
factory.update_col_correction(&coordinates_prev, &coordinates_curr);
factory.process_the_one(&coordinates_curr);
coordinates_prev = coordinates_curr;
distance_index += 1;
}
factory.build_csvbm()
}
#[must_use]
pub fn into_submatrices<R, C>(
mut self,
mut row_col_filters: Vec<(&R, &C)>,
) -> Vec<Result<CSVBinaryMatrix, DegeneratedMatrixError>>
where
R: DimensionFilter,
C: DimensionFilter,
{
let mut factories: Vec<SubCSVBMFactory<R, C>> = Vec::with_capacity(row_col_filters.len());
while let Some((row_filter, column_filter)) = row_col_filters.pop() {
factories.push(SubCSVBMFactory::new_reverse(
self.number_of_rows,
self.number_of_columns,
self.is_reversed,
row_filter,
column_filter,
));
}
let mut ok_factories = vec![];
for factory in &mut factories {
if !factory.is_degenerated() {
ok_factories.push(factory);
}
}
let mut coordinates_prev = Coordinates::new(self.number_of_rows, self.number_of_columns);
let mut coordinates_curr: Coordinates;
let mut ones_coordinates_column_cursor_curr = OnesCoordinatesColumnCursor::new(
self.number_of_columns,
Coordinates::new(self.number_of_rows - 1, self.number_of_columns - 1),
);
let mut distance_index = self.distances.len() - 1;
let mut distance: usize;
while distance_index > 0 {
distance = self.distances.pop().unwrap();
coordinates_curr = ones_coordinates_column_cursor_curr.unchecked_backward(distance);
for factory in &mut ok_factories {
factory.update_row_correction_backward(&coordinates_prev, &coordinates_curr);
factory.update_col_correction_backward(
&coordinates_prev,
&coordinates_curr,
self.number_of_columns,
);
factory.process_the_one_backward(&coordinates_curr);
}
coordinates_prev = coordinates_curr;
distance_index -= 1;
}
let mut sub_binmatrices = Vec::with_capacity(factories.len());
while !factories.is_empty() {
sub_binmatrices.push(factories.pop().unwrap().build_csvbm_backward());
}
sub_binmatrices
}
}
#[cfg(test)]
pub(super) mod tests {
use pretty_assertions::{assert_eq, assert_str_eq};
use rstest::{fixture, rstest};
use super::super::tests::{matrix_a, zeros_matrix};
use super::super::CSVBinaryMatrix;
use super::DegeneratedMatrixError;
use crate::filters::ClosureDimensionFilter;
use crate::matrix::tests::{matrix_a_bis, zeros_matrix_bis};
#[fixture]
pub fn matrix_a_rev() -> CSVBinaryMatrix {
CSVBinaryMatrix {
number_of_rows: 3,
number_of_columns: 3,
distances: vec![0, 1, 1, 1, 4, 1],
is_reversed: true,
}
}
#[fixture]
pub fn bin_csv_nullrowcol() -> CSVBinaryMatrix {
CSVBinaryMatrix {
number_of_rows: 3,
number_of_columns: 3,
distances: vec![0, 1, 6, 1],
is_reversed: false,
}
}
#[rstest]
fn reverse(
zeros_matrix: CSVBinaryMatrix,
mut matrix_a: CSVBinaryMatrix,
matrix_a_rev: CSVBinaryMatrix,
) {
let mut zeros_bin_csv_rev = zeros_matrix.clone();
zeros_bin_csv_rev.reverse();
assert_eq!(
zeros_bin_csv_rev,
CSVBinaryMatrix {
number_of_rows: 3,
number_of_columns: 3,
distances: vec![8],
is_reversed: true,
}
);
matrix_a.reverse();
assert_eq!(matrix_a, matrix_a_rev);
}
#[rstest]
fn into_reversed(zeros_matrix: CSVBinaryMatrix, matrix_a: CSVBinaryMatrix) {
assert_eq!(
zeros_matrix.clone().into_reversed(),
CSVBinaryMatrix {
number_of_rows: 3,
number_of_columns: 3,
distances: vec![8],
is_reversed: true,
}
);
assert_eq!(
matrix_a.clone().into_reversed(),
CSVBinaryMatrix {
number_of_rows: 3,
number_of_columns: 3,
distances: vec![0, 1, 1, 1, 4, 1],
is_reversed: true,
}
);
}
#[rstest]
fn submatrix(
zeros_matrix: CSVBinaryMatrix,
matrix_a: CSVBinaryMatrix,
matrix_a_bis: CSVBinaryMatrix,
bin_csv_nullrowcol: CSVBinaryMatrix,
) {
assert_eq!(
zeros_matrix
.to_submatrix(
&ClosureDimensionFilter::new(|_| true),
&ClosureDimensionFilter::new(|_| true)
)
.unwrap(),
zeros_matrix
);
match zeros_matrix.to_submatrix(
&ClosureDimensionFilter::new(|_| false),
&ClosureDimensionFilter::new(|_| false),
) {
Ok(_) => panic!("expected DegeneratedMatrixError"),
Err(e) => {
assert!(matches!(
e,
DegeneratedMatrixError {
number_of_rows: 0,
number_of_columns: 0
}
));
assert_str_eq!(
format!("{e:?}"),
"DegeneratedMatrixError { number_of_rows: 0, number_of_columns: 0 }"
);
assert_str_eq!(format!("{e}"), "The resulting matrix is degenerated (0x0)");
}
}
assert_eq!(
matrix_a
.to_submatrix(
&ClosureDimensionFilter::new(|_| true),
&ClosureDimensionFilter::new(|_| true),
)
.unwrap(),
matrix_a,
);
assert_eq!(
matrix_a_bis
.to_submatrix(
&ClosureDimensionFilter::new(|_| true),
&ClosureDimensionFilter::new(|_| true),
)
.unwrap(),
matrix_a_bis,
);
let mut sub_bm = CSVBinaryMatrix::try_from(&[[1, 1], [0, 1]]).unwrap();
assert_eq!(
matrix_a
.to_submatrix(
&ClosureDimensionFilter::new(|i| { i != 1 }),
&ClosureDimensionFilter::new(|j| { j != 2 }),
)
.unwrap(),
sub_bm,
);
sub_bm = CSVBinaryMatrix::try_from(&[[1, 1], [0, 0]]).unwrap();
assert_eq!(
matrix_a
.to_submatrix(
&ClosureDimensionFilter::new(|i| { i != 2 }),
&ClosureDimensionFilter::new(|j| { j != 0 })
)
.unwrap(),
sub_bm
);
sub_bm = CSVBinaryMatrix::try_from(&[[0, 1, 0]]).unwrap();
assert_eq!(
matrix_a
.to_submatrix(
&ClosureDimensionFilter::new(|i| { i > 1 }),
&ClosureDimensionFilter::new(|_| true)
)
.unwrap(),
sub_bm
);
sub_bm = CSVBinaryMatrix::try_from(&[[1, 1], [1, 0]]).unwrap();
assert_eq!(
CSVBinaryMatrix {
number_of_rows: 2,
number_of_columns: 3,
distances: vec![0, 1, 1, 2, 1],
is_reversed: false,
}
.to_submatrix(
&ClosureDimensionFilter::new(|_| true),
&ClosureDimensionFilter::new(|j| { j != 0 })
)
.unwrap(),
sub_bm
);
sub_bm = CSVBinaryMatrix::try_from(&[[1, 1], [0, 1]]).unwrap();
assert_eq!(
bin_csv_nullrowcol
.to_submatrix(
&ClosureDimensionFilter::new(|i| { i != 1 }),
&ClosureDimensionFilter::new(|j| { j != 2 })
)
.unwrap(),
sub_bm
);
}
#[rstest]
fn into_submatrices(
zeros_matrix: CSVBinaryMatrix,
zeros_matrix_bis: CSVBinaryMatrix,
matrix_a: CSVBinaryMatrix,
matrix_a_bis: CSVBinaryMatrix,
bin_csv_nullrowcol: CSVBinaryMatrix,
) {
assert_eq!(
zeros_matrix.clone().into_submatrices(vec![
(
&ClosureDimensionFilter::new(|_| true),
&ClosureDimensionFilter::new(|_| true)
),
(
&ClosureDimensionFilter::new(|_| false),
&ClosureDimensionFilter::new(|_| false)
),
],),
vec![
Ok(zeros_matrix_bis),
Err(DegeneratedMatrixError {
number_of_rows: 0,
number_of_columns: 0
})
]
);
assert_eq!(
matrix_a.clone().into_submatrices(vec![(
&ClosureDimensionFilter::new(|_| true),
&ClosureDimensionFilter::new(|_| true)
),],),
vec![Ok(matrix_a_bis.clone())]
);
assert_eq!(
matrix_a_bis.clone().into_submatrices(vec![(
&ClosureDimensionFilter::new(|_| true),
&ClosureDimensionFilter::new(|_| true)
),],),
vec![Ok(matrix_a.clone())]
);
assert_eq!(
matrix_a.clone().into_submatrices(vec![(
&ClosureDimensionFilter::new(|i| { i != 1 }),
&ClosureDimensionFilter::new(|j| { j != 2 })
)],),
vec![Ok(CSVBinaryMatrix::try_from(&[[1, 0], [1, 1]])
.unwrap()
.into_reversed())]
);
assert_eq!(
matrix_a.clone().into_submatrices(vec![(
&ClosureDimensionFilter::new(|i| { i != 2 }),
&ClosureDimensionFilter::new(|j| { j != 0 })
),]),
vec![Ok(CSVBinaryMatrix::try_from(&[[0, 0], [1, 1]])
.unwrap()
.into_reversed())]
);
assert_eq!(
matrix_a.clone().into_submatrices(vec![(
&ClosureDimensionFilter::new(|i| { i > 1 }),
&ClosureDimensionFilter::new(|_| true)
),],),
vec![Ok(CSVBinaryMatrix::try_from(&[[0, 1, 0]])
.unwrap()
.into_reversed())],
);
assert_eq!(
bin_csv_nullrowcol.clone().into_submatrices(vec![(
&ClosureDimensionFilter::new(|i| { i != 1 }),
&ClosureDimensionFilter::new(|j| { j != 2 })
),],),
vec![Ok(CSVBinaryMatrix::try_from(&[[1, 0], [1, 1]])
.unwrap()
.into_reversed())],
);
}
}