aoc-core 0.1.2

Useful Advent of Code data structures, types and functions common to my Rust solutions.
Documentation
use std::ops::{Index, IndexMut};

use grid::Grid;
use itertools::Itertools;

use crate::pos::{Pos, PosGet};

/// Creates a new `u8` grid from a string slice.
#[inline]
#[must_use]
pub fn bytes_grid(input: &str) -> Grid<u8> {
    Grid::from(
        input
            .lines()
            .map(|line| line.bytes().collect())
            .collect_vec(),
    )
}

// ------------------------------------------------------------------------------------------------
// Grid trait implementations

impl<V> PosGet<V> for Grid<V> {
    #[inline]
    fn pos_get(&self, pos: Pos) -> Option<&V> {
        self.get(pos.x, pos.y)
    }

    #[inline]
    fn pos_get_mut(&mut self, pos: Pos) -> Option<&mut V> {
        self.get_mut(pos.x, pos.y)
    }
}

impl<V> Index<Pos> for Grid<V> {
    type Output = V;

    fn index(&self, index: Pos) -> &Self::Output {
        #[allow(clippy::cast_sign_loss)]
        &self[(index.x as usize, index.y as usize)]
    }
}

impl<V> IndexMut<Pos> for Grid<V> {
    fn index_mut(&mut self, index: Pos) -> &mut Self::Output {
        #[allow(clippy::cast_sign_loss)]
        &mut self[(index.x as usize, index.y as usize)]
    }
}

// ------------------------------------------------------------------------------------------------
// GridMask

/// Boolean mask for grid-like data.
#[derive(Debug)]
pub struct GridMask {
    /// Underlying boolean array.
    mask: Vec<bool>,

    /// Columns number.
    cols: usize,
}

impl GridMask {
    /// Creates a new all-false grid mask with the specified `rows` and `cols`.
    #[must_use]
    pub fn new((rows, cols): (usize, usize)) -> Self {
        Self {
            mask: vec![false; rows * cols],
            cols,
        }
    }

    /// Sets the specified `pos` to `false`.
    ///
    /// Returns `true` if the value changed, or `false` if it did not.
    #[inline]
    pub fn set_false(&mut self, pos: Pos) -> bool {
        if self[pos] {
            self[pos] = false;
            true
        } else {
            false
        }
    }

    /// Sets the specified `pos` to `true`.
    ///
    /// Returns `true` if the value changed, or `false` if it did not.
    #[inline]
    pub fn set_true(&mut self, pos: Pos) -> bool {
        if self[pos] {
            false
        } else {
            self[pos] = true;
            true
        }
    }

    /// Toggles the specified `pos` between `false` and `true` and vice versa.
    #[inline]
    pub fn toggle(&mut self, pos: Pos) {
        self[pos] = !self[pos];
    }
}

impl Index<Pos> for GridMask {
    type Output = bool;

    #[inline]
    fn index(&self, pos: Pos) -> &Self::Output {
        #[allow(clippy::cast_sign_loss)]
        &self.mask[pos.x as usize * self.cols + pos.y as usize]
    }
}

impl IndexMut<Pos> for GridMask {
    #[inline]
    fn index_mut(&mut self, pos: Pos) -> &mut Self::Output {
        #[allow(clippy::cast_sign_loss)]
        &mut self.mask[pos.x as usize * self.cols + pos.y as usize]
    }
}

#[cfg(test)]
mod tests {
    use super::{GridMask, PosGet, bytes_grid};
    use crate::pos::Pos;

    #[test]
    fn bytes_grid_basic() {
        let input = ("abc\ndef", [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2)]);
        let expected = (2, 3, b"abcdef");
        let grid = bytes_grid(input.0);
        let output = (grid.rows(), grid.cols(), &input.1.map(|c| grid[c]));
        assert_eq!(expected, output, "\n input: {input:?}");
    }

    // ------------------------------------------------------------------------------------------------
    // PosGet

    #[test]
    fn grid_pos_get() {
        let input = ("abc\ndef", [Pos::new(1, 1), Pos::new(0, 3)]);
        let expected = [Some(&b'e'), None];
        let grid = bytes_grid(input.0);
        let output = input.1.map(|p| grid.pos_get(p));
        assert_eq!(expected, output, "\n input: {input:?}");
    }

    #[test]
    fn grid_pos_get_mut() {
        let input = ("abc\ndef", (1, 1), b'x');
        let expected = b'x';
        let mut grid = bytes_grid(input.0);
        *grid
            .pos_get_mut(Pos::from(input.1))
            .expect("Expected in-bound position") = input.2;
        let output = grid[input.1];
        assert_eq!(expected, output, "\n input: {input:?}");
    }

    #[test]
    fn grid_index_pos() {
        let input = ("abc\ndef", Pos::new(1, 1));
        let expected = b'e';
        let grid = bytes_grid(input.0);
        let output = grid[input.1];
        assert_eq!(expected, output, "\n input: {input:?}");
    }

    #[test]
    fn grid_indexmut_pos() {
        let input = ("abc\ndef", (1, 1), b'x');
        let expected = b'x';
        let mut grid = bytes_grid(input.0);
        grid[Pos::from(input.1)] = input.2;
        let output = grid[input.1];
        assert_eq!(expected, output, "\n input: {input:?}");
    }

    // ---------- GridMask ----------

    #[test]
    fn gridmask_new() {
        let input = (2, 3);
        let expected = (vec![false; 6], 3);
        let gridmask = GridMask::new(input);
        let output = (gridmask.mask, gridmask.cols);
        assert_eq!(expected, output, "\n input: {input:?}");
    }

    #[test]
    fn gridmask_set_false() {
        let input = ((2, 3), Pos::new(1, 1));
        let expected = (false, false, true, false);
        let mut gridmask = GridMask::new(input.0);
        let output = (
            gridmask[input.1],
            gridmask.set_false(input.1),
            {
                gridmask[input.1] = true;
                gridmask.set_false(input.1)
            },
            gridmask[input.1],
        );
        assert_eq!(expected, output, "\n input: {input:?}");
    }

    #[test]
    fn gridmask_set_true() {
        let input = ((2, 3), Pos::new(1, 1));
        let expected = (false, true, true, false);
        let mut gridmask = GridMask::new(input.0);
        let output = (
            gridmask[input.1],
            gridmask.set_true(input.1),
            gridmask[input.1],
            gridmask.set_true(input.1),
        );
        assert_eq!(expected, output, "\n input: {input:?}");
    }

    #[test]
    fn gridmask_toggle() {
        let input = ((2, 3), Pos::new(1, 1));
        let expected = (false, true, false);
        let mut gridmask = GridMask::new(input.0);
        let output = (
            gridmask[input.1],
            {
                gridmask.toggle(input.1);
                gridmask[input.1]
            },
            {
                gridmask.toggle(input.1);
                gridmask[input.1]
            },
        );
        assert_eq!(expected, output, "\n input: {input:?}");
    }

    #[test]
    fn gridmask_index_pos() {
        let input = ((2, 3), [Pos::new(0, 2), Pos::new(1, 1)]);
        let expected = [true, false];
        let mut gridmask = GridMask::new(input.0);
        gridmask.set_true(input.1[0]);
        let output = input.1.map(|p| gridmask[p]);
        assert_eq!(expected, output, "\n input: {input:?}");
    }

    #[test]
    fn gridmask_indexmut_pos() {
        let input = ((2, 3), [Pos::new(0, 2), Pos::new(1, 1)]);
        let expected = [true, false, false, true];
        let mut gridmask = GridMask::new(input.0);
        gridmask.set_true(input.1[0]);
        let mut output = [false; 4];
        output[0] = gridmask[input.1[0]];
        output[1] = gridmask[input.1[1]];
        gridmask[input.1[0]] = !gridmask[input.1[0]];
        gridmask[input.1[1]] = !gridmask[input.1[1]];
        output[2] = gridmask[input.1[0]];
        output[3] = gridmask[input.1[1]];
        assert_eq!(expected, output, "\n input: {input:?}");
    }
}