day 4 solution
This commit is contained in:
7 files changed
+1328
No files matched your search
Generated
+1
@@ -20,6 +20,7 @@ name = "aoc2020"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
"regex",
|
||||
]
|
||||
|
||||
|
||||
@@ -8,4 +8,5 @@ edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
lazy_static = "1.4.0"
|
||||
regex = "1"
|
||||
+1121
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,2 @@
|
||||
250
|
||||
158
|
||||
+192
@@ -0,0 +1,192 @@
|
||||
// ==== day 4
|
||||
|
||||
use crate::utils;
|
||||
use anyhow::Result;
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Default)]
|
||||
struct Passport {
|
||||
byr: Option<String>,
|
||||
iyr: Option<String>,
|
||||
eyr: Option<String>,
|
||||
hgt: Option<String>,
|
||||
hcl: Option<String>,
|
||||
ecl: Option<String>,
|
||||
pid: Option<String>,
|
||||
cid: Option<String>,
|
||||
}
|
||||
|
||||
impl Passport {
|
||||
// true if all the required fields (valid or otherwise) are present (i.e. all but "cid")
|
||||
fn required_fields_present(&self) -> bool {
|
||||
self.byr.is_some()
|
||||
&& self.iyr.is_some()
|
||||
&& self.eyr.is_some()
|
||||
&& self.hgt.is_some()
|
||||
&& self.hcl.is_some()
|
||||
&& self.ecl.is_some()
|
||||
&& self.pid.is_some()
|
||||
}
|
||||
|
||||
// true if all the required fields are both present and valid
|
||||
fn is_valid(&self) -> bool {
|
||||
self.required_fields_present()
|
||||
&& self.is_byr_valid()
|
||||
&& self.is_iyr_valid()
|
||||
&& self.is_eyr_valid()
|
||||
&& self.is_hgt_valid()
|
||||
&& self.is_hcl_valid()
|
||||
&& self.is_ecl_valid()
|
||||
&& self.is_pid_valid()
|
||||
&& self.is_cid_valid()
|
||||
}
|
||||
|
||||
// byr (Birth Year) - four digits; at least 1920 and at most 2002.
|
||||
fn is_byr_valid(&self) -> bool {
|
||||
if let Some(s) = &self.byr {
|
||||
utils::parse_usize_in_range(&s, 1920, 2002).is_some()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
// iyr (Issue Year) - four digits; at least 2010 and at most 2020.
|
||||
fn is_iyr_valid(&self) -> bool {
|
||||
if let Some(s) = &self.iyr {
|
||||
utils::parse_usize_in_range(&s, 2010, 2020).is_some()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
// eyr (Expiration Year) - four digits; at least 2020 and at most 2030.
|
||||
fn is_eyr_valid(&self) -> bool {
|
||||
if let Some(s) = &self.eyr {
|
||||
utils::parse_usize_in_range(&s, 2020, 2030).is_some()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
// hgt (Height) - a number followed by either cm or in:
|
||||
// If cm, the number must be at least 150 and at most 193.
|
||||
// If in, the number must be at least 59 and at most 76.
|
||||
fn is_hgt_valid(&self) -> bool {
|
||||
lazy_static! {
|
||||
static ref HGT_RE: regex::Regex =
|
||||
Regex::new(r"^[0-9]+(cm|in)$").expect("regex compilation failure");
|
||||
}
|
||||
|
||||
if let Some(s) = &self.hgt {
|
||||
if HGT_RE.is_match(s) {
|
||||
if s.ends_with("cm") {
|
||||
return utils::parse_usize_in_range(&s[..s.len() - 2], 150, 193).is_some();
|
||||
} else if s.ends_with("in") {
|
||||
return utils::parse_usize_in_range(&s[..s.len() - 2], 59, 76).is_some();
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
// hcl (Hair Color) - a # followed by exactly six characters 0-9 or a-f.
|
||||
fn is_hcl_valid(&self) -> bool {
|
||||
lazy_static! {
|
||||
static ref HCL_RE: regex::Regex =
|
||||
Regex::new(r"^#[0-9a-f]{6}$").expect("regex compilation failure");
|
||||
}
|
||||
|
||||
if let Some(s) = &self.hcl {
|
||||
HCL_RE.is_match(s)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
// ecl (Eye Color) - exactly one of: amb blu brn gry grn hzl oth.
|
||||
fn is_ecl_valid(&self) -> bool {
|
||||
lazy_static! {
|
||||
static ref ECL_RE: regex::Regex =
|
||||
Regex::new(r"^(amb|blu|brn|gry|grn|hzl|oth)$").expect("regex compilation failure");
|
||||
}
|
||||
|
||||
if let Some(s) = &self.ecl {
|
||||
ECL_RE.is_match(s)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
// pid (Passport ID) - a nine-digit number, including leading zeroes.
|
||||
fn is_pid_valid(&self) -> bool {
|
||||
lazy_static! {
|
||||
static ref PID_RE: regex::Regex =
|
||||
Regex::new(r"^[0-9]{9}$").expect("regex compilation failure");
|
||||
}
|
||||
|
||||
if let Some(s) = &self.pid {
|
||||
PID_RE.is_match(s)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
// cid (Country ID) - ignored, missing or not.
|
||||
fn is_cid_valid(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Passport {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn from_str(s: &str) -> std::result::Result<Self, anyhow::Error> {
|
||||
lazy_static! {
|
||||
static ref PASSPORT_RE: regex::Regex =
|
||||
Regex::new(r"(byr|iyr|eyr|hgt|hcl|ecl|pid|cid):(\S*)")
|
||||
.expect("regex compilation failure");
|
||||
}
|
||||
|
||||
let mut ret: Self = Default::default();
|
||||
s.split_whitespace().for_each(|s| {
|
||||
PASSPORT_RE.captures_iter(&s).for_each(|cap| match &cap[1] {
|
||||
"byr" => ret.byr = Some(cap[2].to_string()),
|
||||
"iyr" => ret.iyr = Some(cap[2].to_string()),
|
||||
"eyr" => ret.eyr = Some(cap[2].to_string()),
|
||||
"hgt" => ret.hgt = Some(cap[2].to_string()),
|
||||
"hcl" => ret.hcl = Some(cap[2].to_string()),
|
||||
"ecl" => ret.ecl = Some(cap[2].to_string()),
|
||||
"cid" => ret.cid = Some(cap[2].to_string()),
|
||||
"pid" => ret.pid = Some(cap[2].to_string()),
|
||||
_ => panic!("warning: unknown attribute: {}:{}", &cap[1], &cap[2]),
|
||||
});
|
||||
});
|
||||
Ok(ret)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run() -> Result<(usize, usize)> {
|
||||
let data = utils::read_file("data/input-04.txt")?;
|
||||
let answers = utils::read_i64s("data/output-04.txt")?;
|
||||
|
||||
// parse into an array of Passport{}
|
||||
let passports: Vec<Passport> = data
|
||||
.split("\n\n")
|
||||
.map(|s| Passport::from_str(&s).expect("error parsing passport"))
|
||||
.collect();
|
||||
|
||||
// count passports with all req'd fields
|
||||
let p1 = passports
|
||||
.iter()
|
||||
.filter(|p| p.required_fields_present())
|
||||
.count();
|
||||
assert_eq!(p1, answers[0] as usize);
|
||||
|
||||
// count passports with all req'd and validated fields
|
||||
let p2 = passports.iter().filter(|p| p.is_valid()).count();
|
||||
assert_eq!(p2, answers[1] as usize);
|
||||
|
||||
Ok((p1, p2))
|
||||
}
|
||||
@@ -3,6 +3,7 @@ use anyhow::Result;
|
||||
mod day01;
|
||||
mod day02;
|
||||
mod day03;
|
||||
mod day04;
|
||||
mod utils;
|
||||
|
||||
// TODO
|
||||
@@ -16,5 +17,6 @@ fn main() -> Result<()> {
|
||||
println!("Day 01: {:?}", day01::run()?);
|
||||
println!("Day 02: {:?}", day02::run()?);
|
||||
println!("Day 03: {:?}", day03::run()?);
|
||||
println!("Day 04: {:?}", day04::run()?);
|
||||
Ok(())
|
||||
}
|
||||
@@ -14,6 +14,15 @@ pub fn read_file(filename: &str) -> Result<String> {
|
||||
Ok(fs::read_to_string(filename)?)
|
||||
}
|
||||
|
||||
pub fn parse_usize_in_range(s: &str, min: usize, max: usize) -> Option<usize> {
|
||||
let x: usize = s.parse().expect("int parse error");
|
||||
if min <= x && x <= max {
|
||||
Some(x)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// Lexicographic combinations.
|
||||
//
|
||||
// This algorithm generates all t-combinations of n numbers {0, 1, ..., n-1},
|
||||
|
||||
Reference in new issue
Block a user