use crate::prelude::*; pub fn solution() -> Solution { Solution::new(Day::new(4, 2020), part_1, part_2).with_expected(237, 172) } #[derive(Debug, Clone)] struct Passport { byr: Option, iyr: Option, eyr: Option, hgt: Option, hcl: Option, ecl: Option, pid: Option, cid: Option, } fn parse_passports(input: &str) -> Vec { let mut passports = vec![]; let blank = Passport { byr: None, iyr: None, eyr: None, hgt: None, hcl: None, ecl: None, pid: None, cid: None, }; let mut intermediate = blank.clone(); for line in input.lines() { if line.is_empty() { passports.push(intermediate.clone()); intermediate = blank.clone(); continue; } let components = line.split(' '); for component in components { let colon_index = component.find(':').unwrap(); let key = &component[0..colon_index]; let value = &component[colon_index + 1..]; match key { "byr" => intermediate.byr = Some(value.to_string()), "iyr" => intermediate.iyr = Some(value.to_string()), "eyr" => intermediate.eyr = Some(value.to_string()), "hgt" => intermediate.hgt = Some(value.to_string()), "hcl" => intermediate.hcl = Some(value.to_string()), "ecl" => intermediate.ecl = Some(value.to_string()), "pid" => intermediate.pid = Some(value.to_string()), "cid" => intermediate.cid = Some(value.to_string()), _ => unreachable!("{key}"), }; } } passports } fn part_1(input: &str) -> Result { Ok( parse_passports(input) .into_iter() .filter(|passport| { passport.byr.is_some() && passport.iyr.is_some() && passport.eyr.is_some() && passport.hgt.is_some() && passport.hcl.is_some() && passport.ecl.is_some() && passport.pid.is_some() }) .count() .to_string(), ) } fn part_2(input: &str) -> Result { let hcl_regex = Regex::new("^#[a-fA-F0-9]{6}$").unwrap(); let pid_regex = Regex::new("^[0-9]{9}$").unwrap(); let ecl_values = vec![ "amb".to_string(), "blu".to_string(), "brn".to_string(), "gry".to_string(), "grn".to_string(), "hzl".to_string(), "oth".to_string(), ]; Ok( parse_passports(input) .into_iter() .filter(|passport| { passport.byr.is_some() && passport.iyr.is_some() && passport.eyr.is_some() && passport.hgt.is_some() && passport.hcl.is_some() && passport.ecl.is_some() && passport.pid.is_some() }) .filter(|passport| { let byr = passport .byr .as_ref() .unwrap() .parse::() .unwrap_or_default(); (1920..=2002).contains(&byr) }) .filter(|passport| { let iyr = passport .iyr .as_ref() .unwrap() .parse::() .unwrap_or_default(); (2010..=2020).contains(&iyr) }) .filter(|passport| { let eyr = passport .eyr .as_ref() .unwrap() .parse::() .unwrap_or_default(); (2020..=2030).contains(&eyr) }) .filter(|passport| { let hgt = passport.hgt.as_ref().unwrap(); match &hgt[hgt.len() - 2..] { "cm" => { let value = hgt[0..hgt.len() - 2].parse::().unwrap_or_default(); (150..=193).contains(&value) } "in" => { let value = hgt[0..hgt.len() - 2].parse::().unwrap_or_default(); (59..=76).contains(&value) } _ => false, } }) .filter(|passport| { let hcl = passport.hcl.as_ref().unwrap(); hcl_regex.is_match(hcl) }) .filter(|passport| { let ecl = passport.ecl.as_ref().unwrap(); ecl_values.contains(ecl) }) .filter(|passport| { let pid = passport.pid.as_ref().unwrap(); pid_regex.is_match(pid) }) .count() .to_string(), ) }