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