1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
//! # Password Philosophy
//!
//! Parsing the rules upfront allows both part 1 and part 2 to be solved in a straightforward manner.
//!
//! There's no need to first convert the input into lines since we know that each rule has 4 parts.
//! Instead we use the [`split`] method with a slice of delimeters to break the input into
//! an `Iterator` of tokens, then use our utility [`chunk`] method to group the tokens into an
//! array of size 4.
//!
//! [`split`]: slice::split
//! [`chunk`]: crate::util::iter
use crate::util::iter::*;
use crate::util::parse::*;

pub struct Rule<'a> {
    start: usize,
    end: usize,
    letter: u8,
    password: &'a [u8],
}

impl Rule<'_> {
    fn from([a, b, c, d]: [&str; 4]) -> Rule<'_> {
        let start = a.unsigned();
        let end = b.unsigned();
        let letter = c.as_bytes()[0];
        let password = d.as_bytes();
        Rule { start, end, letter, password }
    }
}

pub fn parse(input: &str) -> Vec<Rule<'_>> {
    input
        .split(['-', ':', ' ', '\n'])
        .filter(|s| !s.is_empty())
        .chunk::<4>()
        .map(Rule::from)
        .collect()
}

pub fn part1(input: &[Rule<'_>]) -> usize {
    input
        .iter()
        .filter(|rule| {
            let count = rule.password.iter().filter(|&&l| l == rule.letter).count();
            rule.start <= count && count <= rule.end
        })
        .count()
}

pub fn part2(input: &[Rule<'_>]) -> usize {
    input
        .iter()
        .filter(|rule| {
            let first = rule.password[rule.start - 1] == rule.letter;
            let second = rule.password[rule.end - 1] == rule.letter;
            first ^ second
        })
        .count()
}