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