summaryrefslogtreecommitdiff
path: root/2020/rust/day04/src/main.rs
blob: 0d4c901d9dc615e3a1609049dc89057afcef5dbc (plain)
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
106
107
108
109
110
111
112
113
114
use anyhow::{Result, anyhow};
use std::collections::HashMap;
use std::env::args;
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::path::Path;
use regex::Regex;

type Passport = HashMap<String, String>;

fn read_input<T: AsRef<Path>>(filename: T) -> Result<Vec<Passport>> {
    let f = File::open(filename)?;
    let reader = BufReader::new(f);
    let mut passport = Passport::new();
    let mut passports = vec![];

    for line in reader.lines() {
        let line = line?;
        if line.is_empty() {
            passports.push(passport);
            passport = Passport::new();
            continue;
        }
        for pair in line.split(' ') {
            let mut s = pair.split(':');
            let key = From::from(s.next().ok_or_else(|| anyhow!("No key found"))?.trim());
            let value = From::from(s.next().ok_or_else(|| anyhow!("No value found"))?.trim());
            passport.insert(key, value);
        }
    }
    passports.push(passport);

    Ok(passports)
}

fn part1(input: &[Passport]) -> usize {
    let mut valid_count = 0;

    'validate: for passport in input {
        for key in &[ "byr", "iyr", "eyr", "hgt", "hcl", "ecl", "pid", /*"cid",*/ ] {
            if ! passport.contains_key(*key) {
                continue 'validate;
            }
        }
        valid_count += 1;
    }

    valid_count
}

fn part2(input: &[Passport]) -> Result<usize> {
    let mut valid_count = 0;
    let mut re: HashMap<String, Regex> = HashMap::new();

    // byr (Birth Year) - four digits; at least 1920 and at most 2002.
    re.insert(String::from("byr"), Regex::new(r"^(19[2-9][0-9])|(200[0-2])$")?);
    // iyr (Issue Year) - four digits; at least 2010 and at most 2020.
    re.insert(String::from("iyr"), Regex::new(r"^20(1[0-9])|(20)$")?);
    // eyr (Expiration Year) - four digits; at least 2020 and at most 2030.
    re.insert(String::from("eyr"), Regex::new(r"^20(2[0-9])|(30)$")?);
    // hgt (Height) - a number followed by either cm or in:
    // If cm, the number must be at least 150 and at most 193.
    // If in, the number must be at least 59 and at most 76.
    re.insert(String::from("hgt"),
        Regex::new(r"^1(([5-8][0-9])|(9[0-3]))cm|((59)|(6[0-9])|(7[0-6]))in$")?);
    // hcl (Hair Color) - a # followed by exactly six characters 0-9 or a-f.
    re.insert(String::from("hcl"), Regex::new(r"^#[0-9a-f]{6}$")?);
    // ecl (Eye Color) - exactly one of: amb blu brn gry grn hzl oth.
    re.insert(String::from("ecl"), Regex::new(r"^(amb|blu|brn|gry|grn|hzl|oth)$")?);
    // pid (Passport ID) - a nine-digit number, including leading zeroes.
    re.insert(String::from("pid"), Regex::new(r"^\d{9}$")?);
    // cid (Country ID) - ignored, missing or not.

    'validate: for passport in input {
        for key in &["byr", "iyr", "eyr", "hgt", "hcl", "ecl", "pid"] {
            match (passport.get(*key), re.get(*key)) {
                (Some(val), Some(re)) => if ! re.is_match(val) {
                    continue 'validate;
                },
                _ => continue 'validate,
            }
        }
        valid_count += 1;
    }

    Ok(valid_count)
}

fn main() {
    let ( do_part_1, do_part_2 ) = aoc::do_parts();

    let filename = match args().nth(1) {
        Some(f) => f,
        None => {
            eprintln!("Missing input filename");
            std::process::exit(1);
        },
    };
    match read_input(filename) {
        Ok(input) => {
            if do_part_1 {
                let solution = part1(&input);
                println!("Part1, found {} valid passports", solution);
            }
            if do_part_2 {
                match part2(&input) {
                    Ok(solution) => println!("Part2, found {} valid passports", solution),
                    Err(err) => eprintln!("Part2, failed: {}", err),
                }
            }
        },
        Err(err) => eprintln!("Could not read input: {}", err),
    }
}