day04
This commit is contained in:
parent
c3d7ac5d11
commit
a7e4ce92b4
3 changed files with 217 additions and 30 deletions
|
@ -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;
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
pub(crate) mod splitter;
|
||||
pub mod splitter;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue