advent_of_code/year2020/
day04.rs

1use crate::input::Input;
2
3fn is_valid(field_idx: usize, value: &str) -> bool {
4    fn in_range(string: &str, start: u32, end: u32) -> bool {
5        (start..=end).contains(&string.parse::<u32>().unwrap_or_default())
6    }
7
8    match field_idx {
9        0 => value.len() == 4 && in_range(value, 1920, 2002),
10        1 => value.len() == 4 && in_range(value, 2010, 2020),
11        2 => value.len() == 4 && in_range(value, 2020, 2030),
12        3 => {
13            (value.ends_with("cm") && in_range(&value[0..(value.len() - 2)], 150, 193))
14                || (value.ends_with("in") && in_range(&value[0..(value.len() - 2)], 59, 76))
15        }
16        4 => {
17            value.starts_with('#')
18                && value.len() == 7
19                && value[1..]
20                    .bytes()
21                    .all(|c| matches!(c, b'0'..=b'9' | b'a'..=b'f'))
22        }
23        5 => matches!(value, "amb" | "blu" | "brn" | "gry" | "grn" | "hzl" | "oth"),
24        6 => value.len() == 9 && value.parse::<u32>().is_ok(),
25        _ => false,
26    }
27}
28
29pub fn solve(input: &Input) -> Result<u32, String> {
30    const FIELD_NAMES: [&str; 7] = ["byr", "iyr", "eyr", "hgt", "hcl", "ecl", "pid"];
31
32    let mut fields_validity = [false; 7];
33    let mut valid_passports_count = 0;
34
35    for line in input.text.lines().chain(std::iter::once("")) {
36        if line.is_empty() {
37            if fields_validity.iter().all(|&ok| ok) {
38                valid_passports_count += 1;
39            }
40            fields_validity.iter_mut().for_each(|ok| *ok = false);
41        } else {
42            for (line_idx, entry) in line.split(' ').enumerate() {
43                let mut parts = entry.split(':');
44                if let (Some(key), Some(value), None) = (parts.next(), parts.next(), parts.next()) {
45                    if let Some(field_idx) = FIELD_NAMES.iter().position(|&field| field == key) {
46                        fields_validity[field_idx] =
47                            input.is_part_one() || is_valid(field_idx, value);
48                    }
49                } else {
50                    return Err(format!(
51                        "Line {}: Word not having the format $KEY:$VALUE",
52                        line_idx + 1
53                    ));
54                }
55            }
56        }
57    }
58
59    Ok(valid_passports_count)
60}
61
62#[test]
63pub fn tests() {
64    use crate::input::{test_part_one, test_part_two};
65
66    test_part_one!("ecl:gry pid:860033327 eyr:2020 hcl:#fffffd
67byr:1937 iyr:2017 cid:147 hgt:183cm
68
69iyr:2013 ecl:amb cid:350 eyr:2023 pid:028048884
70hcl:#cfa07d byr:1929
71
72hcl:#ae17e1 iyr:2013
73eyr:2024
74ecl:brn pid:760753108 byr:1931
75hgt:179cm
76
77hcl:#cfa07d eyr:2025 pid:166559648
78iyr:2011 ecl:brn hgt:59in" => 2);
79
80    let real_input = include_str!("day04_input.txt");
81    test_part_one!(real_input => 210);
82    test_part_two!(real_input => 131);
83}