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
use crate::input::Input;

fn is_valid(field_idx: usize, value: &str) -> bool {
    fn in_range(string: &str, start: u32, end: u32) -> bool {
        (start..=end).contains(&string.parse::<u32>().unwrap_or_default())
    }

    match field_idx {
        0 => value.len() == 4 && in_range(value, 1920, 2002),
        1 => value.len() == 4 && in_range(value, 2010, 2020),
        2 => value.len() == 4 && in_range(value, 2020, 2030),
        3 => {
            (value.ends_with("cm") && in_range(&value[0..(value.len() - 2)], 150, 193))
                || (value.ends_with("in") && in_range(&value[0..(value.len() - 2)], 59, 76))
        }
        4 => {
            value.starts_with('#')
                && value.len() == 7
                && value[1..]
                    .bytes()
                    .all(|c| matches!(c, b'0'..=b'9' | b'a'..=b'f'))
        }
        5 => matches!(value, "amb" | "blu" | "brn" | "gry" | "grn" | "hzl" | "oth"),
        6 => value.len() == 9 && value.parse::<u32>().is_ok(),
        _ => false,
    }
}

pub fn solve(input: &mut Input) -> Result<u32, String> {
    const FIELD_NAMES: [&str; 7] = ["byr", "iyr", "eyr", "hgt", "hcl", "ecl", "pid"];

    let mut fields_validity = [false; 7];
    let mut valid_passports_count = 0;

    for line in input.text.lines().chain(std::iter::once("")) {
        if line.is_empty() {
            if fields_validity.iter().all(|&ok| ok) {
                valid_passports_count += 1;
            }
            fields_validity.iter_mut().for_each(|ok| *ok = false);
        } else {
            for (line_idx, entry) in line.split(' ').enumerate() {
                let mut parts = entry.split(':');
                if let (Some(key), Some(value), None) = (parts.next(), parts.next(), parts.next()) {
                    if let Some(field_idx) = FIELD_NAMES.iter().position(|&field| field == key) {
                        fields_validity[field_idx] =
                            input.is_part_one() || is_valid(field_idx, value);
                    }
                } else {
                    return Err(format!(
                        "Line {}: Word not having the format $KEY:$VALUE",
                        line_idx + 1
                    ));
                }
            }
        }
    }

    Ok(valid_passports_count)
}

#[test]
pub fn tests() {
    use crate::{test_part_one, test_part_two};

    test_part_one!("ecl:gry pid:860033327 eyr:2020 hcl:#fffffd
byr:1937 iyr:2017 cid:147 hgt:183cm

iyr:2013 ecl:amb cid:350 eyr:2023 pid:028048884
hcl:#cfa07d byr:1929

hcl:#ae17e1 iyr:2013
eyr:2024
ecl:brn pid:760753108 byr:1931
hgt:179cm

hcl:#cfa07d eyr:2025 pid:166559648
iyr:2011 ecl:brn hgt:59in" => 2);

    let real_input = include_str!("day04_input.txt");
    test_part_one!(real_input => 210);
    test_part_two!(real_input => 131);
}