From bc38a6ff1bea762e67c04ee02d95e009c3445ea1 Mon Sep 17 00:00:00 2001 From: Avril Date: Fri, 4 Dec 2020 13:38:31 +0000 Subject: [PATCH] day4 --- day4/Cargo.toml | 15 +++ day4/Makefile | 21 ++++ day4/src/error.rs | 156 +++++++++++++++++++++++++++++ day4/src/main.rs | 63 ++++++++++++ day4/src/passport.rs | 226 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 481 insertions(+) create mode 100644 day4/Cargo.toml create mode 100644 day4/Makefile create mode 100644 day4/src/error.rs create mode 100644 day4/src/main.rs create mode 100644 day4/src/passport.rs diff --git a/day4/Cargo.toml b/day4/Cargo.toml new file mode 100644 index 0000000..20fe27e --- /dev/null +++ b/day4/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "day4" +version = "0.1.0" +authors = ["Avril "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[features] +test= [] +part2 = [] + +[dependencies] +lazy_static = "1.4.0" +smallmap = "1.2.0" diff --git a/day4/Makefile b/day4/Makefile new file mode 100644 index 0000000..5ffbcb6 --- /dev/null +++ b/day4/Makefile @@ -0,0 +1,21 @@ + +CARGO_FEATURE_FLAGS?= + +.PHONY: all +.NOTPARALLEL: all +all: + $(MAKE) part1 + $(MAKE) part2 + +.NOTPARALLEL: part1 +part1: $(wildcard src/*.rs) + cargo build --release $(addprefix --features ,$(CARGO_FEATURE_FLAGS)) + mv -f target/release/day4 $@ + +.NOTPARALLEL: part2 +part2: $(wildcard src/*.rs) + cargo build --release --features $@ $(addprefix --features ,$(CARGO_FEATURE_FLAGS)) + mv -f target/release/day4 $@ + +clean: + rm -f part{1,2} diff --git a/day4/src/error.rs b/day4/src/error.rs new file mode 100644 index 0000000..1770b6a --- /dev/null +++ b/day4/src/error.rs @@ -0,0 +1,156 @@ +use std::{ + fmt, error, +}; + +#[derive(Debug)] +pub struct InfoParseError; + +impl error::Error for InfoParseError{} +impl fmt::Display for InfoParseError +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + write!(f, "failed to parse passport info structure") + } +} + +// --- + +#[derive(Debug)] +pub enum BoundedU16FromStrError +{ + Parse, + Bound(std::ops::RangeInclusive), +} + +impl error::Error for BoundedU16FromStrError{} +impl fmt::Display for BoundedU16FromStrError +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + match self { + Self::Parse => write!(f, "failed to parse u8"), + Self::Bound(b) => write!(f, "value was not in bounds {:?}", b), + } + } +} + +#[derive(Debug)] +pub enum HeightFromStrError +{ + Parse(BoundedU16FromStrError), + Unit, +} +impl error::Error for HeightFromStrError +{ + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + match &self + { + Self::Parse(s) => Some(s), + _ => None, + } + } +} +impl fmt::Display for HeightFromStrError +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + match self { + Self::Unit => write!(f, "invalid/unknown height unit"), + Self::Parse(_) => write!(f, "number was not in bounds for unit"), + } + } +} + + + +#[derive(Debug)] +pub struct HairColourFromStrError; + +impl error::Error for HairColourFromStrError{} +impl fmt::Display for HairColourFromStrError +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + write!(f, "invalid hair colour") + } +} + + +#[derive(Debug)] +pub struct EyeColourFromStrError; + +impl error::Error for EyeColourFromStrError{} +impl fmt::Display for EyeColourFromStrError +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + write!(f, "invalid eye colour") + } +} + + +#[derive(Debug)] +pub struct PassportIdFromStrError; + +impl error::Error for PassportIdFromStrError{} +impl fmt::Display for PassportIdFromStrError +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + write!(f, "invalid passport ID") + } +} + + +// -- + +#[derive(Debug)] +pub enum PassportParseError +{ + PassportID(PassportIdFromStrError), + EyeColour(EyeColourFromStrError), + HairColour(HairColourFromStrError), + Height(HeightFromStrError), + ExprYear(BoundedU16FromStrError), + IssueYear(BoundedU16FromStrError), + BirthYear(BoundedU16FromStrError), +} + +impl error::Error for PassportParseError +{ + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + use PassportParseError::*; + #[allow(non_snake_case)] + Some(match &self { + PassportID(passportIdFromStrError) => passportIdFromStrError, + EyeColour(eyeColourFromStrError) => eyeColourFromStrError, + HairColour(hairColourFromStrError) => hairColourFromStrError, + Height(heightFromStrError) => heightFromStrError, + ExprYear(boundedU8FromStrError) => boundedU8FromStrError, + IssueYear(boundedU8FromStrError) => boundedU8FromStrError, + BirthYear(boundedU8FromStrError) => boundedU8FromStrError, + }) + } +} + +impl fmt::Display for PassportParseError +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + use PassportParseError::*; + write!(f, "invalid ")?; + match self { + PassportID(_) => write!(f, "passport id"), + EyeColour(_) => write!(f, "eye colour"), + HairColour(_) =>write!(f, "hair colour"), + Height(_) => write!(f, "height"), + ExprYear(_) => write!(f, "expire year"), + IssueYear(_) => write!(f, "issue year"), + BirthYear(_) => write!(f, "birth year"), + }?; + write!(f, ": ")?; + use error::Error; + write!(f, "{}", self.source().unwrap()) + } +} diff --git a/day4/src/main.rs b/day4/src/main.rs new file mode 100644 index 0000000..73dcebc --- /dev/null +++ b/day4/src/main.rs @@ -0,0 +1,63 @@ +#![feature(str_split_once)] +#![feature(min_const_generics)] + +use std::{ + fs, + io::{ + self, + BufReader, + BufRead, + }, + convert::TryFrom, +}; + +#[cfg(not(feature="test"))] const INPUT: &str = "input"; +#[cfg(feature="test")] const INPUT: &str = "input-test"; + +mod error; +mod passport; + +fn parse_single(from: &mut (impl BufRead + ?Sized)) -> Result +{ + let mut string = String::new(); + loop { + let n = string.len(); + let r = from.read_line(&mut string)?; + let nw = &string[n..(n+r)]; + if nw.trim().len()==0 { + break; + } + } + Ok(string) + +} + +fn main() -> Result<(), Box> { + + let mut input = BufReader::new(fs::OpenOptions::new() + .read(true) + .open(INPUT)?); + + let mut valid = 0; + loop { + let line = parse_single(&mut input)?; + if line.trim().len() > 0 { + #[allow(unused_variables)] + if let Ok(ppinf) = passport::PassportInfo::try_from(&line[..]) { + #[cfg(not(feature="part2"))] { valid+=1; } + #[cfg(feature="part2")] { + match passport::Passport::try_from(ppinf) { + Err(err) => { + eprintln!("Error: {}", err); + }, + Ok(_) => valid+=1, + } + } + } + } else { + break; + } + } + println!("{}", valid); + Ok(()) +} diff --git a/day4/src/passport.rs b/day4/src/passport.rs new file mode 100644 index 0000000..1a2e183 --- /dev/null +++ b/day4/src/passport.rs @@ -0,0 +1,226 @@ +use std::{ + convert::TryFrom, + collections::HashMap, + str, +}; +use super::error::*; + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct PassportInfo<'a> +{ + birth_year: &'a str, + issue_year: &'a str, + expr_year: &'a str, + height: &'a str, + hair_colour: &'a str, + eye_colour: &'a str, + pp_id: &'a str, + cn_id: Option<&'a str>, +} + +impl<'a> TryFrom<&'a str> for PassportInfo<'a> +{ + type Error = InfoParseError; + + fn try_from(from: &'a str) -> Result + { + let mut items = HashMap::new(); + for item in from.split_whitespace() { + if let Some((k, v)) = item.split_once(':') { + if items.insert(k, v).is_some() { + return Err(InfoParseError); + } + } + } + macro_rules! parse { + (? $l:literal) => { + items.get($l).map(|&x| x) + }; + ($l:literal) => { + parse!(? $l).ok_or(InfoParseError)?; + }; + } + + Ok(Self{ + birth_year: parse!("byr"), + issue_year: parse!("iyr"), + expr_year: parse!("eyr"), + height: parse!("hgt"), + hair_colour: parse!("hcl"), + eye_colour: parse!("ecl"), + pp_id: parse!("pid"), + cn_id: parse!(? "cid"), + }) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct BoundedU16 +{ + value:u16 +} + +impl BoundedU16 +{ + pub const fn bound() -> std::ops::RangeInclusive + { + LOW..=HIGH + } + pub const fn contains(value: u16) -> bool + { + let value = value as usize; + value >= LOW && value <= HIGH + } +} + + +impl str::FromStr for BoundedU16 +{ + type Err = BoundedU16FromStrError; + fn from_str(s: &str) -> Result { + let v: u16 = s.parse().map_err(|_| BoundedU16FromStrError::Parse)?; + //println!("{:?}: {}", Self::bound(), v); + if Self::contains(v) { + Ok(Self{value: v}) + } else { + Err(BoundedU16FromStrError::Bound(Self::bound())) + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum Height +{ + Cm(BoundedU16<150, 193>), + In(BoundedU16<56, 76>), +} + +impl str::FromStr for Height +{ + type Err = HeightFromStrError; + fn from_str(s: &str) -> Result { + if s.ends_with("cm") + { + let num = &s[..(s.len()-2)]; + Ok(Self::Cm(num.parse().map_err(HeightFromStrError::Parse)?)) + } else if s.ends_with("in") + { + let num = &s[..(s.len()-2)]; + Ok(Self::In(num.parse().map_err(HeightFromStrError::Parse)?)) + } else { + Err(HeightFromStrError::Unit) + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct HairColour(String); + +impl str::FromStr for HairColour +{ + type Err = HairColourFromStrError; + fn from_str(s: &str) -> Result { + const HEX: &[u8] = b"0123456789abcdef"; + lazy_static::lazy_static! { + static ref VALID_CHARS: smallmap::Map = { + let mut map = smallmap::Map::new(); + for &b in HEX.iter() + { + map.insert(b, ()); + } + map + }; + } + + if s.starts_with("#") && s.len() == 7 { + let s = &s[1..]; + let bytes = s.as_bytes(); + for b in bytes.iter() + { + if !VALID_CHARS.contains_key(b) { + return Err(HairColourFromStrError); + } + } + Ok(Self(s.to_owned())) + } else { + Err(HairColourFromStrError) + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, Copy)] +#[repr(u8)] +pub enum EyeColour +{ + Amb, Blu, Brn, Gry, Grn, Hzl, Oth, +} + +impl str::FromStr for EyeColour +{ + type Err = EyeColourFromStrError; + fn from_str(s: &str) -> Result { + Ok(match s.trim() { + "amb" => Self::Amb, + "blu" => Self::Blu, + "brn" => Self::Brn, + "gry" => Self::Gry, + "grn" => Self::Grn, + "hzl" => Self::Hzl, + "oth" => Self::Oth, + _ => return Err(EyeColourFromStrError), + }) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct PassportId +{ + id: u64, +} + +impl str::FromStr for PassportId +{ + type Err = PassportIdFromStrError; + fn from_str(s: &str) -> Result { + if s.len() == 9 { + Ok(Self{ + id: s.parse().map_err(|_| PassportIdFromStrError)?, + }) + } else { + Err(PassportIdFromStrError) + } + } +} + + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct Passport +{ + birth_year: BoundedU16<1920, 2002>, + issue_year: BoundedU16<2010, 2020>, + expr_year: BoundedU16<2020, 2030>, + height: Height, + hair_colour: HairColour, + eye_colour: EyeColour, + pp_id: PassportId, + cn_id: Option<()>, +} + +impl<'a> TryFrom> for Passport +{ + type Error = PassportParseError; + + fn try_from(from: PassportInfo<'a>) -> Result + { + Ok(Self{ + birth_year: from.birth_year.parse().map_err(PassportParseError::BirthYear)?, + issue_year: from.issue_year.parse().map_err(PassportParseError::IssueYear)?, + expr_year: from.expr_year.parse().map_err(PassportParseError::ExprYear)?, + height: from.height.parse().map_err(PassportParseError::Height)?, + hair_colour: from.hair_colour.parse().map_err(PassportParseError::HairColour)?, + eye_colour: from.eye_colour.parse().map_err(PassportParseError::EyeColour)?, + pp_id: from.pp_id.parse().map_err(PassportParseError::PassportID)?, + cn_id: from.cn_id.map(|_| ()), + }) + } +}