aoc-core 0.1.2

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

/// Cardinal directions enum.
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub enum Dir {
    /// South.
    S,

    /// East
    E,

    /// North
    N,

    /// West
    W,
}

/// Internal macro for Dir functions match implementation.
macro_rules! match_enum_transform {
    ($match:expr, $enum:ident, [$($v1:ident : $v2:ident),+]) => {
        match $match {
            $($enum::$v1 => $enum::$v2,)+
        }
    };
}

impl Dir {
    /// Returns the opposite direction.
    #[inline]
    #[must_use]
    pub const fn opposite(self) -> Self {
        match_enum_transform!(self, Dir, [S:N, E:W, N:S, W:E])
    }

    /// Changes the direction to its opposite.
    #[inline]
    pub const fn opposite_mut(&mut self) {
        *self = self.opposite();
    }

    /// Returns the next direction in counter-clockwise rotation.
    #[inline]
    #[must_use]
    pub const fn rotate_ccw(self) -> Self {
        match_enum_transform!(self, Dir, [S:E, E:N, N:W, W:S])
    }

    /// Changes the direction to the next one in counter-clockwise rotation.
    #[inline]
    pub const fn rotate_ccw_mut(&mut self) {
        *self = self.rotate_ccw();
    }

    /// Returns the next direction in clockwise rotation.
    #[inline]
    #[must_use]
    pub const fn rotate_cw(self) -> Self {
        match_enum_transform!(self, Dir, [S:W, E:S, N:E, W:N])
    }

    /// Changes the direction to the next one in clockwise rotation.
    #[inline]
    pub const fn rotate_cw_mut(&mut self) {
        *self = self.rotate_cw();
    }
}

// ------------------------------------------------------------------------------------------------
// Trait implementations

impl From<usize> for Dir {
    #[inline]
    fn from(value: usize) -> Self {
        match value {
            0 => Self::S,
            1 => Self::E,
            2 => Self::N,
            3 => Self::W,
            _ => unreachable!("Invalid direction value"),
        }
    }
}

impl From<Dir> for usize {
    #[inline]
    fn from(value: Dir) -> Self {
        value as Self
    }
}

impl<T> Index<Dir> for [T] {
    type Output = T;

    #[inline]
    fn index(&self, index: Dir) -> &Self::Output {
        &self[index as usize]
    }
}

#[cfg(test)]
mod tests {
    use super::Dir::{self, *};

    macro_rules! assert_dir {
        ([$($in:ident),*], [$($ex:ident),*], $func:ident) => {
            let input = [$($in),*];
            let expected = [$($ex),*];
            let output = input.map(Dir::$func);
            assert_eq!(expected, output, "\n input: {input:?}");
        };
    }

    macro_rules! assert_dir_mut {
        ([$($in:ident),*], [$($ex:ident),*], $func:ident) => {
            let input = [$($in),*];
            let expected = [$($ex),*];
            let mut output = input;
            for dir in &mut output {
                dir.$func();
            }
            assert_eq!(expected, output, "\n input: {input:?}");
        };
    }

    // ------------------------------------------------------------------------------------------------
    // Opposite

    #[test]
    fn opposite() {
        assert_dir!([S, E, N, W], [N, W, S, E], opposite);
    }

    #[test]
    fn opposite_mut() {
        assert_dir_mut!([S, E, N, W], [N, W, S, E], opposite_mut);
    }

    // ------------------------------------------------------------------------------------------------
    // Rotation

    #[test]
    fn rotate_ccw() {
        assert_dir!([S, E, N, W], [E, N, W, S], rotate_ccw);
    }

    #[test]
    fn rotate_ccw_mut() {
        assert_dir_mut!([S, E, N, W], [E, N, W, S], rotate_ccw_mut);
    }

    #[test]
    fn rotate_cw() {
        assert_dir!([S, E, N, W], [W, S, E, N], rotate_cw);
    }

    #[test]
    fn rotate_cw_mut() {
        assert_dir_mut!([S, E, N, W], [W, S, E, N], rotate_cw_mut);
    }

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

    #[test]
    fn from_usize() {
        let input = [0, 1, 2, 3];
        let expected = [S, E, N, W];
        let output = input.map(Dir::from);
        assert_eq!(expected, output, "\n input: {input:?}");
    }

    #[test]
    #[should_panic = "Invalid direction"]
    fn from_usize_panic() {
        let _ = Dir::from(4);
    }

    #[test]
    fn into_usize() {
        let input = [S, E, N, W];
        let expected = [0, 1, 2, 3];
        let output = input.map(usize::from);
        assert_eq!(expected, output, "\n input: {input:?}");
    }

    // ------------------------------------------------------------------------------------------------
    // Index

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