A solution to Day 4 ... that I don't love, but works.
This commit is contained in:
1133
inputs/day4.txt
Normal file
1133
inputs/day4.txt
Normal file
File diff suppressed because it is too large
Load Diff
13
inputs/day4_test.txt
Normal file
13
inputs/day4_test.txt
Normal file
@@ -0,0 +1,13 @@
|
||||
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
|
||||
13
inputs/day4_test_invalid.txt
Normal file
13
inputs/day4_test_invalid.txt
Normal file
@@ -0,0 +1,13 @@
|
||||
eyr:1972 cid:100
|
||||
hcl:#18171d ecl:amb hgt:170 pid:186cm iyr:2018 byr:1926
|
||||
|
||||
iyr:2019
|
||||
hcl:#602927 eyr:1967 hgt:170cm
|
||||
ecl:grn pid:012533040 byr:1946
|
||||
|
||||
hcl:dab227 iyr:2012
|
||||
ecl:brn hgt:182cm pid:021572410 eyr:2020 byr:1992 cid:277
|
||||
|
||||
hgt:59cm ecl:zzz
|
||||
eyr:2038 hcl:74454a iyr:2023
|
||||
pid:3556412378 byr:2007
|
||||
12
inputs/day4_test_valid.txt
Normal file
12
inputs/day4_test_valid.txt
Normal file
@@ -0,0 +1,12 @@
|
||||
pid:087499704 hgt:74in ecl:grn iyr:2012 eyr:2030 byr:1980
|
||||
hcl:#623a2f
|
||||
|
||||
eyr:2029 ecl:blu cid:129 byr:1989
|
||||
iyr:2014 pid:896056539 hcl:#a97842 hgt:165cm
|
||||
|
||||
hcl:#888785
|
||||
hgt:164cm byr:2001 iyr:2015 cid:88
|
||||
pid:545766238 ecl:hzl
|
||||
eyr:2022
|
||||
|
||||
iyr:2010 hgt:158cm hcl:#b6652a ecl:blu byr:1944 eyr:2021 pid:093154719
|
||||
171
src/bin/passport.rs
Normal file
171
src/bin/passport.rs
Normal file
@@ -0,0 +1,171 @@
|
||||
use advent2020::errors::{PassportParseError, TopLevelError};
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Passport {
|
||||
birth_year: Option<String>,
|
||||
issue_year: Option<String>,
|
||||
expiration_year: Option<String>,
|
||||
height: Option<String>,
|
||||
hair_color: Option<String>,
|
||||
eye_color: Option<String>,
|
||||
passport_id: Option<String>,
|
||||
country_id: Option<String>,
|
||||
}
|
||||
|
||||
const VALID_EYE_COLORS: &[&str] = &["amb", "blu", "brn", "gry", "grn", "hzl", "oth"];
|
||||
|
||||
macro_rules! valid_range {
|
||||
($str: expr, $low: expr, $high: expr) => {
|
||||
match u64::from_str($str) {
|
||||
Err(_) => return false,
|
||||
Ok(x) if x < $low || x > $high => return false,
|
||||
Ok(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Passport {
|
||||
fn new() -> Passport {
|
||||
Passport {
|
||||
birth_year: None,
|
||||
issue_year: None,
|
||||
expiration_year: None,
|
||||
height: None,
|
||||
hair_color: None,
|
||||
eye_color: None,
|
||||
passport_id: None,
|
||||
country_id: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn injest_data(&mut self, line: &str) -> Result<(), PassportParseError> {
|
||||
for item in line.split_whitespace() {
|
||||
let parts: Vec<&str> = item.split(':').collect();
|
||||
|
||||
if parts.len() != 2 {
|
||||
return Err(PassportParseError::InvalidChunk(item.to_string()));
|
||||
}
|
||||
|
||||
match parts[0] {
|
||||
"byr" => self.birth_year = Some(parts[1].to_string()),
|
||||
"iyr" => self.issue_year = Some(parts[1].to_string()),
|
||||
"eyr" => self.expiration_year = Some(parts[1].to_string()),
|
||||
"hgt" => self.height = Some(parts[1].to_string()),
|
||||
"hcl" => self.hair_color = Some(parts[1].to_string()),
|
||||
"ecl" => self.eye_color = Some(parts[1].to_string()),
|
||||
"pid" => self.passport_id = Some(parts[1].to_string()),
|
||||
"cid" => self.country_id = Some(parts[1].to_string()),
|
||||
unknown => return Err(PassportParseError::InvalidField(unknown.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_basically_valid(&self) -> bool {
|
||||
self.birth_year.is_some() &&
|
||||
self.issue_year.is_some() &&
|
||||
self.expiration_year.is_some() &&
|
||||
self.height.is_some() &&
|
||||
self.hair_color.is_some() &&
|
||||
self.eye_color.is_some() &&
|
||||
self.passport_id.is_some()
|
||||
}
|
||||
|
||||
fn is_really_valid(&self) -> bool {
|
||||
// check the years
|
||||
if !valid_year_range(&self.birth_year, 1920, 2002) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if !valid_year_range(&self.issue_year, 2010, 2020) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if !valid_year_range(&self.expiration_year, 2020, 2030) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// check the height
|
||||
match self.height {
|
||||
None => return false,
|
||||
Some(ref x) => {
|
||||
if let Some(idx) = x.rfind("cm") {
|
||||
valid_range!(&x[0..idx], 150, 193);
|
||||
} else if let Some(idx) = x.rfind("in") {
|
||||
valid_range!(&x[0..idx], 59, 76);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// check the hair color
|
||||
match self.hair_color {
|
||||
None => return false,
|
||||
Some(ref x) if x.len() != 7 => return false,
|
||||
Some(ref x) => {
|
||||
if !x.starts_with('#') || x[1..].chars().any(|x| !x.is_digit(16)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check the eye color
|
||||
match self.eye_color {
|
||||
None => return false,
|
||||
Some(ref x) if VALID_EYE_COLORS.contains(&x.as_str()) => {},
|
||||
Some(_) => return false,
|
||||
}
|
||||
|
||||
// check the passport number
|
||||
match self.passport_id {
|
||||
None => false,
|
||||
Some(ref x) => x.len() == 9 && x.chars().all(|x| x.is_digit(10)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn valid_year_range(field: &Option<String>, start: u64, end: u64) -> bool {
|
||||
match field {
|
||||
None => return false,
|
||||
Some(x) => valid_range!(x, start, end),
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn real_main() -> Result<(), TopLevelError> {
|
||||
let mut passports = Vec::new();
|
||||
|
||||
for argument in env::args().skip(1) {
|
||||
let contents = fs::read_to_string(argument)?;
|
||||
let mut current_passport = Passport::new();
|
||||
|
||||
for line in contents.lines() {
|
||||
if line == "" {
|
||||
passports.push(current_passport);
|
||||
current_passport = Passport::new();
|
||||
} else {
|
||||
current_passport.injest_data(line)?;
|
||||
}
|
||||
}
|
||||
passports.push(current_passport);
|
||||
}
|
||||
|
||||
let valid_passports: Vec<&Passport> = passports.iter().filter(|x| x.is_basically_valid()).collect();
|
||||
println!("There are {} *basically* valid passports.", valid_passports.len());
|
||||
println!(" ... {} are really valid", passports.iter().filter(|x| x.is_really_valid()).count());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
match real_main() {
|
||||
Err(e) => eprintln!("ERROR: {}", e),
|
||||
Ok(_) => {}
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ pub enum TopLevelError {
|
||||
NoInputFound,
|
||||
NoSolutionFound,
|
||||
UnknownError,
|
||||
PassportParseError(PassportParseError),
|
||||
}
|
||||
|
||||
impl fmt::Display for TopLevelError {
|
||||
@@ -26,6 +27,7 @@ impl fmt::Display for TopLevelError {
|
||||
TopLevelError::IOError(e) => write!(f, "IO error: {}", e),
|
||||
TopLevelError::NoInputFound => write!(f, "No valid inputs found"),
|
||||
TopLevelError::NoSolutionFound => write!(f, "No solution found."),
|
||||
TopLevelError::PassportParseError(p) => write!(f, "Error parsing passport: {}", p),
|
||||
TopLevelError::UnknownError => {
|
||||
write!(f, "Unknown error occurred; this shouldn't be possible.")
|
||||
}
|
||||
@@ -69,3 +71,19 @@ impl fmt::Display for MapParseError {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum PassportParseError {
|
||||
InvalidChunk(String),
|
||||
InvalidField(String),
|
||||
}
|
||||
|
||||
impl fmt::Display for PassportParseError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
PassportParseError::InvalidChunk(s) => write!(f, "Invalid chunk in passport line: {}", s),
|
||||
PassportParseError::InvalidField(s) => write!(f, "Invalid field in passport: {}", s),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
convert_error!(PassportParseError, TopLevelError, PassportParseError);
|
||||
Reference in New Issue
Block a user