//! Day 04 of 2020. use crate::prelude::*; /// Get the solution for day 04 of 2020. pub fn solution() -> Solution { Solution::new(Day::new(4, 2020), part_1, part_2).with_expected(237, 172) } /// The passport puzzle input. #[derive(Debug, Clone)] struct Passport { /// Birth year. byr: Option, /// Issue year. iyr: Option, /// Expiration year. eyr: Option, /// Height. hgt: Option, /// Hair color. hcl: Option, /// Eye color. ecl: Option, /// Passport ID. pid: Option, /// Country ID. cid: Option, } /// Parse the puzzle input into a list of passports. 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 } /// The logic to solve part one. 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(), ) } /// The logic to solve part two. 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(), ) }