parent
b107f62c09
commit
bc38a6ff1b
@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "day4"
|
||||
version = "0.1.0"
|
||||
authors = ["Avril <flanchan@cumallover.me>"]
|
||||
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"
|
@ -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}
|
@ -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<usize>),
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
}
|
@ -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<String, io::Error>
|
||||
{
|
||||
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<dyn std::error::Error>> {
|
||||
|
||||
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(())
|
||||
}
|
@ -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<Self, Self::Error>
|
||||
{
|
||||
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<const LOW: usize, const HIGH: usize>
|
||||
{
|
||||
value:u16
|
||||
}
|
||||
|
||||
impl<const LOW: usize, const HIGH:usize> BoundedU16<LOW, HIGH>
|
||||
{
|
||||
pub const fn bound() -> std::ops::RangeInclusive<usize>
|
||||
{
|
||||
LOW..=HIGH
|
||||
}
|
||||
pub const fn contains(value: u16) -> bool
|
||||
{
|
||||
let value = value as usize;
|
||||
value >= LOW && value <= HIGH
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl<const LOW: usize, const HIGH:usize> str::FromStr for BoundedU16<LOW, HIGH>
|
||||
{
|
||||
type Err = BoundedU16FromStrError;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
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<Self, Self::Err> {
|
||||
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<Self, Self::Err> {
|
||||
const HEX: &[u8] = b"0123456789abcdef";
|
||||
lazy_static::lazy_static! {
|
||||
static ref VALID_CHARS: smallmap::Map<u8, ()> = {
|
||||
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<Self, Self::Err> {
|
||||
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<Self, Self::Err> {
|
||||
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<PassportInfo<'a>> for Passport
|
||||
{
|
||||
type Error = PassportParseError;
|
||||
|
||||
fn try_from(from: PassportInfo<'a>) -> Result<Self, Self::Error>
|
||||
{
|
||||
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(|_| ()),
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in new issue