Completed day 4
This commit is contained in:
parent
d57f26c617
commit
1cdc019c79
14
day04/Cargo.lock
generated
Normal file
14
day04/Cargo.lock
generated
Normal 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
10
day04/Cargo.toml
Normal 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
1146
day04/input/input.txt
Executable file
File diff suppressed because it is too large
Load Diff
13
day04/input/test1.txt
Normal file
13
day04/input/test1.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
|
26
day04/input/test2.txt
Normal file
26
day04/input/test2.txt
Normal 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
261
day04/src/main.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user