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:
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 crate::util::integer::*;
17use std::marker::PhantomData;
18use std::str::Bytes;
19
20const MINUS: u8 = b'-'.wrapping_sub(b'0');
21
22pub trait ParseByte {
23    fn to_decimal(self) -> u8;
24}
25
26impl ParseByte for u8 {
27    #[inline]
28    fn to_decimal(self) -> u8 {
29        self.wrapping_sub(b'0')
30    }
31}
32
33pub struct ParseUnsigned<'a, T> {
34    bytes: Bytes<'a>,
35    phantom: PhantomData<T>,
36}
37
38pub struct ParseSigned<'a, T> {
39    bytes: Bytes<'a>,
40    phantom: PhantomData<T>,
41}
42
43pub trait ParseOps {
44    fn unsigned<T: Unsigned<T>>(&self) -> T;
45    fn signed<T: Signed<T>>(&self) -> T;
46    fn iter_unsigned<T: Unsigned<T>>(&self) -> ParseUnsigned<'_, T>;
47    fn iter_signed<T: Signed<T>>(&self) -> ParseSigned<'_, T>;
48}
49
50impl<S: AsRef<str>> ParseOps for S {
51    fn unsigned<T: Unsigned<T>>(&self) -> T {
52        let str = self.as_ref();
53        try_unsigned(&mut str.bytes()).unwrap_or_else(|| panic!("Unable to parse \"{str}\""))
54    }
55
56    fn signed<T: Signed<T>>(&self) -> T {
57        let str = self.as_ref();
58        try_signed(&mut str.bytes()).unwrap_or_else(|| panic!("Unable to parse \"{str}\""))
59    }
60
61    fn iter_unsigned<T: Unsigned<T>>(&self) -> ParseUnsigned<'_, T> {
62        ParseUnsigned { bytes: self.as_ref().bytes(), phantom: PhantomData }
63    }
64
65    fn iter_signed<T: Signed<T>>(&self) -> ParseSigned<'_, T> {
66        ParseSigned { bytes: self.as_ref().bytes(), phantom: PhantomData }
67    }
68}
69
70impl<T: Unsigned<T>> Iterator for ParseUnsigned<'_, T> {
71    type Item = T;
72
73    #[inline]
74    fn size_hint(&self) -> (usize, Option<usize>) {
75        let (lower, upper) = self.bytes.size_hint();
76        (lower / 3, upper.map(|u| u / 3))
77    }
78
79    #[inline]
80    fn next(&mut self) -> Option<Self::Item> {
81        try_unsigned(&mut self.bytes)
82    }
83}
84
85impl<T: Signed<T>> Iterator for ParseSigned<'_, T> {
86    type Item = T;
87
88    #[inline]
89    fn size_hint(&self) -> (usize, Option<usize>) {
90        let (lower, upper) = self.bytes.size_hint();
91        (lower / 3, upper.map(|u| u / 3))
92    }
93
94    #[inline]
95    fn next(&mut self) -> Option<Self::Item> {
96        try_signed(&mut self.bytes)
97    }
98}
99
100fn try_unsigned<T: Unsigned<T>>(bytes: &mut Bytes<'_>) -> Option<T> {
101    let mut n = loop {
102        let digit = bytes.next()?.to_decimal();
103        if digit < 10 {
104            break T::from(digit);
105        }
106    };
107
108    for byte in bytes {
109        let digit = byte.to_decimal();
110        if digit >= 10 {
111            break;
112        }
113        n = T::TEN * n + T::from(digit);
114    }
115
116    Some(n)
117}
118
119fn try_signed<T: Signed<T>>(bytes: &mut Bytes<'_>) -> Option<T> {
120    let (mut n, negative) = loop {
121        let digit = bytes.next()?.to_decimal();
122        if digit == MINUS {
123            break (T::ZERO, true);
124        }
125        if digit < 10 {
126            break (T::from(digit), false);
127        }
128    };
129
130    for byte in bytes {
131        let digit = byte.to_decimal();
132        if digit >= 10 {
133            break;
134        }
135        n = T::TEN * n + T::from(digit);
136    }
137
138    Some(if negative { -n } else { n })
139}