Skip to main content

aoc/util/
parse.rs

1//! Extracts and parses signed and unsigned integers from surrounding text and whitespace.
2//!
3//! A common pattern in Advent of Code is to parse and return `123`, `456` and `789` from input
4//! resembling the following form:
5//!
6//! ```none
7//! Lorem ipsum 123 dolor 456 sit 789 amet
8//! ```
9//!
10//! This module provides two [`&str`] extension methods [`iter_signed`] and [`iter_unsigned`]. The
11//! reason for the separate methods is that some Advent of Code inputs contain the `-` character
12//! as a delimiter and this would cause numbers to be incorrectly parsed as negative.
13//!
14//! [`iter_unsigned`]: ParseOps::iter_unsigned
15//! [`iter_signed`]: ParseOps::iter_signed
16use std::marker::PhantomData;
17use std::str::Bytes;
18
19use crate::util::integer::*;
20
21const MINUS: u8 = b'-'.wrapping_sub(b'0');
22
23pub trait ParseByte {
24    fn to_decimal<T: Integer<T>>(self) -> T;
25}
26
27impl ParseByte for u8 {
28    #[inline]
29    fn to_decimal<T: Integer<T>>(self) -> T {
30        T::from(self.wrapping_sub(b'0'))
31    }
32}
33
34pub struct ParseUnsigned<'a, T> {
35    bytes: Bytes<'a>,
36    phantom: PhantomData<T>,
37}
38
39pub struct ParseSigned<'a, T> {
40    bytes: Bytes<'a>,
41    phantom: PhantomData<T>,
42}
43
44pub trait ParseOps {
45    fn unsigned<T: Unsigned<T>>(&self) -> T;
46    fn signed<T: Signed<T>>(&self) -> T;
47    fn iter_unsigned<T: Unsigned<T>>(&self) -> ParseUnsigned<'_, T>;
48    fn iter_signed<T: Signed<T>>(&self) -> ParseSigned<'_, T>;
49}
50
51impl<S: AsRef<str> + ?Sized> ParseOps for S {
52    fn unsigned<T: Unsigned<T>>(&self) -> T {
53        let str = self.as_ref();
54        try_unsigned(&mut str.bytes()).unwrap_or_else(|| panic!("Unable to parse \"{str}\""))
55    }
56
57    fn signed<T: Signed<T>>(&self) -> T {
58        let str = self.as_ref();
59        try_signed(&mut str.bytes()).unwrap_or_else(|| panic!("Unable to parse \"{str}\""))
60    }
61
62    fn iter_unsigned<T: Unsigned<T>>(&self) -> ParseUnsigned<'_, T> {
63        ParseUnsigned { bytes: self.as_ref().bytes(), phantom: PhantomData }
64    }
65
66    fn iter_signed<T: Signed<T>>(&self) -> ParseSigned<'_, T> {
67        ParseSigned { bytes: self.as_ref().bytes(), phantom: PhantomData }
68    }
69}
70
71impl<T: Unsigned<T>> Iterator for ParseUnsigned<'_, T> {
72    type Item = T;
73
74    #[inline]
75    fn size_hint(&self) -> (usize, Option<usize>) {
76        let (lower, upper) = self.bytes.size_hint();
77        (lower / 3, upper.map(|u| u / 3))
78    }
79
80    #[inline]
81    fn next(&mut self) -> Option<Self::Item> {
82        try_unsigned(&mut self.bytes)
83    }
84}
85
86impl<T: Signed<T>> Iterator for ParseSigned<'_, T> {
87    type Item = T;
88
89    #[inline]
90    fn size_hint(&self) -> (usize, Option<usize>) {
91        let (lower, upper) = self.bytes.size_hint();
92        (lower / 3, upper.map(|u| u / 3))
93    }
94
95    #[inline]
96    fn next(&mut self) -> Option<Self::Item> {
97        try_signed(&mut self.bytes)
98    }
99}
100
101fn try_unsigned<T: Unsigned<T>>(bytes: &mut Bytes<'_>) -> Option<T> {
102    let mut n = loop {
103        let digit = bytes.next()?.to_decimal();
104        if digit < 10 {
105            break T::from(digit);
106        }
107    };
108
109    for byte in bytes {
110        let digit = byte.to_decimal();
111        if digit >= 10 {
112            break;
113        }
114        n = T::TEN * n + T::from(digit);
115    }
116
117    Some(n)
118}
119
120fn try_signed<T: Signed<T>>(bytes: &mut Bytes<'_>) -> Option<T> {
121    let (mut n, negative) = loop {
122        let digit = bytes.next()?.to_decimal();
123        if digit == MINUS {
124            break (T::ZERO, true);
125        }
126        if digit < 10 {
127            break (T::from(digit), false);
128        }
129    };
130
131    for byte in bytes {
132        let digit = byte.to_decimal();
133        if digit >= 10 {
134            break;
135        }
136        n = T::TEN * n + T::from(digit);
137    }
138
139    Some(if negative { -n } else { n })
140}