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; fn read_input>(filename: T) -> Result> { 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 { let mut valid_count = 0; let mut re: HashMap = 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), } }