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::iter::from_fn;
17
18use crate::util::integer::*;
19
20const MINUS: u8 = b'-'.wrapping_sub(b'0');
21
22pub trait ParseByte {
23    fn to_decimal<T: Integer>(self) -> T;
24}
25
26impl ParseByte for u8 {
27    #[inline]
28    fn to_decimal<T: Integer>(self) -> T {
29        T::from(self.wrapping_sub(b'0'))
30    }
31}
32
33pub trait ParseOps {
34    fn unsigned<T: Unsigned>(&self) -> T;
35    fn signed<T: Signed>(&self) -> T;
36    fn iter_unsigned<T: Unsigned>(&self) -> impl Iterator<Item = T>;
37    fn iter_signed<T: Signed>(&self) -> impl Iterator<Item = T>;
38}
39
40impl<S: AsRef<str> + ?Sized> ParseOps for S {
41    #[inline]
42    fn unsigned<T: Unsigned>(&self) -> T {
43        let mut bytes = self.as_ref().bytes();
44        try_unsigned(&mut bytes).unwrap()
45    }
46
47    #[inline]
48    fn signed<T: Signed>(&self) -> T {
49        let mut bytes = self.as_ref().bytes();
50        try_signed(&mut bytes).unwrap()
51    }
52
53    #[inline]
54    fn iter_unsigned<T: Unsigned>(&self) -> impl Iterator<Item = T> {
55        let mut bytes = self.as_ref().bytes();
56        from_fn(move || try_unsigned(&mut bytes))
57    }
58
59    #[inline]
60    fn iter_signed<T: Signed>(&self) -> impl Iterator<Item = T> {
61        let mut bytes = self.as_ref().bytes();
62        from_fn(move || try_signed(&mut bytes))
63    }
64}
65
66fn try_unsigned<T: Unsigned>(bytes: &mut impl Iterator<Item = u8>) -> Option<T> {
67    let mut n = loop {
68        let digit = bytes.next()?.to_decimal();
69        if digit < 10 {
70            break T::from(digit);
71        }
72    };
73
74    for byte in bytes {
75        let digit = byte.to_decimal();
76        if digit >= 10 {
77            break;
78        }
79        n = T::TEN * n + T::from(digit);
80    }
81
82    Some(n)
83}
84
85fn try_signed<T: Signed>(bytes: &mut impl Iterator<Item = u8>) -> Option<T> {
86    let (mut n, negative) = loop {
87        let digit = bytes.next()?.to_decimal();
88        if digit == MINUS {
89            break (T::ZERO, true);
90        }
91        if digit < 10 {
92            break (T::from(digit), false);
93        }
94    };
95
96    for byte in bytes {
97        let digit = byte.to_decimal();
98        if digit >= 10 {
99            break;
100        }
101        n = T::TEN * n + T::from(digit);
102    }
103
104    Some(if negative { -n } else { n })
105}