diff options
Diffstat (limited to '2020/rust/day04')
-rw-r--r-- | 2020/rust/day04/Cargo.toml | 10 | ||||
-rw-r--r-- | 2020/rust/day04/src/main.rs | 159 |
2 files changed, 169 insertions, 0 deletions
diff --git a/2020/rust/day04/Cargo.toml b/2020/rust/day04/Cargo.toml new file mode 100644 index 0000000..4742cd9 --- /dev/null +++ b/2020/rust/day04/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "day04" +version = "0.1.0" +authors = ["cos <cos>"] +edition = "2018" + +[dependencies] +aoc = { path = "../aoc" } +anyhow = "1.0" +regex = "1.4" diff --git a/2020/rust/day04/src/main.rs b/2020/rust/day04/src/main.rs new file mode 100644 index 0000000..34ae9bc --- /dev/null +++ b/2020/rust/day04/src/main.rs @@ -0,0 +1,159 @@ +use anyhow::{Result, anyhow}; +use std::collections::HashMap; +use std::env::args; +use std::fs::File; +use std::io::{BufRead, BufReader}; +use std::path::Path; +use regex::Regex; + +type Passport = HashMap<String, String>; + +fn read_input<T: AsRef<Path>>(filename: T) -> Result<Vec<Passport>> { + let f = File::open(filename)?; + let reader = BufReader::new(f); + let mut passport = Passport::new(); + let mut passports = vec![]; + + for line in reader.lines() { + let line = line?; + if line.is_empty() { + passports.push(passport); + passport = Passport::new(); + continue; + } + for pair in line.split(' ') { + let mut s = pair.split(':'); + let key = From::from(s.next().ok_or_else(|| anyhow!("No key found"))?.trim()); + let value = From::from(s.next().ok_or_else(|| anyhow!("No value found"))?.trim()); + passport.insert(key, value); + } + } + passports.push(passport); + + Ok(passports) +} + +fn part1(input: &[Passport]) -> usize { + let mut valid_count = 0; + + 'validate: for passport in input { + for key in &[ "byr", "iyr", "eyr", "hgt", "hcl", "ecl", "pid", /*"cid",*/ ] { + if ! passport.contains_key(*key) { + continue 'validate; + } + } + valid_count += 1; + } + + valid_count +} + +fn part2(input: &[Passport]) -> Result<usize> { + let mut valid_count = 0; + let hgt_re = Regex::new(r"^(?P<value>\d+)(?P<unit>cm|in)$")?; + let hcl_re = Regex::new(r"^#[0-9a-f]{6}$")?; + let ecl_re = Regex::new(r"^(amb|blu|brn|gry|grn|hzl|oth)$")?; + let pid_re = Regex::new(r"^\d{9}$")?; + + 'validate: for passport in input { + // byr (Birth Year) - four digits; at least 1920 and at most 2002. + match passport.get("byr") { + Some(byr) => match byr.parse() { + Ok(1920..=2002) => {}, + _ => continue 'validate, + } + _ => continue 'validate, + } + // iyr (Issue Year) - four digits; at least 2010 and at most 2020. + match passport.get("iyr") { + Some(iyr) => match iyr.parse() { + Ok(2010..=2020) => {}, + _ => continue 'validate, + } + _ => continue 'validate, + } + // eyr (Expiration Year) - four digits; at least 2020 and at most 2030. + match passport.get("eyr") { + Some(eyr) => match eyr.parse() { + Ok(2020..=2030) => {}, + _ => continue 'validate, + } + _ => continue 'validate, + } + // 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. + match passport.get("hgt") { + Some(hgt) => match hgt_re.captures(hgt) { + Some(caps) => match &caps.name("unit") { + Some(unit) => match unit.as_str() { + "cm" => match &caps.name("value") { + Some(value) => match value.as_str().parse() { + Ok(150..=193) => {}, + _ => continue 'validate, + } + _ => continue 'validate, + }, + "in" => match &caps.name("value") { + Some(value) => match value.as_str().parse() { + Ok(59..=76) => {}, + _ => continue 'validate, + }, + _ => continue 'validate, + }, + _ => continue 'validate, + } + None => continue 'validate, + } + None => continue 'validate, + } + _ => continue 'validate, + } + // hcl (Hair Color) - a # followed by exactly six characters 0-9 or a-f. + match passport.get("hcl") { + Some(hcl) if hcl_re.is_match(hcl) => {}, + _ => continue 'validate, + } + // ecl (Eye Color) - exactly one of: amb blu brn gry grn hzl oth. + match passport.get("ecl") { + Some(ecl) if ecl_re.is_match(ecl) => {}, + _ => continue 'validate, + } + // pid (Passport ID) - a nine-digit number, including leading zeroes. + match passport.get("pid") { + Some(pid) if pid_re.is_match(pid) => {}, + _ => continue 'validate, + } + // cid (Country ID) - ignored, missing or not. + valid_count += 1; + } + + Ok(valid_count) +} + +fn main() { + let ( do_part_1, do_part_2 ) = aoc::do_parts(); + + let filename = match args().nth(1) { + Some(f) => f, + None => { + eprintln!("Missing input filename"); + std::process::exit(1); + }, + }; + match read_input(filename) { + Ok(input) => { + if do_part_1 { + let solution = part1(&input); + println!("Part1, found {} valid passports", solution); + } + if do_part_2 { + match part2(&input) { + Ok(solution) => println!("Part2, found {} valid passports", solution), + Err(err) => eprintln!("Part2, failed: {}", err), + } + } + }, + Err(err) => eprintln!("Could not read input: {}", err), + } +} |