Files
aoc20/4-passport_processing/src/main.rs
2021-01-08 23:41:37 +11:00

191 lines
6.4 KiB
Rust

use std::{collections::HashMap, str::FromStr};
use anyhow::Result;
use regex::Regex;
#[derive(Debug,thiserror::Error)]
enum ParseError {
#[error("Expected {0}")]
Expected(String)
}
#[derive(Debug,PartialEq, Eq, Hash)]
enum EntryType {
Byr,
Iyr,
Eyr,
Hgt,
Hcl,
Ecl,
Pid,
Cid,
}
impl FromStr for EntryType {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"byr" => Ok(Self::Byr),
"iyr" => Ok(Self::Iyr),
"eyr" => Ok(Self::Eyr),
"hgt" => Ok(Self::Hgt),
"hcl" => Ok(Self::Hcl),
"ecl" => Ok(Self::Ecl),
"pid" => Ok(Self::Pid),
"cid" => Ok(Self::Cid),
_ => Err(ParseError::Expected(format!("byr, iyr, eyr, hgt, hcl, ecl, pid, cid, but got {}", s)))
}
}
}
impl EntryType {
pub fn is_valid(&self, value: &Option<&str>) -> bool {
match (*value, self) {
(Some(value), EntryType::Byr) => {
// four digits; at least 1920 and at most 2002.
match str::parse::<i64>(value) {
Ok(number) => {
(1920..=2002).contains(&number)
}
Err(_) => { false }
}
}
(Some(value), EntryType::Iyr) => {
// four digits; at least 2010 and at most 2020.
match str::parse::<i64>(value) {
Ok(number) => {
(2010..=2020).contains(&number)
}
Err(_) => { false }
}
}
(Some(value), EntryType::Eyr) => {
// four digits; at least 2020 and at most 2030.
match str::parse::<i64>(value) {
Ok(number) if (2020..=2030)
.contains(&number) => true,
_ => false
}
}
(Some(value), EntryType::Hgt) => {
// a number followed by either cm or in:
// if cm, the number must be at least 150 and at most 193.
// if in, the number must be at least 59 and at most 76.
let rex_cm = Regex::new(r"(\d*)cm").unwrap();
let rex_in = Regex::new(r"(\d*)in").unwrap();
if rex_cm.is_match(value) {
let m = rex_cm.captures(value);
// At least "None" fails the test
match str::parse::<i64>(m.unwrap().get(1).unwrap().as_str()) {
Ok(height) if (150..=193).contains(&height) => true,
_ => false
}
} else if rex_in.is_match(value) {
let m = rex_in.captures(value);
// At least "None" fails the test
match str::parse::<i64>(m.unwrap().get(1).unwrap().as_str()) {
Ok(height) if (59..=76).contains(&height) => true,
_ => false
}
} else {
false
}
}
(Some(value), EntryType::Hcl) => {
// a # followed by exactly six characters 0-9 or a-f.
//probably ok as long as we deal with ascii right ?
let rex = Regex::new(r"([a-z]|[0-9]){6}").unwrap();
value.starts_with('#') && rex.is_match(&(value.chars().skip(1).collect::<String>()))
}
(Some(value), EntryType::Ecl) => {
// exactly one of: amb blu brn gry grn hzl oth.
let valid_values = vec!["amb", "blu", "brn", "gry", "grn", "hzl", "oth"];
valid_values.contains(&value)
}
(Some(value), EntryType::Pid) => {
// a nine-digit number, including leading zeroes.
// probably ok if we deal only with ascii right ?
value.as_bytes().len() == 9 && str::parse::<i64>(value).is_ok()
}
(_, EntryType::Cid) => {
// ignored, missing or not
true
}
_ => false
}
}
}
#[derive(Debug)]
struct Passport<'a> {
fields: HashMap<EntryType, Option<&'a str>>
}
impl<'a> Passport<'a> {
fn new() -> Self {
let mut res = Self {
fields: HashMap::new()
};
res.fields.insert(EntryType::Byr, None);
res.fields.insert(EntryType::Iyr, None);
res.fields.insert(EntryType::Eyr, None);
res.fields.insert(EntryType::Hgt, None);
res.fields.insert(EntryType::Hcl, None);
res.fields.insert(EntryType::Ecl, None);
res.fields.insert(EntryType::Pid, None);
res.fields.insert(EntryType::Cid, None);
res
}
fn parse(&mut self, s: &'static str) {
for entry_s in s.split(&[' ', '\n'][..]) {
let mut tokens = entry_s.split(':');
if let Ok(etype) = EntryType::from_str(tokens.next().unwrap()) {
self.fields.insert(etype, tokens.next());
}
}
}
fn is_valid_legacy(&self, mandatory_cid: bool) -> bool {
self.fields.iter().all(|(key, value)| {
(!mandatory_cid && *key == EntryType::Cid) ||
*value != None
})
}
fn is_valid(&self) -> bool {
self.fields.iter().all(|(key, value)| {
key.is_valid(value)
})
}
}
fn main() {
let data = include_str!("../data/input.txt");
// eprintln!("data = {}", data);
//eprintln!("data.split() = {:?}", data.split("\n\n").collect::<Vec<&str>>());
let passports: Vec<Passport> = data.split("\n\n").map(|pass_str| {
let mut res = Passport::new();
res.parse(pass_str);
res
}).collect();
// eprintln!("passports = {:#?}", passports);
// eprintln!("passports.size() = {:#?}", passports.len());
let nb_valid = passports.iter().filter(|p| (*p).is_valid_legacy(false)).count();
eprintln!("nb_valid = {:#?}", nb_valid);
let nb_valid_secondpass = passports.iter().filter(|p| p.is_valid()).count();
eprintln!("nb_valid_secondpass = {:#?}", nb_valid_secondpass);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_validation() {
let mut pass = Passport::new();
pass.parse("cid:271 pid:009457684 hcl:#80fab0\nbyr:1999 ecl:blu\niyr:2015\neyr:2029 hgt:159cm");
eprintln!("pass = {:#?}", pass);
assert_eq!(pass.is_valid(), true)
}
}