diff --git a/2020/04/main.rs b/2020/04/main.rs index 3704b05..0e49494 100644 --- a/2020/04/main.rs +++ b/2020/04/main.rs @@ -12,8 +12,9 @@ fn main() -> Result<(), Box<dyn Error>> { let lines = utils::read_input_lines()?; let passports = load_passports(&lines)?; - let valid_passports = passports.iter().filter(|p| p.is_valid_or_from_north_pole()).count(); - println!("Valid passports: {}", valid_passports); + let passports = passports.into_iter().filter(|p| p.is_valid_or_from_north_pole()).collect::<Vec<Passport>>(); + println!("Valid passports with no field validation: {}", passports.len()); + println!("Valid passports with field validation: {}", passports.iter().filter(|p| p.are_field_values_valid()).count()); Ok(()) } @@ -61,24 +62,41 @@ impl PassportField { _ => None } } + + fn is_value_valid(&self, value: &str) -> bool { + fn as_u32(value: &str) -> Option<u32> { + value.parse().ok() + } + + fn as_u32_with_unit(value: &str, unit: &str) -> Option<u32> { + value.strip_suffix(unit).and_then(as_u32) + } + + match self { + PassportField::BirthYear => as_u32(value).filter(|year| *year >= 1920 && *year <= 2002).is_some(), + PassportField::IssueYear => as_u32(value).filter(|year| *year >= 2010 && *year <= 2020).is_some(), + PassportField::ExpirationYear => as_u32(value).filter(|year| *year >= 2020 && *year <= 2030).is_some(), + PassportField::Height => { + if let Some(height) = as_u32_with_unit(value, "cm") { + height >= 150 && height <= 193 + } else if let Some(height) = as_u32_with_unit(value, "in") { + height >= 59 && height <= 76 + } else { + false + } + } + PassportField::HairColor => value.strip_prefix('#').filter(|hex| hex.chars().all(|c| c.is_digit(16))).is_some(), + PassportField::EyeColor => VALID_EYE_COLORS.contains(value), + PassportField::PassportId => value.len() == 9 && value.chars().all(|c| c.is_ascii_digit()), + PassportField::CountryId => true + } + } } struct Passport { fields: HashMap<PassportField, String>, } -lazy_static! { - static ref REQUIRED_FIELDS: HashSet<PassportField> = HashSet::from([ - PassportField::BirthYear, - PassportField::IssueYear, - PassportField::ExpirationYear, - PassportField::Height, - PassportField::HairColor, - PassportField::EyeColor, - PassportField::PassportId - ]); -} - impl Passport { fn new() -> Passport { Passport { fields: HashMap::new() } @@ -98,4 +116,24 @@ impl Passport { let fields = &self.fields.keys().map(|f| *f).collect::<HashSet<PassportField>>(); return fields.is_superset(&REQUIRED_FIELDS); } + + fn are_field_values_valid(&self) -> bool { + return self.fields.iter().all(|(field, value)| field.is_value_valid(value.as_str())); + } +} + +lazy_static! { + static ref REQUIRED_FIELDS: HashSet<PassportField> = HashSet::from([ + PassportField::BirthYear, + PassportField::IssueYear, + PassportField::ExpirationYear, + PassportField::Height, + PassportField::HairColor, + PassportField::EyeColor, + PassportField::PassportId + ]); + + static ref VALID_EYE_COLORS: HashSet<&'static str> = HashSet::from([ + "amb", "blu", "brn", "gry", "grn", "hzl", "oth" + ]); }