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 crate::util::parse::*;
9use std::ops::RangeInclusive;
10
11type Input = (u32, u32);
12
13pub fn parse(input: &str) -> Input {
14    let mut passport = Vec::new();
15    let mut part_one = 0;
16    let mut part_two = 0;
17
18    for block in input.split("\n\n") {
19        parse_block(&mut passport, block);
20
21        if passport.len() == 7 {
22            part_one += 1;
23            part_two += passport.iter().all(validate_field) as u32;
24        }
25
26        passport.clear();
27    }
28
29    (part_one, part_two)
30}
31
32pub fn part1(input: &Input) -> u32 {
33    input.0
34}
35
36pub fn part2(input: &Input) -> u32 {
37    input.1
38}
39
40fn parse_block<'a>(passport: &mut Vec<[&'a str; 2]>, block: &'a str) {
41    for pair @ [key, _] in block.split([':', ' ', '\n']).chunk::<2>() {
42        if key != "cid" {
43            passport.push(pair);
44        }
45    }
46}
47
48fn validate_field(&[key, value]: &[&str; 2]) -> bool {
49    match key {
50        "byr" => validate_range(value, 1920..=2002),
51        "iyr" => validate_range(value, 2010..=2020),
52        "eyr" => validate_range(value, 2020..=2030),
53        "hgt" => validate_height(value),
54        "hcl" => validate_hair_color(value),
55        "ecl" => validate_eye_color(value),
56        "pid" => validate_passport_id(value),
57        _ => unreachable!(),
58    }
59}
60
61fn validate_range(s: &str, range: RangeInclusive<u32>) -> bool {
62    range.contains(&s.unsigned())
63}
64
65fn validate_height(hgt: &str) -> bool {
66    if hgt.len() == 4 && hgt.ends_with("in") {
67        validate_range(&hgt[..2], 59..=76)
68    } else if hgt.len() == 5 && hgt.ends_with("cm") {
69        validate_range(&hgt[..3], 150..=193)
70    } else {
71        false
72    }
73}
74
75fn validate_hair_color(hcl: &str) -> bool {
76    let hcl = hcl.as_bytes();
77    hcl.len() == 7 && hcl[0] == b'#' && hcl[1..].iter().all(u8::is_ascii_hexdigit)
78}
79
80fn validate_eye_color(ecl: &str) -> bool {
81    matches!(ecl, "amb" | "blu" | "brn" | "gry" | "grn" | "hzl" | "oth")
82}
83
84fn validate_passport_id(pid: &str) -> bool {
85    pid.len() == 9 && pid.bytes().all(|b| b.is_ascii_digit())
86}