Completed day 4

This commit is contained in:
Tyler Hallada 2020-12-04 17:23:50 -05:00
parent d57f26c617
commit 1cdc019c79
6 changed files with 1470 additions and 0 deletions

14
day04/Cargo.lock generated Normal file
View File

@ -0,0 +1,14 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "anyhow"
version = "1.0.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf8dcb5b4bbaa28653b647d8c77bd4ed40183b48882e130c1f1ffb73de069fd7"
[[package]]
name = "day04"
version = "0.1.0"
dependencies = [
"anyhow",
]

10
day04/Cargo.toml Normal file
View File

@ -0,0 +1,10 @@
[package]
name = "day04"
version = "0.1.0"
authors = ["Tyler Hallada <tyler@hallada.net>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow = "1.0"

1146
day04/input/input.txt Executable file

File diff suppressed because it is too large Load Diff

13
day04/input/test1.txt Normal file
View 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

26
day04/input/test2.txt Normal file
View File

@ -0,0 +1,26 @@
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
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

261
day04/src/main.rs Normal file
View File

@ -0,0 +1,261 @@
use anyhow::{anyhow, Context, Error, Result};
use std::fmt;
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::str::FromStr;
const INPUT: &str = "input/input.txt";
#[derive(Debug, PartialEq, Default)]
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>,
}
impl fmt::Display for Passport {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut passport_string = String::new();
if let Some(birth_year) = &self.birth_year {
passport_string.push_str(&format!("byr:{} ", birth_year));
}
if let Some(issue_year) = &self.issue_year {
passport_string.push_str(&format!("iyr:{} ", issue_year));
}
if let Some(expiration_year) = &self.expiration_year {
passport_string.push_str(&format!("eyr:{} ", expiration_year));
}
if let Some(height) = &self.height {
passport_string.push_str(&format!("hgt:{} ", height));
}
if let Some(hair_color) = &self.hair_color {
passport_string.push_str(&format!("hcl:{} ", hair_color));
}
if let Some(eye_color) = &self.eye_color {
passport_string.push_str(&format!("ecl:{} ", eye_color));
}
if let Some(passport_id) = &self.passport_id {
passport_string.push_str(&format!("pid:{} ", passport_id));
}
if let Some(country_id) = &self.country_id {
passport_string.push_str(&format!("cid:{} ", country_id));
}
writeln!(f, "{}", passport_string.trim_end())
}
}
impl FromStr for Passport {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
let mut passport = Self::default();
let fields = s.split_whitespace();
for field in fields {
let mut field = field.split(":");
let name = field.next().context("Failed to parse field name")?;
let value = field.next().context("Failed to parse field value")?;
match name {
"byr" => passport.birth_year = Some(value.to_owned()),
"iyr" => passport.issue_year = Some(value.to_owned()),
"eyr" => passport.expiration_year = Some(value.to_owned()),
"hgt" => passport.height = Some(value.to_owned()),
"hcl" => passport.hair_color = Some(value.to_owned()),
"ecl" => passport.eye_color = Some(value.to_owned()),
"pid" => passport.passport_id = Some(value.to_owned()),
"cid" => passport.country_id = Some(value.to_owned()),
_ => return Err(anyhow!("Unrecognized field name: {}", name)),
}
}
Ok(passport)
}
}
impl Passport {
fn from_reader<R: BufRead>(reader: R) -> Result<Vec<Self>> {
let mut passport_buf = String::new();
let mut passports: Vec<Passport> = Vec::new();
for line in reader.lines() {
let line = line?;
if line.trim().is_empty() && !passport_buf.is_empty() {
passports.push(passport_buf.parse()?);
passport_buf.truncate(0);
} else {
passport_buf.push_str(" ");
passport_buf.push_str(&line);
}
}
if !passport_buf.is_empty() {
passports.push(passport_buf.parse()?);
}
Ok(passports)
}
fn validate(&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 validate_birth_year(&self) -> bool {
match &self.birth_year {
Some(birth_year) => {
matches!(birth_year.parse::<u32>(), Ok(year) if (1920..=2002).contains(&year))
}
None => false,
}
}
fn validate_issue_year(&self) -> bool {
match &self.issue_year {
Some(issue_year) => {
matches!(issue_year.parse::<u32>(), Ok(year) if (2010..=2020).contains(&year))
}
None => false,
}
}
fn validate_expiration_year(&self) -> bool {
match &self.expiration_year {
Some(expiration_year) => {
matches!(expiration_year.parse::<u32>(), Ok(year) if (2020..=2030).contains(&year))
}
None => false,
}
}
fn validate_height(&self) -> bool {
match &self.height {
Some(height) => {
if let Some(height) = height.strip_suffix("cm") {
matches!(height.parse::<u32>(), Ok(height) if (150..=193).contains(&height))
} else if let Some(height) = height.strip_suffix("in") {
matches!(height.parse::<u32>(), Ok(height) if (59..=76).contains(&height))
} else {
false
}
}
None => false,
}
}
fn validate_hair_color(&self) -> bool {
match &self.hair_color {
Some(hair_color) => {
hair_color.starts_with("#")
&& hair_color.chars().skip(1).all(|c| c.is_ascii_hexdigit())
}
None => false,
}
}
fn validate_eye_color(&self) -> bool {
match &self.eye_color {
Some(eye_color) => {
matches!(
eye_color.as_str(),
"amb" | "blu" | "brn" | "gry" | "grn" | "hzl" | "oth"
)
}
None => false,
}
}
fn validate_passport_id(&self) -> bool {
match &self.passport_id {
Some(passport_id) => {
passport_id.len() == 9 && passport_id.chars().all(|c| c.is_ascii_digit())
}
None => false,
}
}
fn strict_validate(&self) -> bool {
self.validate_birth_year()
&& self.validate_issue_year()
&& self.validate_expiration_year()
&& self.validate_height()
&& self.validate_hair_color()
&& self.validate_eye_color()
&& self.validate_passport_id()
}
}
fn solve_part1(input_path: &str) -> Result<usize> {
let file = File::open(input_path)?;
let reader = BufReader::new(file);
let passports = Passport::from_reader(reader)?;
Ok(passports
.iter()
.filter(|passport| passport.validate())
.count())
}
fn solve_part2(input_path: &str) -> Result<usize> {
let file = File::open(input_path)?;
let reader = BufReader::new(file);
let passports = Passport::from_reader(reader)?;
Ok(passports
.iter()
.filter(|passport| passport.strict_validate())
.count())
}
fn main() {
println!("Part 1: {}", solve_part1(INPUT).unwrap());
println!("Part 2: {}", solve_part2(INPUT).unwrap());
}
#[cfg(test)]
mod tests {
use super::*;
const TEST_INPUT1: &str = "input/test1.txt";
const TEST_INPUT2: &str = "input/test2.txt";
#[test]
fn parses_input() {
let file = File::open(TEST_INPUT1).unwrap();
let reader = BufReader::new(file);
let passports = Passport::from_reader(reader).unwrap();
assert_eq!(passports.len(), 4);
assert_eq!(
format!("{}", passports[0]),
"byr:1937 iyr:2017 eyr:2020 hgt:183cm hcl:#fffffd ecl:gry pid:860033327 cid:147\n"
);
assert_eq!(
format!("{}", passports[1]),
"byr:1929 iyr:2013 eyr:2023 hcl:#cfa07d ecl:amb pid:028048884 cid:350\n"
);
assert_eq!(
format!("{}", passports[2]),
"byr:1931 iyr:2013 eyr:2024 hgt:179cm hcl:#ae17e1 ecl:brn pid:760753108\n"
);
assert_eq!(
format!("{}", passports[3]),
"iyr:2011 eyr:2025 hgt:59in hcl:#cfa07d ecl:brn pid:166559648\n"
);
}
#[test]
fn solves_part1() {
assert_eq!(solve_part1(TEST_INPUT1).unwrap(), 2);
}
#[test]
fn solves_part2() {
assert_eq!(solve_part2(TEST_INPUT2).unwrap(), 4);
}
}