year2025/
day06.rs

1use utils::prelude::*;
2
3/// Parsing numbers from columns of digits.
4#[derive(Clone, Debug)]
5pub struct Day06 {
6    part1: u64,
7    part2: u64,
8}
9
10const MAX_NUMBERS: usize = 4;
11const MAX_NUMBER_DIGITS: usize = 4;
12
13impl Day06 {
14    pub fn new(input: &str, _: InputType) -> Result<Self, InputError> {
15        let mut lines = input.lines().map(str::as_bytes).collect::<Vec<_>>();
16        if lines.len() > MAX_NUMBERS + 1 {
17            return Err(InputError::new(
18                input,
19                0,
20                "expected at most four lines of numbers followed by one line of operators",
21            ));
22        }
23        if lines.len() < 3 {
24            return Err(InputError::new(
25                input,
26                0,
27                "expected at least two lines of numbers followed by one line of operators",
28            ));
29        }
30
31        let operators = lines.pop().unwrap();
32        if let Some(&l) = lines.iter().find(|l| l.len() != operators.len()) {
33            return Err(InputError::new(
34                input,
35                l,
36                "expected all number lines to match operator line length",
37            ));
38        }
39
40        let mut index = 0;
41        let (mut part1, mut part2) = (0, 0);
42        while index < operators.len() {
43            let operator: fn(&[u64]) -> u64 = match operators[index] {
44                b'+' => |x| x.iter().sum(),
45                b'*' => |x| x.iter().product(),
46                _ => return Err(InputError::new(input, operators, "expected '+' or '*'")),
47            };
48
49            let mut column_width = 1;
50            while index + column_width < operators.len() && operators[index + column_width] == b' '
51            {
52                column_width += 1;
53            }
54            let number_width = if index + column_width == operators.len() {
55                // The final column has no space between it and the next column
56                column_width
57            } else {
58                column_width - 1
59            };
60            if number_width > MAX_NUMBER_DIGITS {
61                return Err(InputError::new(
62                    input,
63                    operators,
64                    "too many digits in column",
65                ));
66            }
67
68            let mut normal_numbers = [0; MAX_NUMBERS];
69            let mut cephalopod_numbers = [0; MAX_NUMBER_DIGITS];
70            for (i, &l) in lines.iter().enumerate() {
71                let mut start = index;
72                while start < l.len() && l[start] == b' ' {
73                    start += 1;
74                }
75                let mut end = index + number_width - 1;
76                while end > start && l[end] == b' ' {
77                    end -= 1;
78                }
79
80                let mut normal_number = 0;
81                for i in start..=end {
82                    if !matches!(l[i], b'1'..=b'9') {
83                        return Err(InputError::new(input, &l[i..], "expected '1'-'9' or ' '"));
84                    }
85                    normal_number = (normal_number * 10) + (l[i] - b'0') as u64;
86                }
87                normal_numbers[i] = normal_number;
88
89                for (j, &b) in l[index..index + number_width].iter().enumerate() {
90                    if b != b' ' {
91                        // Normal number loop has already checked that any non-space bytes are '1'-'9'
92                        cephalopod_numbers[j] = (cephalopod_numbers[j] * 10) + (b - b'0') as u64;
93                    }
94                }
95
96                if index + number_width < l.len() && l[index + number_width] != b' ' {
97                    return Err(InputError::new(
98                        input,
99                        &l[index + number_width..],
100                        "expected ' ' between columns",
101                    ));
102                }
103            }
104
105            part1 += operator(&normal_numbers[..lines.len()]);
106            part2 += operator(&cephalopod_numbers[..number_width]);
107            index += column_width;
108        }
109
110        Ok(Self { part1, part2 })
111    }
112
113    #[must_use]
114    pub fn part1(&self) -> u64 {
115        self.part1
116    }
117
118    #[must_use]
119    pub fn part2(&self) -> u64 {
120        self.part2
121    }
122}
123
124examples!(Day06 -> (u64, u64) [
125    {
126        input: "123 328  51 64 \n 45 64  387 23 \n  6 98  215 314\n*   +   *   +  ",
127        part1: 4277556,
128        part2: 3263827,
129    },
130]);