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 hgt_re = Regex::new(r"^(?P\d+)(?Pcm|in)$")?; let hcl_re = Regex::new(r"^#[0-9a-f]{6}$")?; let ecl_re = Regex::new(r"^(amb|blu|brn|gry|grn|hzl|oth)$")?; let pid_re = Regex::new(r"^\d{9}$")?; 'validate: for passport in input { // byr (Birth Year) - four digits; at least 1920 and at most 2002. match passport.get("byr") { Some(byr) => match byr.parse() { Ok(1920..=2002) => {}, _ => continue 'validate, } _ => continue 'validate, } // iyr (Issue Year) - four digits; at least 2010 and at most 2020. match passport.get("iyr") { Some(iyr) => match iyr.parse() { Ok(2010..=2020) => {}, _ => continue 'validate, } _ => continue 'validate, } // eyr (Expiration Year) - four digits; at least 2020 and at most 2030. match passport.get("eyr") { Some(eyr) => match eyr.parse() { Ok(2020..=2030) => {}, _ => continue 'validate, } _ => continue 'validate, } // 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. match passport.get("hgt") { Some(hgt) => match hgt_re.captures(hgt) { Some(caps) => match &caps.name("unit") { Some(unit) => match unit.as_str() { "cm" => match &caps.name("value") { Some(value) => match value.as_str().parse() { Ok(150..=193) => {}, _ => continue 'validate, } _ => continue 'validate, }, "in" => match &caps.name("value") { Some(value) => match value.as_str().parse() { Ok(59..=76) => {}, _ => continue 'validate, }, _ => continue 'validate, }, _ => continue 'validate, } None => continue 'validate, } None => continue 'validate, } _ => continue 'validate, } // hcl (Hair Color) - a # followed by exactly six characters 0-9 or a-f. match passport.get("hcl") { Some(hcl) if hcl_re.is_match(hcl) => {}, _ => continue 'validate, } // ecl (Eye Color) - exactly one of: amb blu brn gry grn hzl oth. match passport.get("ecl") { Some(ecl) if ecl_re.is_match(ecl) => {}, _ => continue 'validate, } // pid (Passport ID) - a nine-digit number, including leading zeroes. match passport.get("pid") { Some(pid) if pid_re.is_match(pid) => {}, _ => continue 'validate, } // cid (Country ID) - ignored, missing or not. 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), } }