aoc-core 0.1.2

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

use itertools::Itertools;
use lexical_core::FromLexical;

/// Enables number parsing using [`lexical-core`](https://docs.rs/lexical-core).
pub trait ParseNumber {
    /// Parses a single number.
    fn parse_number<N: FromLexical>(&self) -> N;

    /// Parses any amount of numbers, ignoring anything that's non a minus or digit.
    fn parse_numbers<N: FromLexical>(&self) -> impl Iterator<Item = N>;

    /// Parses any amount of numbers, ignoring anything that's non a minus or digit,
    /// and collecting them into a fixed size array.
    fn parse_numbers_array<N: FromLexical, const COUNT: usize>(&self) -> [N; COUNT];
}

impl ParseNumber for [u8] {
    #[inline]
    fn parse_number<N: FromLexical>(&self) -> N {
        lexical_core::parse(self)
            .unwrap_or_else(|_| panic!("Expected {}, got {self:?}", type_name::<N>()))
    }

    #[inline]
    fn parse_numbers<N: FromLexical>(&self) -> impl Iterator<Item = N> {
        self.split(|&b| !(b.is_ascii_digit() || b == b'-'))
            .filter(|s| !s.is_empty())
            .map(ParseNumber::parse_number)
    }

    #[inline]
    fn parse_numbers_array<N: FromLexical, const COUNT: usize>(&self) -> [N; COUNT] {
        self.parse_numbers()
            .collect_array()
            .unwrap_or_else(|| panic!("Expected exactly {COUNT} {}", type_name::<N>()))
    }
}

impl ParseNumber for str {
    #[inline]
    fn parse_number<N: FromLexical>(&self) -> N {
        self.as_bytes().parse_number()
    }

    #[inline]
    fn parse_numbers<N: FromLexical>(&self) -> impl Iterator<Item = N> {
        self.as_bytes().parse_numbers()
    }

    #[inline]
    fn parse_numbers_array<N: FromLexical, const COUNT: usize>(&self) -> [N; COUNT] {
        self.as_bytes().parse_numbers_array()
    }
}

/// Parses any amount of numbers separated by spaces,
/// specifically using the optimized [`split_ascii_whitespace`](str::split_ascii_whitespace).
#[inline]
pub fn parse_numbers_whitespace<N>(input: &str) -> impl Iterator<Item = N>
where
    N: FromLexical,
{
    input.split_ascii_whitespace().map(str::parse_number)
}

#[cfg(test)]
mod tests {
    use super::{ParseNumber, parse_numbers_whitespace};

    // ------------------------------------------------------------------------------------------------
    // ParseNumber [u8]

    #[test]
    fn u8_array_parse_number() {
        let input = b"-8";
        let expected = -8;
        let output: i8 = input.parse_number();
        assert_eq!(expected, output, "\n input: {input:?}");
    }

    #[test]
    #[should_panic = "Expected"]
    fn u8_array_parse_number_panic() {
        let _: i8 = b"u8".parse_number();
    }

    #[test]
    fn u8_array_parse_numbers() {
        let input = b"-8,7   4!!!-1hello0";
        let expected = vec![-8, 7, 4, -1, 0];
        let output: Vec<i8> = input.parse_numbers().collect();
        assert_eq!(expected, output, "\n input: {input:?}");
    }

    #[test]
    fn u8_array_parse_numbers_array() {
        let input = b"-8,7   4!!!-1hello0";
        let expected = [-8, 7, 4, -1, 0];
        let output: [i8; 5] = input.parse_numbers_array();
        assert_eq!(expected, output, "\n input: {input:?}");
    }

    #[test]
    #[should_panic = "Expected exactly"]
    fn u8_array_parse_numbers_array_panic() {
        let _: [i8; 5] = b"-8,7   4!!!-1hello0and13".parse_numbers_array();
    }

    // ------------------------------------------------------------------------------------------------
    // ParseNumber str

    #[test]
    fn str_parse_number() {
        let input = "-8";
        let expected = -8;
        let output: i8 = input.parse_number();
        assert_eq!(expected, output, "\n input: {input:?}");
    }

    #[test]
    #[should_panic = "Expected"]
    fn str_parse_number_panic() {
        let _: i8 = "u8".parse_number();
    }

    #[test]
    fn str_parse_numbers() {
        let input = "-8,7   4!!!-1hello0";
        let expected = vec![-8, 7, 4, -1, 0];
        let output: Vec<i8> = input.parse_numbers().collect();
        assert_eq!(expected, output, "\n input: {input:?}");
    }

    #[test]
    fn str_parse_numbers_array() {
        let input = "-8,7   4!!!-1hello0";
        let expected = [-8, 7, 4, -1, 0];
        let output: [i8; 5] = input.parse_numbers_array();
        assert_eq!(expected, output, "\n input: {input:?}");
    }

    #[test]
    #[should_panic = "Expected exactly"]
    fn str_array_parse_numbers_array_panic() {
        let _: [i8; 5] = "-8,7   4!!!-1hello0and13".parse_numbers_array();
    }

    // ------------------------------------------------------------------------------------------------
    // Functions

    #[test]
    fn str_parse_numbers_whitespace() {
        let input = "-8 7   4   -1     0";
        let expected = vec![-8, 7, 4, -1, 0];
        let output: Vec<i8> = parse_numbers_whitespace(input).collect();
        assert_eq!(expected, output, "\n input: {input:?}");
    }
}