aoc-core 0.1.2

Useful Advent of Code data structures, types and functions common to my Rust solutions.
Documentation
use derive_more::{Add, AddAssign, Display, Sub, SubAssign};

use crate::dir::Dir;

/// 2-dimensional coordinates struct.
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Add, AddAssign, Sub, SubAssign, Display)]
#[display("{x},{y}")]
pub struct Pos {
    /// X coordinate.
    pub x: isize,

    /// Y coordinate.
    pub y: isize,
}

impl Pos {
    /// Creates a new pos with `x` and `y` coordinates.
    #[inline]
    #[must_use]
    pub const fn new(x: isize, y: isize) -> Self {
        Self { x, y }
    }

    /// Returns a new position after movign towards `dir`.
    #[inline]
    #[must_use]
    pub const fn move_dir(&self, dir: Dir) -> Self {
        match dir {
            Dir::S => Self::new(self.x + 1, self.y),
            Dir::E => Self::new(self.x, self.y + 1),
            Dir::N => Self::new(self.x - 1, self.y),
            Dir::W => Self::new(self.x, self.y - 1),
        }
    }

    /// Changes the position by moving towards `dir`.
    #[inline]
    pub const fn move_dir_mut(&mut self, dir: Dir) {
        match dir {
            Dir::S => self.x += 1,
            Dir::E => self.y += 1,
            Dir::N => self.x -= 1,
            Dir::W => self.y -= 1,
        }
    }

    /// Checks if the position is within a rectangular region's `bounds`
    /// (two pos representing two diagonally opposite corners).
    #[inline]
    #[must_use]
    pub const fn in_bounds(&self, bounds: (Self, Self)) -> bool {
        bounds.0.x <= self.x && self.x <= bounds.1.x && bounds.0.y <= self.y && self.y <= bounds.1.y
    }

    /// Calculates the Manhattan distance to `other`.
    #[inline]
    #[must_use]
    pub const fn manhattan_distance(&self, other: Self) -> usize {
        self.x.abs_diff(other.x) + self.y.abs_diff(other.y)
    }

    /// Returns an iterator of adjacent positions.
    #[inline]
    pub fn adjacent(&self) -> impl Iterator<Item = Self> + use<> {
        [
            Self::new(self.x + 1, self.y),
            Self::new(self.x, self.y + 1),
            Self::new(self.x - 1, self.y),
            Self::new(self.x, self.y - 1),
        ]
        .into_iter()
    }

    /// Returns an iterator of positions in the corners.
    #[inline]
    pub fn corners(&self) -> impl Iterator<Item = Self> + use<> {
        [
            Self::new(self.x + 1, self.y + 1),
            Self::new(self.x - 1, self.y + 1),
            Self::new(self.x - 1, self.y - 1),
            Self::new(self.x + 1, self.y - 1),
        ]
        .into_iter()
    }

    /// Returns an iterator of both adjacent and corner positions.
    #[inline]
    pub fn neighbors(&self) -> impl Iterator<Item = Self> + use<> {
        self.adjacent().chain(self.corners())
    }
}

impl From<(isize, isize)> for Pos {
    #[inline]
    fn from(value: (isize, isize)) -> Self {
        Self::new(value.0, value.1)
    }
}

impl From<(usize, usize)> for Pos {
    #[inline]
    fn from(value: (usize, usize)) -> Self {
        #[allow(clippy::cast_possible_wrap)]
        Self::new(value.0 as isize, value.1 as isize)
    }
}

impl From<Pos> for (usize, usize) {
    #[inline]
    fn from(val: Pos) -> Self {
        #[allow(clippy::cast_sign_loss)]
        (val.x as usize, val.y as usize)
    }
}

/// Allows indexing with a [`Pos`] instance.
pub trait PosGet<V> {
    /// Returns a reference to the value at `pos`,
    /// or `None` if the position cannot index the type.
    fn pos_get(&self, pos: Pos) -> Option<&V>;

    /// Returns a mutable reference to the value at `pos`,
    /// or `None` if the position cannot index the type.
    fn pos_get_mut(&mut self, pos: Pos) -> Option<&mut V>;
}

#[cfg(test)]
mod tests {
    use rustc_hash::FxHashSet;

    use super::Pos;
    use crate::dir::Dir::*;

    // ------------------------------------------------------------------------------------------------
    // Pos

    #[test]
    fn new() {
        let input: (isize, isize) = (1, 2);
        let expected = (1, 2);
        let pos = Pos::new(input.0, input.1);
        let output = (pos.x, pos.y);
        assert_eq!(expected, output, "\n input: {input:?}");
    }

    #[test]
    fn move_dir() {
        let input = [S, E, N, W];
        let expected = [
            Pos::new(2, 2),
            Pos::new(1, 3),
            Pos::new(0, 2),
            Pos::new(1, 1),
        ];
        let pos = Pos::new(1, 2);
        let output = input.map(|d| pos.move_dir(d));
        assert_eq!(expected, output, "\n input: {input:?}");
    }

    #[test]
    fn move_dir_mut() {
        let input = [
            (Pos::new(0, 1), S),
            (Pos::new(1, 1), E),
            (Pos::new(1, 2), N),
            (Pos::new(2, 2), W),
        ];
        let expected = [
            (Pos::new(1, 1), S),
            (Pos::new(1, 2), E),
            (Pos::new(0, 2), N),
            (Pos::new(2, 1), W),
        ];
        let mut output = input;
        for (pos, dir) in &mut output {
            pos.move_dir_mut(*dir);
        }
        assert_eq!(expected, output, "\n input: {input:?}");
    }

    #[test]
    fn in_bounds() {
        let input = (
            (Pos::new(1, 2), Pos::new(4, 7)),
            [
                Pos::new(2, 5),
                Pos::new(1, 5),
                Pos::new(4, 5),
                Pos::new(2, 2),
                Pos::new(2, 7),
                Pos::new(0, 5),
                Pos::new(5, 5),
                Pos::new(2, 1),
                Pos::new(2, 8),
            ],
        );
        let expected = [true, true, true, true, true, false, false, false, false];
        let output = input.1.map(|p| p.in_bounds(input.0));
        assert_eq!(expected, output, "\n input: {input:?}");
    }

    #[test]
    fn manhattan_distance() {
        let input = [
            (Pos::new(1, 2), Pos::new(3, 4)),
            (Pos::new(-4, 3), Pos::new(2, -1)),
            (Pos::new(0, 0), Pos::new(-1, 1)),
            (Pos::new(4, -3), Pos::new(-2, 2)),
        ];
        let expected = [4, 10, 2, 11];
        let output = input.map(|(p, q)| p.manhattan_distance(q));
        assert_eq!(expected, output, "\n input: {input:?}");
    }

    #[test]
    fn adjacent() {
        let input = [
            Pos::new(0, 0),
            Pos::new(4, 5),
            Pos::new(-1, 3),
            Pos::new(0, -2),
        ];
        let expected = [
            FxHashSet::from_iter([
                Pos::new(1, 0),
                Pos::new(0, 1),
                Pos::new(-1, 0),
                Pos::new(0, -1),
            ]),
            FxHashSet::from_iter([
                Pos::new(5, 5),
                Pos::new(4, 6),
                Pos::new(3, 5),
                Pos::new(4, 4),
            ]),
            FxHashSet::from_iter([
                Pos::new(0, 3),
                Pos::new(-1, 4),
                Pos::new(-2, 3),
                Pos::new(-1, 2),
            ]),
            FxHashSet::from_iter([
                Pos::new(1, -2),
                Pos::new(0, -1),
                Pos::new(-1, -2),
                Pos::new(0, -3),
            ]),
        ];
        let output = input.map(|p| p.adjacent().collect());
        assert_eq!(expected, output, "\n input: {input:?}");
    }

    #[test]
    fn corners() {
        let input = [
            Pos::new(0, 0),
            Pos::new(4, 5),
            Pos::new(-1, 3),
            Pos::new(0, -2),
        ];
        let expected = [
            FxHashSet::from_iter([
                Pos::new(1, 1),
                Pos::new(-1, 1),
                Pos::new(-1, -1),
                Pos::new(1, -1),
            ]),
            FxHashSet::from_iter([
                Pos::new(5, 6),
                Pos::new(3, 6),
                Pos::new(3, 4),
                Pos::new(5, 4),
            ]),
            FxHashSet::from_iter([
                Pos::new(0, 4),
                Pos::new(-2, 4),
                Pos::new(-2, 2),
                Pos::new(0, 2),
            ]),
            FxHashSet::from_iter([
                Pos::new(1, -1),
                Pos::new(-1, -1),
                Pos::new(-1, -3),
                Pos::new(1, -3),
            ]),
        ];
        let output = input.map(|p| p.corners().collect());
        assert_eq!(expected, output, "\n input: {input:?}");
    }

    #[test]
    fn neighbors() {
        let input = [Pos::new(4, 5), Pos::new(-1, 3)];
        let expected = [
            FxHashSet::from_iter([
                Pos::new(5, 5),
                Pos::new(4, 6),
                Pos::new(3, 5),
                Pos::new(4, 4),
                Pos::new(5, 6),
                Pos::new(3, 6),
                Pos::new(3, 4),
                Pos::new(5, 4),
            ]),
            FxHashSet::from_iter([
                Pos::new(0, 3),
                Pos::new(-1, 4),
                Pos::new(-2, 3),
                Pos::new(-1, 2),
                Pos::new(0, 4),
                Pos::new(-2, 4),
                Pos::new(-2, 2),
                Pos::new(0, 2),
            ]),
        ];
        let output = input.map(|p| p.neighbors().collect());
        assert_eq!(expected, output, "\n input: {input:?}");
    }

    // ------------------------------------------------------------------------------------------------
    // From

    #[test]
    fn pos_from_isize_tuple() {
        let input: (isize, isize) = (1, 2);
        let expected = Pos::new(1, 2);
        let output = Pos::from(input);
        assert_eq!(expected, output, "\n input: {input:?}");
    }

    #[test]
    fn pos_from_usize_tuple() {
        let input: (usize, usize) = (1, 2);
        let expected = Pos::new(1, 2);
        let output = Pos::from(input);
        assert_eq!(expected, output, "\n input: {input:?}");
    }

    #[test]
    fn usize_tuple_from_pos() {
        let input = Pos::new(1, 2);
        let expected = (1, 2);
        let output = From::from(input);
        assert_eq!(expected, output, "\n input: {input:?}");
    }
}