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