aoc/year2020/
day04.rs

1//! # Passport Processing
2//!
3//! [Regular expressions](https://en.wikipedia.org/wiki/Regular_expression) are a good fit for this
4//! problem. However as the principles of this crate are to avoid external dependencies and
5//! maximize speed we'll instead hand code validation functions for each of the
6//! passport field criteria.
7use crate::util::iter::*;
8use std::ops::RangeInclusive;
9
10type Passport<'a> = Vec<[&'a str; 2]>;
11
12pub fn parse(input: &str) -> Vec<Passport<'_>> {
13    input.split("\n\n").map(parse_block).collect()
14}
15
16pub fn part1(input: &[Passport<'_>]) -> usize {
17    input.iter().filter(|passport| passport.len() == 7).count()
18}
19
20pub fn part2(input: &[Passport<'_>]) -> usize {
21    input
22        .iter()
23        .filter(|passport| passport.len() == 7)
24        .filter(|passport| passport.iter().all(validate_field))
25        .count()
26}
27
28fn parse_block(block: &str) -> Passport<'_> {
29    let mut fields = Vec::with_capacity(7);
30
31    for pair @ [key, _] in block.split([':', ' ', '\n']).chunk::<2>() {
32        if key != "cid" {
33            fields.push(pair);
34        }
35    }
36
37    fields
38}
39
40fn validate_field(&[key, value]: &[&str; 2]) -> bool {
41    match key {
42        "byr" => validate_range(value, 1920..=2002),
43        "iyr" => validate_range(value, 2010..=2020),
44        "eyr" => validate_range(value, 2020..=2030),
45        "hgt" => validate_height(value),
46        "hcl" => validate_hair_color(value),
47        "ecl" => validate_eye_color(value),
48        "pid" => validate_passport_id(value),
49        _ => unreachable!(),
50    }
51}
52
53fn validate_range(s: &str, range: RangeInclusive<u32>) -> bool {
54    s.parse().is_ok_and(|n| range.contains(&n))
55}
56
57fn validate_height(hgt: &str) -> bool {
58    if hgt.len() == 4 && hgt.ends_with("in") {
59        validate_range(&hgt[..2], 59..=76)
60    } else if hgt.len() == 5 && hgt.ends_with("cm") {
61        validate_range(&hgt[..3], 150..=193)
62    } else {
63        false
64    }
65}
66
67fn validate_hair_color(hcl: &str) -> bool {
68    let hcl = hcl.as_bytes();
69    hcl.len() == 7 && hcl[0] == b'#' && hcl[1..].iter().all(u8::is_ascii_hexdigit)
70}
71
72fn validate_eye_color(ecl: &str) -> bool {
73    matches!(ecl, "amb" | "blu" | "brn" | "gry" | "grn" | "hzl" | "oth")
74}
75
76fn validate_passport_id(pid: &str) -> bool {
77    pid.len() == 9 && pid.bytes().all(|b| b.is_ascii_digit())
78}