grud 0.1.1

Store and access data in two-dimensional grids
Documentation
//! Default implementation of a 2-dimensional grid of elements.
//!
//! See [`Grid`] for details.

use std::{
    fmt::{Debug, Display},
    ops::{Index, IndexMut},
    slice::{Iter, IterMut},
};

use crate::point::Point;

/// A [dense] fixed-size grid that stores elements using a [`Vec`].
///
/// [dense]: https://stackoverflow.com/questions/39030196/what-exactly-is-a-dense-array
#[derive(Clone)]
pub struct Grid<T>
where
    T: Clone,
{
    data: Vec<T>,
    width: usize,
}

impl<T> Grid<T>
where
    T: Clone,
{
    /// Creates a new grid of the specified `width` and `height`, filling with `default`.
    ///
    /// # Examples
    ///
    /// ```
    /// use grud::Grid;
    ///
    /// let _ = Grid::new(3, 3, 0);
    /// ```
    pub fn new(width: usize, height: usize, default: T) -> Self {
        Self {
            data: vec![default; width * height],
            width,
        }
    }

    /// Creates a new grid of the specified `width`, inferring height from the length of the `data`.
    ///
    /// # Examples
    ///
    /// ```
    /// use grud::Grid;
    ///
    /// let grid = Grid::with_width(2, vec![1, 2, 3, 4, 5, 6]);
    /// assert_eq!(grid.width(), 2);
    /// assert_eq!(grid.height(), 3);
    /// ```
    ///
    /// # Panics
    ///
    /// If `data.len()` is not evenly divisble by `width`.
    pub fn with_width(width: usize, data: Vec<T>) -> Self {
        assert_eq!(
            data.len() % width,
            0,
            "Data length {} not divisible by {width}",
            data.len()
        );
        Self { data, width }
    }

    /// Returns the grid represnted as a flattened 2-dimensional vector.
    ///
    /// # Examples
    ///
    /// ```
    /// use grud::Grid;
    ///
    /// let grid = Grid::from(vec![
    ///   vec![1, 2],
    ///   vec![3, 4],
    ///   vec![5, 6],
    /// ]);
    ///
    /// assert_eq!(grid.as_vec(), &vec![1, 2, 3, 4, 5, 6]);
    /// ```
    pub fn as_vec(&self) -> &Vec<T> {
        &self.data
    }

    /// Returns the grid represnted by a multi-dimensional matrix (i.e. vector of vectors).
    ///
    /// # Examples
    ///
    /// ```
    /// use grud::Grid;
    ///
    /// let grid = Grid::with_width(2, vec![1, 2, 3, 4, 5, 6]);
    /// assert_eq!(grid.to_matrix(), vec![
    ///     vec![1, 2],
    ///     vec![3, 4],
    ///     vec![5, 6],
    /// ]);
    /// ```
    pub fn to_matrix(&self) -> Vec<Vec<T>> {
        let mut data = Vec::<Vec<T>>::with_capacity(self.height());
        for j in 0..self.height() {
            let mut row = Vec::<T>::with_capacity(self.width());
            for i in 0..self.width() {
                row.push(self[(i, j)].clone());
            }
            data.push(row);
        }
        data
    }

    /// Returns the width of the grid.
    ///
    /// # Examples
    ///
    /// ```
    /// use grud::Grid;
    ///
    /// let grid = Grid::new(2, 3, 0);
    /// assert_eq!(grid.width(), 2);
    /// ```
    pub fn width(&self) -> usize {
        self.width
    }

    /// Returns the height of the grid.
    ///
    /// # Examples
    ///
    /// ```
    /// use grud::Grid;
    ///
    /// let grid = Grid::new(2, 3, 0);
    /// assert_eq!(grid.height(), 3);
    /// ```
    pub fn height(&self) -> usize {
        self.data.len() / self.width()
    }

    /// Returns the total size of the grid as represented by `width * height`.
    ///
    /// # Examples
    ///
    /// ```
    /// use grud::Grid;
    ///
    /// let grid = Grid::new(2, 3, 0);
    /// assert_eq!(grid.area(), 2 * 3);
    /// ```
    pub fn area(&self) -> usize {
        self.width() * self.height()
    }
}

impl<T> Debug for Grid<T>
where
    T: Clone + Debug,
{
    /// Formats the grid into string output for debugging.
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Grid")
            .field("data", &self.data)
            .field("width", &self.width())
            .field("height", &self.height())
            .finish()
    }
}

impl<T> Display for Grid<T>
where
    T: Clone + Display,
{
    /// Formats the grid into a multi-line string output.
    ///
    /// If `T` is [`Display`] and is represented by a consistent sized grapheme cluster, the effect
    /// is similar to using a text-based user interface to output grahaeme clusters in a 2D grid:
    ///
    /// ```
    /// use grud::Grid;
    ///
    /// let grid = Grid::with_width(3, vec![1, 2, 3, 4, 5, 6, 7, 8, 9]);
    ///
    /// // 123
    /// // 456
    /// // 789
    /// assert_eq!(format!("{}", grid), "123\n456\n789\n");
    /// ```
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        for j in 0..self.height() {
            for i in 0..self.width() {
                write!(f, "{}", self[(i, j)])?;
            }
            writeln!(f)?;
        }
        Ok(())
    }
}

impl<'a, T> IntoIterator for &'a Grid<T>
where
    T: Clone,
{
    type Item = &'a T;
    type IntoIter = Iter<'a, T>;

    /// Returns an iterator that walks the grid in indexed order.
    ///
    /// ```
    /// use grud::Grid;
    ///
    /// let grid = Grid::with_width(2, vec!["a", "b", "c", "d"]);
    ///
    /// let items: Vec<String> = grid.into_iter().map(|i| i.to_ascii_uppercase()).collect();
    ///
    /// assert_eq!(items, vec!["A", "B", "C", "D"]);
    /// ```
    fn into_iter(self) -> Self::IntoIter {
        self.data.iter()
    }
}

impl<'a, T> IntoIterator for &'a mut Grid<T>
where
    T: Clone,
{
    type Item = &'a mut T;
    type IntoIter = IterMut<'a, T>;

    /// Returns an iterator that walks the grid in indexed order with mutable references.
    ///
    /// ```
    /// use grud::Grid;
    ///
    /// let mut grid = Grid::with_width(2, vec![1, 2, 3, 4]);
    ///
    /// for i in &mut grid {
    ///   *i += 1;
    /// }
    ///
    /// assert_eq!(grid.as_vec(), &vec![2, 3, 4, 5]);
    /// ```
    fn into_iter(self) -> Self::IntoIter {
        self.data.iter_mut()
    }
}

impl<T> From<Vec<Vec<T>>> for Grid<T>
where
    T: Clone,
{
    /// Converts a multi-dimensional vector of vectors into a [`Grid`].
    ///
    /// # Examples
    ///
    /// ```
    /// use grud::Grid;
    ///
    /// let grid = Grid::from(vec![
    ///   vec![1, 2],
    ///   vec![3, 4],
    ///   vec![5, 6],
    /// ]);
    ///
    /// assert_eq!(grid.width(), 2);
    /// assert_eq!(grid.height(), 3);
    /// ```
    ///
    /// # Panics
    ///
    /// If the length of every inner vector is not the same.
    fn from(data: Vec<Vec<T>>) -> Self {
        let height = data.len();
        if height == 0 {
            return Self {
                data: vec![],
                width: 0,
            };
        }
        let width = data[0].len();
        assert!(
            data.iter().all(|v| v.len() == width),
            "Length of every inner vector not the smae"
        );
        Self {
            data: data.iter().flat_map(|v| v.clone()).collect(),
            width,
        }
    }
}

impl<T> Index<usize> for Grid<T>
where
    T: Clone,
{
    type Output = T;

    /// Given an index into the implementation vector, returns the underlying data.
    ///
    /// This operator requires understanding the internal representation of data. For example,
    /// a 3x3 Grid (i.e. `Grid::new(3, 3, "•")`) has the indexed locations laid out as such:
    ///
    /// ```txt
    /// •0 •1 •2
    /// •3 •4 •5
    /// •6 •7 •8
    /// ```
    ///
    /// # Examples
    ///
    /// ```
    /// use grud::Grid;
    ///
    /// let grid = Grid::new(1, 1, "X");
    /// assert_eq!(grid[0], "X");
    /// ```
    ///
    /// # Panics
    ///
    /// If `index` is out of bounds.
    fn index(&self, index: usize) -> &Self::Output {
        &self.data[index]
    }
}

impl<T> IndexMut<usize> for Grid<T>
where
    T: Clone,
{
    /// Given an index into the implementation vector, sets the underlying data.
    ///
    /// This operator requires understanding the internal representation of data. For example,
    /// a 3x3 Grid (i.e. `Grid::new(3, 3, "•")`) has the indexed locations laid out as such:
    ///
    /// ```txt
    /// •0 •1 •2
    /// •3 •4 •5
    /// •6 •7 •8
    /// ```
    ///
    /// # Examples
    ///
    /// ```
    /// use grud::Grid;
    ///
    /// let mut grid = Grid::new(1, 1, "X");
    /// grid[0] = "Y";
    ///
    /// # assert_eq!(grid[0], "Y");
    /// ```
    ///
    /// # Panics
    ///
    /// If `index` is out of bounds.
    fn index_mut(&mut self, index: usize) -> &mut Self::Output {
        &mut self.data[index]
    }
}

impl<T, I> Index<I> for Grid<T>
where
    T: Clone,
    I: Point,
{
    type Output = T;

    /// Given a two-dimensional coordinate [`Point`], returns the underlying data.
    ///
    /// # Examples
    ///
    /// Using a tuple to represent `(width, height)`:
    ///
    /// ```
    /// use grud::Grid;
    ///
    /// let grid = Grid::new(1, 1, "X");
    /// assert_eq!(grid[(0, 0)], "X");
    /// ```
    ///
    /// Using an array to represent `[width, height]`:
    ///
    /// ```
    /// use grud::Grid;
    ///
    /// let grid = Grid::new(1, 1, "X");
    /// assert_eq!(grid[[0, 0]], "X");
    /// ```
    ///
    /// # Panics
    ///
    /// If `index` is out of bounds.
    fn index(&self, index: I) -> &Self::Output {
        let index = index.to_index(self.width());
        &self[index]
    }
}

impl<T, I> IndexMut<I> for Grid<T>
where
    T: Clone,
    I: Point,
{
    /// Given a two-dimensional coordinate [`Point`], sets the underlying data.
    ///
    /// # Examples
    ///
    /// Using a tuple to represent `(width, height)`:
    ///
    /// ```
    /// use grud::Grid;
    ///
    /// let mut grid = Grid::new(1, 1, "X");
    /// grid[(0, 0)] = "Y";
    ///
    /// # assert_eq!(grid[(0, 0)], "Y");
    /// ```
    ///
    /// Using a array to represent `[width, height]`:
    ///
    /// ```
    /// use grud::Grid;
    ///
    /// let mut grid = Grid::new(1, 1, "X");
    /// grid[(0, 0)] = "Y";
    ///
    /// # assert_eq!(grid[[0, 0]], "Y");
    /// ```
    ///
    /// # Panics
    ///
    /// If `index` is out of bounds.
    fn index_mut(&mut self, index: I) -> &mut Self::Output {
        let index = index.to_index(self.width());
        &mut self[index]
    }
}

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

    #[test]
    fn new_grid() {
        let grid = Grid::new(2, 3, " ");

        assert_eq!(grid.area(), 6);
        assert_eq!(grid.width(), 2);
        assert_eq!(grid.height(), 3);
        assert_eq!(grid.as_vec(), &vec![" ", " ", " ", " ", " ", " "]);
    }

    #[test]
    fn new_grid_of_width_to_matrix() {
        let grid = Grid::with_width(2, vec!["A", "B", "C", "D"]);

        assert_eq!(grid.to_matrix(), vec![vec!["A", "B"], vec!["C", "D"]])
    }

    #[test]
    #[should_panic]
    fn new_grid_of_width_not_divisible() {
        Grid::with_width(2, vec![1, 2, 3]);
    }

    #[test]
    fn grid_from_matrix() {
        let grid: Grid<_> = vec![vec!["A", "B"], vec!["C", "D"]].into();

        assert_eq!(grid.as_vec(), &vec!["A", "B", "C", "D"]);
    }

    #[test]
    #[should_panic]
    fn grid_from_matrix_not_consistent() {
        let _: Grid<_> = vec![vec!["A"], vec!["B", "C"]].into();
    }

    #[test]
    fn grid_clone() {
        let a: Grid<_> = vec![vec!["A", "B"], vec!["C", "D"]].into();
        let b = a.clone();

        assert_eq!(a.as_vec(), b.as_vec());
    }

    #[test]
    fn grid_debug() {
        let a: Grid<_> = vec![vec!["A", "B"], vec!["C", "D"]].into();
        let a = format!("{:?}", a);

        assert_eq!(
            a,
            "Grid { data: [\"A\", \"B\", \"C\", \"D\"], width: 2, height: 2 }"
        );
    }

    #[test]
    fn grid_display() {
        let a: Grid<_> = vec![vec!["A", "B"], vec!["C", "D"]].into();
        let a = format!("{}", a);

        assert_eq!(a, "AB\nCD\n");
    }

    #[test]
    fn grid_iter() {
        let a: Grid<_> = vec![vec!["A", "B"], vec!["C", "D"]].into();
        let v = a
            .into_iter()
            .map(|i| i.to_ascii_lowercase())
            .collect::<Vec<String>>();

        assert_eq!(v, vec!["a", "b", "c", "d"]);
    }

    #[test]
    fn grid_iter_mut() {
        let mut a: Grid<_> = vec![vec![1, 2], vec![3, 4]].into();
        for i in &mut a {
            *i += 1;
        }

        assert_eq!(a.as_vec(), &vec![2, 3, 4, 5]);
    }

    #[test]
    fn grid_index() {
        let grid: Grid<_> = vec![vec!["A", "B"], vec!["C", "D"]].into();

        assert_eq!(grid[0], "A");
        assert_eq!(grid[1], "B");
        assert_eq!(grid[2], "C");
        assert_eq!(grid[3], "D");
    }

    #[test]
    #[should_panic]
    fn grid_index_out_of_bounds() {
        let grid: Grid<()> = vec![].into();

        #[allow(clippy::no_effect)]
        grid[0];
    }

    #[test]
    fn grid_mut_index() {
        let mut grid: Grid<_> = vec![vec!["A", "B"], vec!["C", "D"]].into();

        grid[0] = "a";
        assert_eq!(grid[0], "a");
        assert_eq!(grid[1], "B");
        assert_eq!(grid[2], "C");
        assert_eq!(grid[3], "D");
    }

    #[test]
    #[should_panic]
    fn grid_mut_index_out_of_bounds() {
        let mut grid: Grid<()> = vec![].into();

        grid[0] = ();
    }

    #[test]
    fn grid_index_point() {
        let grid: Grid<_> = vec![vec!["A", "B"], vec!["C", "D"]].into();

        assert_eq!(grid[(0, 0)], "A");
        assert_eq!(grid[(1, 0)], "B");
        assert_eq!(grid[(0, 1)], "C");
        assert_eq!(grid[(1, 1)], "D");
    }

    #[test]
    fn grid_mut_index_point() {
        let mut grid: Grid<_> = vec![vec!["A", "B"], vec!["C", "D"]].into();

        grid[(0, 0)] = "a";
        grid[(1, 0)] = "b";
        grid[(0, 1)] = "c";
        grid[(1, 1)] = "d";

        assert_eq!(grid.as_vec(), &vec!["a", "b", "c", "d"]);
    }
}