year2015/
day05.rs

1use utils::prelude::*;
2
3/// Matching string patterns.
4#[derive(Clone, Debug)]
5pub struct Day05<'a> {
6    lines: Vec<&'a [u8]>,
7}
8
9impl<'a> Day05<'a> {
10    pub fn new(input: &'a str, _: InputType) -> Result<Self, InputError> {
11        Ok(Self {
12            lines: parser::take_while1(u8::is_ascii_lowercase)
13                .error_msg("expected a-z")
14                .parse_lines(input)?,
15        })
16    }
17
18    #[must_use]
19    #[expect(clippy::eq_op)]
20    pub fn part1(&self) -> usize {
21        const VOWELS: u32 = (1 << (b'a' - b'a'))
22            | (1 << (b'e' - b'a'))
23            | (1 << (b'i' - b'a'))
24            | (1 << (b'o' - b'a'))
25            | (1 << (b'u' - b'a'));
26
27        self.lines
28            .iter()
29            // At least one letter that appears twice in a row
30            .filter(|&&l| l.windows(2).any(|w| w[0] == w[1]))
31            // At least 3 vowels
32            .filter(|&&l| {
33                l.iter()
34                    // Using a mask to match vowels instead of chained equals is ~2x faster
35                    .filter(|&&b| VOWELS & (1 << (b - b'a')) != 0)
36                    .count()
37                    >= 3
38            })
39            // Not any of these strings
40            .filter(|&&l| l.windows(2).all(|w| w != b"ab"))
41            .filter(|&&l| l.windows(2).all(|w| w != b"cd"))
42            .filter(|&&l| l.windows(2).all(|w| w != b"pq"))
43            .filter(|&&l| l.windows(2).all(|w| w != b"xy"))
44            .count()
45    }
46
47    #[must_use]
48    pub fn part2(&self) -> usize {
49        // Share an array to avoid clearing it for each string
50        let mut pair_positions = [0u32; 729];
51        let mut pos = 0;
52
53        self.lines
54            .iter()
55            // Contains a letter that repeats 2 characters later
56            .filter(|&&l| l.windows(3).any(|w| w[0] == w[2]))
57            // Contains a repeated pair of letters (without overlapping)
58            .filter(|&&l| {
59                let string_start = pos;
60                l.windows(2).any(|w| {
61                    let pair = 26 * (w[0] - b'a') as usize + (w[1] - b'a') as usize;
62                    if pair_positions[pair] > string_start {
63                        // Already seen the pair earlier in this string
64                        if pair_positions[pair] < pos {
65                            // Match found as pairs don't overlap
66                            return true;
67                        }
68                    } else {
69                        // First occurrence of the pair in this string
70                        pair_positions[pair] = pos + 1;
71                    }
72                    pos += 1;
73                    false
74                })
75            })
76            .count()
77    }
78}
79
80examples!(Day05<'_> -> (usize, usize) [
81    {input: "ugknbfddgicrmopn", part1: 1},
82    {input: "aaa", part1: 1, part2: 0},
83    {input: "aaaa", part1: 1, part2: 1},
84    {input: "jchzalrnumimnmhp", part1: 0},
85    {input: "haegwjzuvuyypxyu", part1: 0},
86    {input: "dvszwmarrgswjxmb", part1: 0},
87    {input: "qjhvhtzxzqqjkmpb", part2: 1},
88    {input: "xxyxx", part2: 1},
89    {input: "uurcxstgmygtbstg", part2: 0},
90    {input: "ieodomkazucvgmuy", part2: 0},
91]);