Skip to main content

aoc/year2020/
day02.rs

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