From a7e4ce92b435f1f08aa7595375bb86bab90e9461 Mon Sep 17 00:00:00 2001 From: Stefan Schwarz Date: Fri, 11 Dec 2020 22:43:16 +0100 Subject: [PATCH] day04 --- src/day04/main.rs | 239 +++++++++++++++++++++++++++++++++++++++++----- src/lib.rs | 2 +- src/splitter.rs | 6 +- 3 files changed, 217 insertions(+), 30 deletions(-) diff --git a/src/day04/main.rs b/src/day04/main.rs index b6a8442..c62bbb3 100644 --- a/src/day04/main.rs +++ b/src/day04/main.rs @@ -1,47 +1,234 @@ -use crate::splitter; -use alloc_counter::{count_alloc, AllocCounterSystem}; -use anyhow::Result; +use anyhow::{anyhow, bail, Context, Result}; +use std::collections::HashMap; use std::convert::TryFrom; -#[global_allocator] -static A: AllocCounterSystem = AllocCounterSystem; - static INPUT: &str = include_str!("../../input04"); fn main() -> Result<()> { - Ok() + let valid_passports = PassportScanner::from(INPUT).count(); + println!("found {} valid passports for part1", valid_passports); + let valid_passports = PassportScanner::from(INPUT) + .filter(|passport| passport.validate().is_ok()) + .count(); + println!("found {} valid passports for part2", valid_passports); + Ok(()) } struct PassportScanner<'a> { data: &'a str, } -impl<'a> PassportScanner { - fn new(data: &'a str) -> Self { +impl<'a> From<&'a str> for PassportScanner<'a> { + fn from(data: &'a str) -> Self { PassportScanner { data } } } -impl<'a> Iterator for PassportScanner { - type Item = Passport; +impl<'a> Iterator for PassportScanner<'a> { + type Item = Passport<'a>; + fn next(&mut self) -> Option { - let split_at = self.data.chars().enumerate().find_map(|i, c| { - if c == '\n' && self.data.get()? == '\n' { - return Some(i); + while self.data.len() > 0 { + let mut splitted = self.data.splitn(2, "\n\n"); + let data = splitted.next()?; + let rest = splitted.next().or(Some("")).unwrap(); + self.data = rest.trim(); + + match Passport::try_from(data) { + Ok(passport) => { + return Some(passport); + } + Err(_) => (), } - None - })?; - let (passport_data, rest) = self.data.split_at(split_at); - self.data = rest; + } + None } } -struct Passport { - BirthYear: u32, - IssueYear: u32, - ExpirationYear: u32, - Height: u32, - HairColor: [u8; 3], - PassportID: u32, - CountryID: Option, +struct Passport<'a> { + byr: &'a str, + iyr: &'a str, + eyr: &'a str, + hgt: &'a str, + hcl: &'a str, + ecl: &'a str, + pid: &'a str, + cid: Option<&'a str>, +} + +impl<'a> TryFrom<&'a str> for Passport<'a> { + type Error = anyhow::Error; + fn try_from(data: &'a str) -> Result { + let mut byr: Option<&str> = None; + let mut iyr: Option<&str> = None; + let mut eyr: Option<&str> = None; + let mut hgt: Option<&str> = None; + let mut hcl: Option<&str> = None; + let mut ecl: Option<&str> = None; + let mut pid: Option<&str> = None; + let mut cid: Option<&str> = None; + + for field in data.split_ascii_whitespace() { + let mut splitted = field.splitn(2, ':'); + let kind = splitted.next().unwrap(); + let value = splitted.next().unwrap(); + match kind { + "byr" => byr = Some(value), + "iyr" => iyr = Some(value), + "eyr" => eyr = Some(value), + "hgt" => hgt = Some(value), + "hcl" => hcl = Some(value), + "ecl" => ecl = Some(value), + "pid" => pid = Some(value), + "cid" => cid = Some(value), + _ => bail!("invalid kind: {}", kind), + } + } + let passport = Passport { + byr: byr.ok_or(anyhow!("byr missing"))?, + iyr: iyr.ok_or(anyhow!("iyr mssing"))?, + eyr: eyr.ok_or(anyhow!("eyr mssing"))?, + hgt: hgt.ok_or(anyhow!("hgt mssing"))?, + hcl: hcl.ok_or(anyhow!("hcl mssing"))?, + ecl: ecl.ok_or(anyhow!("ecl mssing"))?, + pid: pid.ok_or(anyhow!("pid mssing"))?, + cid, + }; + Ok(passport) + } +} + +impl<'a> Passport<'a> { + fn validate(&self) -> Result<()> { + self.validate_byr()?; + self.validate_iyr()?; + self.validate_eyr()?; + self.validate_hgt()?; + self.validate_hcl()?; + self.validate_ecl()?; + self.validate_pid()?; + Ok(()) + } + + fn validate_byr(&self) -> Result<()> { + match self.byr.parse::().context("byr is not a number")? { + 1920..=2002 => (), + value => bail!("byr out of range: {}", value), + }; + Ok(()) + } + + fn validate_iyr(&self) -> Result<()> { + match self.iyr.parse::().context("iyr is not numeric")? { + 2010..=2020 => (), + value => bail!("iyr out of range: {}", value), + }; + Ok(()) + } + + fn validate_eyr(&self) -> Result<()> { + match self.eyr.parse::().context("eyr is not numeric")? { + 2020..=2030 => (), + value => bail!("eyr out of range: {}", value), + }; + Ok(()) + } + + fn validate_hgt(&self) -> Result<()> { + let split_at = self.hgt.chars().take_while(|&c| c.is_numeric()).count(); + let (height, unit) = self.hgt.split_at(split_at); + let (min, max) = match unit { + "cm" => (150, 193), + "in" => (59, 76), + _ => bail!("hgt has unsupported unit: {}", unit), + }; + let height = height.parse::().context("hgt start is not numeric")?; + if height < min || height > max { + bail!("hgt out of range: {}", height) + } + Ok(()) + } + + fn validate_hcl(&self) -> Result<()> { + let (first, rest) = self.hcl.split_at(1); + if first != "#" { + bail!("hcl must start with #: \"{}\"", self.hcl) + } + if rest.len() != 6 { + bail!("hcl must have a 6 digit color") + } + if rest + .chars() + .map(|c| c.is_ascii_hexdigit()) + .fold(false, |acc, b| acc || !b) + { + bail!("hcl must have a 6 hexdigit color") + } + + Ok(()) + } + + fn validate_ecl(&self) -> Result<()> { + match self.ecl { + "amb" | "blu" | "brn" | "gry" | "grn" | "hzl" | "oth" => (), + value => bail!("ecl has invalid value: {}", value), + } + Ok(()) + } + + fn validate_pid(&self) -> Result<()> { + if self.pid.len() != 9 { + bail!("pid has invalid length"); + } + if self + .pid + .chars() + .map(|c| c.is_ascii_digit()) + .fold(false, |acc, b| acc || !b) + { + bail!("pid has non digit characters: {}", self.pid) + } + Ok(()) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn example() { + let passports = " +ecl:gry pid:860033327 eyr:2020 hcl:#fffffd +byr:1937 iyr:2017 cid:147 hgt:183cm + +iyr:2013 ecl:amb cid:350 eyr:2023 pid:028048884 +hcl:#cfa07d byr:1929 + +hcl:#ae17e1 iyr:2013 +eyr:2024 +ecl:brn pid:760753108 byr:1931 +hgt:179cm + +hcl:#cfa07d eyr:2025 pid:166559648 +iyr:2011 ecl:brn hgt:59in"; + + assert_eq!(PassportScanner::from(passports).count(), 2); + } + + #[test] + fn mistakes() { + assert!(PassportScanner::from(INPUT).count() > 124); + } + + #[test] + fn correct() { + assert_eq!(PassportScanner::from(INPUT).count(), 230); + assert_eq!( + PassportScanner::from(INPUT) + .filter(|passport| passport.validate().is_ok()) + .count(), + 156 + ); + } } diff --git a/src/lib.rs b/src/lib.rs index 4f01b5e..ea08085 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -pub(crate) mod splitter; +pub mod splitter; #[cfg(test)] mod tests { diff --git a/src/splitter.rs b/src/splitter.rs index 1c46899..22c47f2 100644 --- a/src/splitter.rs +++ b/src/splitter.rs @@ -1,4 +1,4 @@ -struct Splitter<'a> { +pub struct Splitter<'a> { inner: Option<&'a str>, } @@ -19,7 +19,7 @@ impl<'a> std::ops::Deref for Splitter<'a> { } impl<'a> Splitter<'a> { - fn get_before(&mut self, r: char) -> Option<&'a str> { + pub fn get_before(&mut self, r: char) -> Option<&'a str> { let inner = self.inner?; if let Some(pos) = inner.find(r) { let (before, after) = inner.split_at(pos); @@ -30,7 +30,7 @@ impl<'a> Splitter<'a> { Some(inner) } } - fn skip(&mut self) { + pub fn skip(&mut self) { self.inner = match self.inner { Some(inner) => Some(&inner[1..]), None => None,