mirror of
https://github.com/chylex/Advent-of-Code.git
synced 2024-10-17 02:42:45 +02:00
140 lines
4.0 KiB
Rust
140 lines
4.0 KiB
Rust
use std::collections::{HashMap, HashSet};
|
|
use std::error::Error;
|
|
|
|
use lazy_static::lazy_static;
|
|
|
|
use crate::utils::GenericError;
|
|
|
|
#[path = "../utils/mod.rs"]
|
|
mod utils;
|
|
|
|
fn main() -> Result<(), Box<dyn Error>> {
|
|
let lines = utils::read_input_lines()?;
|
|
let passports = load_passports(&lines)?;
|
|
|
|
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(())
|
|
}
|
|
|
|
fn load_passports(lines: &Vec<String>) -> Result<Vec<Passport>, GenericError> {
|
|
let mut passports = Vec::new();
|
|
let mut passport = Passport::new();
|
|
|
|
for line in lines {
|
|
if line.is_empty() {
|
|
passports.push(passport);
|
|
passport = Passport::new();
|
|
} else {
|
|
passport.load_fields_from_line(line.as_str())?;
|
|
}
|
|
}
|
|
|
|
passports.push(passport);
|
|
Ok(passports)
|
|
}
|
|
|
|
#[derive(Eq, PartialEq, Hash, Debug, Copy, Clone)]
|
|
enum PassportField {
|
|
BirthYear,
|
|
IssueYear,
|
|
ExpirationYear,
|
|
Height,
|
|
HairColor,
|
|
EyeColor,
|
|
PassportId,
|
|
CountryId,
|
|
}
|
|
|
|
impl PassportField {
|
|
fn from(s: &str) -> Option<PassportField> {
|
|
match s {
|
|
"byr" => Some(PassportField::BirthYear),
|
|
"iyr" => Some(PassportField::IssueYear),
|
|
"eyr" => Some(PassportField::ExpirationYear),
|
|
"hgt" => Some(PassportField::Height),
|
|
"hcl" => Some(PassportField::HairColor),
|
|
"ecl" => Some(PassportField::EyeColor),
|
|
"pid" => Some(PassportField::PassportId),
|
|
"cid" => Some(PassportField::CountryId),
|
|
_ => 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>,
|
|
}
|
|
|
|
impl Passport {
|
|
fn new() -> Passport {
|
|
Passport { fields: HashMap::new() }
|
|
}
|
|
|
|
fn load_fields_from_line(&mut self, line: &str) -> Result<(), GenericError> {
|
|
for field_entry in line.split(' ') {
|
|
let (field_name, field_value) = field_entry.split_once(':').ok_or_else(|| GenericError::new("Passport entry is missing a colon."))?;
|
|
let field = PassportField::from(field_name).ok_or_else(|| GenericError::new(format!("Passport field is invalid: {}", field_name)))?;
|
|
self.fields.insert(field, field_value.to_string());
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn is_valid_or_from_north_pole(&self) -> bool {
|
|
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"
|
|
]);
|
|
}
|