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"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"lazy_static",
|
||||||
"regex",
|
"regex",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -8,4 +8,5 @@ edition = "2018"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
|
lazy_static = "1.4.0"
|
||||||
regex = "1"
|
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 day01;
|
||||||
mod day02;
|
mod day02;
|
||||||
mod day03;
|
mod day03;
|
||||||
|
mod day04;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
@@ -16,5 +17,6 @@ fn main() -> Result<()> {
|
|||||||
println!("Day 01: {:?}", day01::run()?);
|
println!("Day 01: {:?}", day01::run()?);
|
||||||
println!("Day 02: {:?}", day02::run()?);
|
println!("Day 02: {:?}", day02::run()?);
|
||||||
println!("Day 03: {:?}", day03::run()?);
|
println!("Day 03: {:?}", day03::run()?);
|
||||||
|
println!("Day 04: {:?}", day04::run()?);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -14,6 +14,15 @@ pub fn read_file(filename: &str) -> Result<String> {
|
|||||||
Ok(fs::read_to_string(filename)?)
|
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.
|
// Lexicographic combinations.
|
||||||
//
|
//
|
||||||
// This algorithm generates all t-combinations of n numbers {0, 1, ..., n-1},
|
// This algorithm generates all t-combinations of n numbers {0, 1, ..., n-1},
|
||||||
|
|||||||
Reference in new issue
Block a user