aoc/year2023/
day03.rs

1//! # Gear Ratios
2use crate::util::grid::*;
3use crate::util::parse::*;
4use crate::util::point::*;
5
6pub struct Input {
7    grid: Grid<u8>,
8    seen: Grid<usize>,
9    parts: Vec<u32>,
10}
11
12pub fn parse(input: &str) -> Input {
13    let grid = Grid::parse(input);
14    // In order to tell if we've already seen a number before, store its index at the location
15    // of every digit, using zero to indicate no value. For example:
16    // `467..114..` => `1110022200`
17    let mut seen: Grid<usize> = grid.same_size_with(0);
18    // Stores each unique part number. The first value is a dummy placeholder.
19    let mut parts = vec![0];
20    let mut number = 0;
21
22    for y in 0..grid.height {
23        for x in 0..grid.width {
24            let p = Point::new(x, y);
25            let b = grid[p];
26
27            if b.is_ascii_digit() {
28                // Parse contiguous groups of digits.
29                seen[p] = parts.len();
30                number = 10 * number + (b.to_decimal() as u32);
31            } else if number > 0 {
32                // If not a digit then finish the current number.
33                parts.push(number);
34                number = 0;
35            }
36        }
37        // Actual corner case if the last number is in the bottom-right corner.
38        if number > 0 {
39            parts.push(number);
40            number = 0;
41        }
42    }
43
44    Input { grid, seen, parts }
45}
46
47/// Sum all part numbers adjacent to a least one symbol.
48pub fn part1(input: &Input) -> u32 {
49    let Input { grid, seen, parts } = input;
50    let mut parts = parts.clone();
51    let mut result = 0;
52
53    for y in 0..grid.height {
54        for x in 0..grid.width {
55            let p = Point::new(x, y);
56            let b = grid[p];
57
58            if !b.is_ascii_digit() && b != b'.' {
59                for next in DIAGONAL.iter().copied().map(|d| p + d) {
60                    let index = seen[next];
61                    if index != 0 {
62                        result += parts[index];
63                        // Only count each number once when its adjacent to multiple symbols.
64                        parts[index] = 0;
65                    }
66                }
67            }
68        }
69    }
70
71    result
72}
73
74/// Sum all gears adjacent to exactly two part numbers.
75pub fn part2(input: &Input) -> u32 {
76    let Input { grid, seen, parts } = input;
77    let mut result = 0;
78
79    for y in 0..grid.height {
80        for x in 0..grid.width {
81            let p = Point::new(x, y);
82
83            if grid[p] == b'*' {
84                let mut previous = 0;
85                let mut distinct = 0;
86                let mut subtotal = 1;
87
88                // Rely on the left to right and top to bottom order of DIAGONAL
89                // to detect distinct numbers.
90                for next in DIAGONAL.iter().copied().map(|d| p + d) {
91                    let index = seen[next];
92                    if index != 0 && index != previous {
93                        previous = index;
94                        distinct += 1;
95                        subtotal *= parts[index];
96                    }
97                }
98
99                if distinct == 2 {
100                    result += subtotal;
101                }
102            }
103        }
104    }
105
106    result
107}