aoc/year2020/
day02.rs

1//! # Password Philosophy
2//!
3//! Parsing the rules upfront allows both part 1 and part 2 to be solved in a straightforward manner.
4//!
5//! There's no need to first convert the input into lines since we know that each rule has 4 parts.
6//! Instead we use the [`split`] method with a slice of delimeters to break the input into
7//! an `Iterator` of tokens, then use our utility [`chunk`] method to group the tokens into an
8//! array of size 4.
9//!
10//! [`split`]: slice::split
11//! [`chunk`]: crate::util::iter
12use crate::util::iter::*;
13use crate::util::parse::*;
14
15pub struct Rule<'a> {
16    start: usize,
17    end: usize,
18    letter: u8,
19    password: &'a [u8],
20}
21
22impl Rule<'_> {
23    fn from([a, b, c, d]: [&str; 4]) -> Rule<'_> {
24        let start = a.unsigned();
25        let end = b.unsigned();
26        let letter = c.as_bytes()[0];
27        let password = d.as_bytes();
28        Rule { start, end, letter, password }
29    }
30}
31
32pub fn parse(input: &str) -> Vec<Rule<'_>> {
33    input
34        .split(['-', ':', ' ', '\n'])
35        .filter(|s| !s.is_empty())
36        .chunk::<4>()
37        .map(Rule::from)
38        .collect()
39}
40
41pub fn part1(input: &[Rule<'_>]) -> usize {
42    input
43        .iter()
44        .filter(|rule| {
45            let count = rule.password.iter().filter(|&&l| l == rule.letter).count();
46            rule.start <= count && count <= rule.end
47        })
48        .count()
49}
50
51pub fn part2(input: &[Rule<'_>]) -> usize {
52    input
53        .iter()
54        .filter(|rule| {
55            let first = rule.password[rule.start - 1] == rule.letter;
56            let second = rule.password[rule.end - 1] == rule.letter;
57            first ^ second
58        })
59        .count()
60}