AoC2020/src/day4.rs

153 lines
4.6 KiB
Rust

use std::io::BufRead;
use std::collections::{HashMap, HashSet};
type Passport = HashMap<String, String>;
const ALLOWED_FIELDS: [&str; 8] = ["byr", "iyr", "eyr", "hgt", "hcl", "ecl", "pid", "cid"];
const OPTIONAL_FIELDS: [&str; 1] = ["cid"];
fn validate_fields((name, value): (&String, &String)) -> bool {
match name.as_str() {
"byr" => value.parse::<u32>().map(|byr| byr >= 1920 && byr <= 2002).unwrap_or(false),
"iyr" => value.parse::<u32>().map(|byr| byr >= 2010 && byr <= 2020).unwrap_or(false),
"eyr" => value.parse::<u32>().map(|byr| byr >= 2020 && byr <= 2030).unwrap_or(false),
"hgt" => {
let mut hgt_string = value.to_string();
let unit = hgt_string.split_off(hgt_string.len() - 2);
if let Ok(hgt) = hgt_string.parse::<u32>() {
match unit.as_str() {
"cm" => hgt >= 150 && hgt <= 193,
"in" => hgt >= 59 && hgt <= 76,
_ => false
}
} else {
false
}
},
"hcl" => {
let mut it = value.chars();
if let Some('#') = it.next() {
it.all(|c| c.is_ascii_hexdigit())
} else {
false
}
},
"ecl" => match value.as_str() {
"amb" | "blu" | "brn" | "gry" | "grn" | "hzl" | "oth" => true,
_ => false
},
"pid" => value.len() == 9 && value.chars().all(|c| c.is_ascii_digit()),
"cid" => true,
_ => unreachable!()
}
}
fn validate_passport_field(allowed_fields: &HashSet<String>, optional_fields: &HashSet<String>, passport: &Passport) -> bool {
let passport_fields: HashSet<_> = passport.keys().map(String::to_owned).collect();
optional_fields.is_superset(
&passport_fields
.symmetric_difference(&allowed_fields)
.map(String::to_owned)
.collect()
)
}
fn validate_passport_value(passport: &Passport) -> bool {
passport.iter().all(validate_fields)
}
fn parse_input<F: BufRead> (input: F) -> Vec<Passport> {
let tokens: Vec<_> = input.lines()
.map(|line| line.unwrap().split_whitespace().map(|v| Some(str::to_owned(v))).collect::<Vec<_>>())
.flat_map(|splited| if splited.len() == 0 {
vec![None]
} else {
splited
})
.collect();
tokens.split(Option::is_none).map(|passport|
passport.iter().map(|entry| {
let fields: Vec<_> = entry.as_ref().unwrap().split(':').collect();
(fields[0].to_string(), fields[1].to_string())
}).collect::<Passport>()
).collect()
}
fn to_string_set(list: &[&str]) -> HashSet<String> {
list.into_iter()
.map(|s| String::from(*s))
.collect()
}
pub fn validate_part2<F: BufRead> (input: F) -> usize {
let allowed_fields_list = to_string_set(&ALLOWED_FIELDS);
let optional_fields_list = to_string_set(&OPTIONAL_FIELDS);
parse_input(input)
.iter()
.filter(|passport| validate_passport_field(&allowed_fields_list, &optional_fields_list, passport))
.filter(|passport| validate_passport_value(passport))
.count()
}
pub fn validate_part1<F: BufRead> (input: F) -> usize {
let allowed_fileds_list = to_string_set(&ALLOWED_FIELDS);
let optional_fields_list = to_string_set(&OPTIONAL_FIELDS);
parse_input(input)
.iter()
.filter(|passport|
validate_passport_field(&allowed_fileds_list, &optional_fields_list, passport)
).count()
}
pub fn part1<F: BufRead> (input: F) {
println!("{:?}", validate_part1(input));
}
pub fn part2<F: BufRead> (input: F) {
println!("{:?}", validate_part2(input));
}
#[cfg(test)]
mod test {
use super::*;
#[test]
pub fn test_invalid() {
let input = r#"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"#.as_bytes();
assert_eq!(0, validate_part2(input));
}
#[test]
pub fn test_valid() {
let input = r#"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"#.as_bytes();
assert_eq!(4, validate_part2(input));
}
}