use aoc_runner_derive::{aoc, aoc_generator}; use std::collections::HashMap; #[derive(Debug, PartialEq, Eq, Default)] pub struct Creds { byr: Option, iyr: Option, eyr: Option, hgt: Option, hcl: Option, ecl: Option, pid: Option, cid: Option, } impl Creds { fn fields(&self) -> HashMap<&str, Option> { let mut ret = HashMap::new(); ret.insert("byr", self.byr.clone()); ret.insert("iyr", self.iyr.clone()); ret.insert("eyr", self.eyr.clone()); ret.insert("hgt", self.hgt.clone()); ret.insert("hcl", self.hcl.clone()); ret.insert("ecl", self.ecl.clone()); ret.insert("pid", self.pid.clone()); ret.insert("cid", self.cid.clone()); ret } fn is_valid_part1(&self) -> bool { for (k, v) in self.fields() { if k == "cid" { continue; } if v.is_none() { return false; } } return true; } fn year_in_range(val: &str, min: i64, max: i64) -> bool { if val.len() != 4 { return false; } if let Ok(val) = val.parse::() { return val >= min && val <= max; }; return false; } fn is_number(val: &str, ndigits: usize) -> bool { val.chars().filter(|c| c.is_ascii_digit()).count() == ndigits } fn is_valid_height(val: &str) -> bool { let it = val.chars(); let (num, unit): (String, String) = it.partition(|c| c.is_ascii_digit()); let num: Result = num.parse(); if let Ok(num) = num { if unit == "cm" { return (150..=193).contains(&num); }; if unit == "in" { return (59..=76).contains(&num); }; } return false; } fn validate_ecl(val: &str) -> bool { let choices: Vec<&str> = vec!["amb", "blu", "brn", "gry", "grn", "hzl", "oth"]; return choices.iter().any(|&v| v == val); } fn validate_hcl(val: &str) -> bool { if val.len() != 7 { return false; } let mut ch = val.chars(); if ch.next() != Some('#') { return false; } return ch.take_while(|v| v.is_ascii_hexdigit()).count() == 6; } fn validate(key: &str, value: &str) -> bool { match key { "byr" => Self::year_in_range(value, 1920, 2002), "iyr" => Self::year_in_range(value, 2010, 2020), "eyr" => Self::year_in_range(value, 2020, 2030), "hgt" => Self::is_valid_height(value), "pid" => Self::is_number(value, 9), "ecl" => Self::validate_ecl(value), "hcl" => Self::validate_hcl(value), _ => false, } } fn is_valid_part2(&self) -> bool { for (k, v) in self.fields() { if k == "cid" { continue; } if let Some(v) = v { if !Self::validate(k, &v) { return false; } } else { return false; } } return true; } } #[aoc_generator(day4)] pub fn input_generator(input: &str) -> Vec { let mut ret = vec![]; for creds in input.split("\n\n") { let creds = creds.replace("\n", " "); let creds = creds.split(' '); let mut cred_data = Creds::default(); for cred in creds { let cred: Vec<&str> = cred.split(':').collect(); let (key, value) = (cred[0], cred[1]); match key { "byr" => { cred_data.byr = Some(value.to_string()); } "iyr" => { cred_data.iyr = Some(value.to_string()); } "eyr" => { cred_data.eyr = Some(value.to_string()); } "hgt" => { cred_data.hgt = Some(value.to_string()); } "hcl" => { cred_data.hcl = Some(value.to_string()); } "ecl" => { cred_data.ecl = Some(value.to_string()); } "pid" => { cred_data.pid = Some(value.to_string()); } "cid" => { cred_data.cid = Some(value.to_string()); } other => { panic!("Invalid key: {}", other); } } } ret.push(cred_data); } ret } #[aoc(day4, part1)] pub fn solve_part1(input: &[Creds]) -> usize { return input.iter().filter(|v| v.is_valid_part1()).count(); } #[aoc(day4, part2)] pub fn solve_part2(input: &[Creds]) -> usize { return input.iter().filter(|v| v.is_valid_part2()).count(); }