believer 0.2.1

A toolbox to simulate error correcting code performance.
Documentation
use super::{Check, CheckView, ParityCheckMatrix};
use itertools::EitherOrBoth;
use itertools::Itertools;

pub(super) struct Concatener<'a> {
    left_matrix: &'a ParityCheckMatrix,
    right_matrix: &'a ParityCheckMatrix,
}

impl<'a> Concatener<'a> {
    pub(super) fn from(
        left_matrix: &'a ParityCheckMatrix,
        right_matrix: &'a ParityCheckMatrix,
    ) -> Self {
        Self {
            left_matrix,
            right_matrix,
        }
    }

    pub(super) fn concat_horizontally(&self) -> ParityCheckMatrix {
        let n_bits = self.left_matrix.get_n_bits() + self.right_matrix.get_n_bits();
        let checks = self.get_checks_of_horizontal_concatenation();
        ParityCheckMatrix::with_n_bits(n_bits).with_checks(checks)
    }

    fn get_checks_of_horizontal_concatenation(&self) -> Vec<Check> {
        self.left_matrix
            .checks_iter()
            .zip_longest(self.right_matrix.checks_iter())
            .map(|checks| self.concat_horizontally_checks(checks))
            .collect()
    }

    fn concat_horizontally_checks(&self, checks: EitherOrBoth<CheckView, CheckView>) -> Check {
        match checks {
            EitherOrBoth::Both(left_check, right_check) => self.concat(left_check, right_check),
            EitherOrBoth::Left(check) => check.to_vec(),
            EitherOrBoth::Right(check) => self.pad_right_check(check),
        }
    }

    fn concat(&self, left_check: CheckView, right_check: CheckView) -> Check {
        let mut check = left_check.to_vec();
        check.append(&mut self.pad_right_check(right_check));
        check
    }

    fn pad_right_check(&self, check: CheckView) -> Check {
        check
            .iter()
            .map(|bit| bit + self.left_matrix.get_n_bits())
            .collect()
    }

    pub(super) fn concat_diagonally(&self) -> ParityCheckMatrix {
        let n_bits = self.left_matrix.get_n_bits() + self.right_matrix.get_n_bits();
        let checks = self.get_checks_of_diagonal_concatenation();
        ParityCheckMatrix::with_n_bits(n_bits).with_checks(checks)
    }

    fn get_checks_of_diagonal_concatenation(&self) -> Vec<Check> {
        let n_checks = self.left_matrix.get_n_checks() + self.right_matrix.get_n_checks();
        let mut checks = Vec::with_capacity(n_checks);
        checks.append(&mut self.get_all_left_checks());
        checks.append(&mut self.get_all_padded_right_checks());
        checks
    }

    fn get_all_left_checks(&self) -> Vec<Check> {
        self.left_matrix
            .checks_iter()
            .map(|check| check.to_vec())
            .collect()
    }

    fn get_all_padded_right_checks(&self) -> Vec<Check> {
        self.right_matrix
            .checks_iter()
            .map(|check| self.pad_right_check(check))
            .collect()
    }
}

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

    #[test]
    fn horizontal_concat_with_empty_matrix() {
        let left_matrix =
            ParityCheckMatrix::with_n_bits(4).with_checks(vec![vec![0, 1], vec![1, 2, 3]]);

        let right_matrix = ParityCheckMatrix::new();

        let concatened = Concatener::from(&left_matrix, &right_matrix).concat_horizontally();

        assert_eq!(concatened, left_matrix);
    }

    #[test]
    fn horizontal_concat_from_empty_matrix() {
        let left_matrix = ParityCheckMatrix::new();

        let right_matrix =
            ParityCheckMatrix::with_n_bits(4).with_checks(vec![vec![0, 1], vec![1, 2, 3]]);

        let concatened = Concatener::from(&left_matrix, &right_matrix).concat_horizontally();

        assert_eq!(concatened, right_matrix);
    }

    #[test]
    fn horizontal_concat_with_smaller_left_matrix() {
        let left_matrix =
            ParityCheckMatrix::with_n_bits(4).with_checks(vec![vec![0, 1], vec![1, 2, 3]]);

        let right_matrix =
            ParityCheckMatrix::with_n_bits(3).with_checks(vec![vec![0, 1], vec![1, 2], vec![0, 2]]);

        let concatened = Concatener::from(&left_matrix, &right_matrix).concat_horizontally();
        let expected = ParityCheckMatrix::with_n_bits(7).with_checks(vec![
            vec![0, 1, 4, 5],
            vec![1, 2, 3, 5, 6],
            vec![4, 6],
        ]);

        assert_eq!(concatened, expected);
    }

    #[test]
    fn horizontal_concat_with_smaller_right_matrix() {
        let left_matrix =
            ParityCheckMatrix::with_n_bits(3).with_checks(vec![vec![0, 1], vec![1, 2], vec![0, 2]]);
        let right_matrix =
            ParityCheckMatrix::with_n_bits(4).with_checks(vec![vec![0, 1], vec![1, 2, 3]]);

        let concatened = Concatener::from(&left_matrix, &right_matrix).concat_horizontally();
        let expected = ParityCheckMatrix::with_n_bits(7).with_checks(vec![
            vec![0, 1, 3, 4],
            vec![1, 2, 4, 5, 6],
            vec![0, 2],
        ]);

        assert_eq!(concatened, expected);
    }

    #[test]
    fn horizontal_concat_with_equal_length_matrices() {
        let left_matrix =
            ParityCheckMatrix::with_n_bits(4).with_checks(vec![vec![0, 1], vec![1, 2], vec![2, 3]]);
        let right_matrix = ParityCheckMatrix::with_n_bits(4).with_checks(vec![
            vec![0, 1],
            vec![1, 2, 3],
            vec![0, 2, 3],
        ]);

        let concatened = Concatener::from(&left_matrix, &right_matrix).concat_horizontally();
        let expected = ParityCheckMatrix::with_n_bits(8).with_checks(vec![
            vec![0, 1, 4, 5],
            vec![1, 2, 5, 6, 7],
            vec![2, 3, 4, 6, 7],
        ]);

        assert_eq!(concatened, expected);
    }

    #[test]
    fn diagonal_concat_with_empty_matrix() {
        let left_matrix =
            ParityCheckMatrix::with_n_bits(4).with_checks(vec![vec![0, 1], vec![1, 2], vec![2, 3]]);
        let right_matrix = ParityCheckMatrix::new();

        let concatened = Concatener::from(&left_matrix, &right_matrix).concat_diagonally();

        assert_eq!(concatened, left_matrix);
    }

    #[test]
    fn diagonal_concat_from_empty_matrix() {
        let left_matrix = ParityCheckMatrix::new();

        let right_matrix =
            ParityCheckMatrix::with_n_bits(4).with_checks(vec![vec![0, 1], vec![1, 2], vec![2, 3]]);

        let concatened = Concatener::from(&left_matrix, &right_matrix).concat_diagonally();

        assert_eq!(concatened, right_matrix);
    }

    #[test]
    fn diagonal_concat() {
        let left_matrix =
            ParityCheckMatrix::with_n_bits(4).with_checks(vec![vec![0, 1], vec![1, 2], vec![2, 3]]);
        let right_matrix =
            ParityCheckMatrix::with_n_bits(4).with_checks(vec![vec![0, 1], vec![1, 2, 3]]);

        let concatened = Concatener::from(&left_matrix, &right_matrix).concat_diagonally();
        let expected = ParityCheckMatrix::with_n_bits(8).with_checks(vec![
            vec![0, 1],
            vec![1, 2],
            vec![2, 3],
            vec![4, 5],
            vec![5, 6, 7],
        ]);

        assert_eq!(concatened, expected);
    }
}