This commit is contained in:
Stefan Schwarz 2020-12-11 22:43:16 +01:00
parent c3d7ac5d11
commit a7e4ce92b4
3 changed files with 217 additions and 30 deletions

View file

@ -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<Self::Item> {
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;
}
}
struct Passport {
BirthYear: u32,
IssueYear: u32,
ExpirationYear: u32,
Height: u32,
HairColor: [u8; 3],
PassportID: u32,
CountryID: Option<u32>,
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<Self, Self::Error> {
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::<u32>().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::<u32>().context("iyr is not numeric")? {
2010..=2020 => (),
value => bail!("iyr out of range: {}", value),
};
Ok(())
}
fn validate_eyr(&self) -> Result<()> {
match self.eyr.parse::<u32>().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::<u32>().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
);
}
}

View file

@ -1,4 +1,4 @@
pub(crate) mod splitter;
pub mod splitter;
#[cfg(test)]
mod tests {

View file

@ -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,