day 4 solution

This commit is contained in:
Stephen Waits committed 2020-12-04 14:18:37 -08:00
1 parent 069173f036
commit a4558e6e80
7 files changed
+1328

No files matched your search

Generated
+1
View File
@@ -20,6 +20,7 @@ name = "aoc2020"
version = "0.1.0"
dependencies = [
"anyhow",
"lazy_static",
"regex",
]
+1
View File
@@ -8,4 +8,5 @@ edition = "2018"
[dependencies]
anyhow = "1.0"
lazy_static = "1.4.0"
regex = "1"
+1121
View File
File diff suppressed because it is too large. Load diff
+2
View File
@@ -0,0 +1,2 @@
250
158
+192
View File
@@ -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))
}
+2
View File
@@ -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(())
}
+9
View File
@@ -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},