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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
//! # Day 4: Secure Container
//!
//! You arrive at the Venus fuel depot only to discover it's protected by a password. The Elves had
//! written the password on a sticky note, but someone threw it out.
//!
//! However, they do remember a few key facts about the password:
//!
//! - It is a six-digit number.
//! - The value is within the range given in your puzzle input.
//! - Two adjacent digits are the same (like `22` in `122345`).
//! - Going from left to right, the digits **never decrease**; they only ever increase or stay the
//!   same (like `111123` or `135679`).
//!
//! Other than the range rule, the following are true:
//!
//! - `111111` meets these criteria (double `11`, never decreases).
//! - `223450` does not meet these criteria (decreasing pair of digits `50`).
//! - `123789` does not meet these criteria (no double).
//!
//! **How many different passwords** within the range given in your puzzle input meet these
//! criteria?
//!
//! # Day Two
//!
//! An Elf just remembered one more important detail: the two adjacent matching digits **are not
//! part of a larger group of matching digits**.
//
//! Given this additional criterion, but still ignoring the range rule, the following are now true:
//!
//! - `112233` meets these criteria because the digits never decrease and all repeated digits are
//!   exactly two digits long.
//! - `123444` no longer meets the criteria (the repeated `44` is part of a larger group of `444`).
//! - `111122` meets the criteria (even though `1` is repeated more than twice, it still contains a
//!   double `22`).
//!
//! **How many different passwords** within the range given in your puzzle input meet all of the
//! criteria?

use anyhow::{ensure, Result};
use itertools::Itertools;

pub const INPUT: &str = include_str!("d04.txt");

pub fn solve_part_one(input: &str) -> Result<i64> {
    let (min, max) = parse_input(input)?;

    Ok((min..=max).filter(|v| verify_password(*v, false)).count() as i64)
}

pub fn solve_part_two(input: &str) -> Result<i64> {
    let (min, max) = parse_input(input)?;

    Ok((min..=max).filter(|v| verify_password(*v, true)).count() as i64)
}

fn parse_input(input: &str) -> Result<(u32, u32)> {
    let mut input = input.lines().next().unwrap().split('-');
    let min = input.next().unwrap().parse()?;
    let max = input.next().unwrap().parse()?;

    ensure!(111_111 <= min && max <= 999_999, "min and max must be six digits long");

    Ok((min, max))
}

fn verify_password(pw: u32, part2: bool) -> bool {
    if (111_111..=999_999).contains(&pw) {
        let chars = pw.to_string().bytes().collect::<Vec<u8>>();
        return has_pairs(&chars) && is_rising(&chars) && (!part2 || has_unique_pair(&chars));
    }
    false
}

fn has_pairs(chars: &[u8]) -> bool {
    chars.iter().tuple_windows().any(|(l, r)| l == r)
}

fn is_rising(chars: &[u8]) -> bool {
    chars.iter().tuple_windows().all(|(l, r)| l <= r)
}

fn has_unique_pair(chars: &[u8]) -> bool {
    chars.iter().tuple_windows().enumerate().any(|(i, (l, r))| {
        l == r && (i == 0 || *l != chars[i - 1]) && (i == 4 || *l != chars[i + 2])
    })
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn part_one() {
        assert!(verify_password(111_111, false));
        assert!(!verify_password(223_450, false));
        assert!(!verify_password(123_789, false));
    }

    #[test]
    fn part_two() {
        assert!(verify_password(112_233, true));
        assert!(!verify_password(123_444, true));
        assert!(verify_password(111_122, true));
    }
}