Skip to main content

aoc_core/
parsing.rs

1use std::any::type_name;
2
3use itertools::Itertools;
4use lexical_core::FromLexical;
5
6/// Enables number parsing using [`lexical-core`](https://docs.rs/lexical-core).
7pub trait ParseNumber {
8    /// Parses a single number.
9    fn parse_number<N: FromLexical>(&self) -> N;
10
11    /// Parses any amount of numbers, ignoring anything that's non a minus or digit.
12    fn parse_numbers<N: FromLexical>(&self) -> impl Iterator<Item = N>;
13
14    /// Parses any amount of numbers, ignoring anything that's non a minus or digit,
15    /// and collecting them into a fixed size array.
16    fn parse_numbers_array<N: FromLexical, const COUNT: usize>(&self) -> [N; COUNT];
17}
18
19impl ParseNumber for [u8] {
20    #[inline]
21    fn parse_number<N: FromLexical>(&self) -> N {
22        lexical_core::parse(self)
23            .unwrap_or_else(|_| panic!("Expected {}, got {self:?}", type_name::<N>()))
24    }
25
26    #[inline]
27    fn parse_numbers<N: FromLexical>(&self) -> impl Iterator<Item = N> {
28        self.split(|&b| !(b.is_ascii_digit() || b == b'-'))
29            .filter(|s| !s.is_empty())
30            .map(ParseNumber::parse_number)
31    }
32
33    #[inline]
34    fn parse_numbers_array<N: FromLexical, const COUNT: usize>(&self) -> [N; COUNT] {
35        self.parse_numbers()
36            .collect_array()
37            .unwrap_or_else(|| panic!("Expected exactly {COUNT} {}", type_name::<N>()))
38    }
39}
40
41impl ParseNumber for str {
42    #[inline]
43    fn parse_number<N: FromLexical>(&self) -> N {
44        self.as_bytes().parse_number()
45    }
46
47    #[inline]
48    fn parse_numbers<N: FromLexical>(&self) -> impl Iterator<Item = N> {
49        self.as_bytes().parse_numbers()
50    }
51
52    #[inline]
53    fn parse_numbers_array<N: FromLexical, const COUNT: usize>(&self) -> [N; COUNT] {
54        self.as_bytes().parse_numbers_array()
55    }
56}
57
58/// Parses any amount of numbers separated by spaces,
59/// specifically using the optimized [`split_ascii_whitespace`](str::split_ascii_whitespace).
60#[inline]
61pub fn parse_numbers_whitespace<N>(input: &str) -> impl Iterator<Item = N>
62where
63    N: FromLexical,
64{
65    input.split_ascii_whitespace().map(str::parse_number)
66}
67
68#[cfg(test)]
69mod tests {
70    use super::{ParseNumber, parse_numbers_whitespace};
71
72    // ------------------------------------------------------------------------------------------------
73    // ParseNumber [u8]
74
75    #[test]
76    fn u8_array_parse_number() {
77        let input = b"-8";
78        let expected = -8;
79        let output: i8 = input.parse_number();
80        assert_eq!(expected, output, "\n input: {input:?}");
81    }
82
83    #[test]
84    #[should_panic = "Expected"]
85    fn u8_array_parse_number_panic() {
86        let _: i8 = b"u8".parse_number();
87    }
88
89    #[test]
90    fn u8_array_parse_numbers() {
91        let input = b"-8,7   4!!!-1hello0";
92        let expected = vec![-8, 7, 4, -1, 0];
93        let output: Vec<i8> = input.parse_numbers().collect();
94        assert_eq!(expected, output, "\n input: {input:?}");
95    }
96
97    #[test]
98    fn u8_array_parse_numbers_array() {
99        let input = b"-8,7   4!!!-1hello0";
100        let expected = [-8, 7, 4, -1, 0];
101        let output: [i8; 5] = input.parse_numbers_array();
102        assert_eq!(expected, output, "\n input: {input:?}");
103    }
104
105    #[test]
106    #[should_panic = "Expected exactly"]
107    fn u8_array_parse_numbers_array_panic() {
108        let _: [i8; 5] = b"-8,7   4!!!-1hello0and13".parse_numbers_array();
109    }
110
111    // ------------------------------------------------------------------------------------------------
112    // ParseNumber str
113
114    #[test]
115    fn str_parse_number() {
116        let input = "-8";
117        let expected = -8;
118        let output: i8 = input.parse_number();
119        assert_eq!(expected, output, "\n input: {input:?}");
120    }
121
122    #[test]
123    #[should_panic = "Expected"]
124    fn str_parse_number_panic() {
125        let _: i8 = "u8".parse_number();
126    }
127
128    #[test]
129    fn str_parse_numbers() {
130        let input = "-8,7   4!!!-1hello0";
131        let expected = vec![-8, 7, 4, -1, 0];
132        let output: Vec<i8> = input.parse_numbers().collect();
133        assert_eq!(expected, output, "\n input: {input:?}");
134    }
135
136    #[test]
137    fn str_parse_numbers_array() {
138        let input = "-8,7   4!!!-1hello0";
139        let expected = [-8, 7, 4, -1, 0];
140        let output: [i8; 5] = input.parse_numbers_array();
141        assert_eq!(expected, output, "\n input: {input:?}");
142    }
143
144    #[test]
145    #[should_panic = "Expected exactly"]
146    fn str_array_parse_numbers_array_panic() {
147        let _: [i8; 5] = "-8,7   4!!!-1hello0and13".parse_numbers_array();
148    }
149
150    // ------------------------------------------------------------------------------------------------
151    // Functions
152
153    #[test]
154    fn str_parse_numbers_whitespace() {
155        let input = "-8 7   4   -1     0";
156        let expected = vec![-8, 7, 4, -1, 0];
157        let output: Vec<i8> = parse_numbers_whitespace(input).collect();
158        assert_eq!(expected, output, "\n input: {input:?}");
159    }
160}