summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorcos <cos>2020-12-04 08:11:32 +0100
committercos <cos>2020-12-04 10:25:55 +0100
commit1630c01b93513a650c8cb550bcad2da9a6d9d4fb (patch)
tree5d7f7fb034b9b5f09bf028690a9bcf506932a282
parent31c4288918624c5af2adbe642e578d2cb70a0fe0 (diff)
downloadadventofcode-1630c01b93513a650c8cb550bcad2da9a6d9d4fb.zip
Add day04, 2020
-rw-r--r--2020/rust/Cargo.toml2
-rw-r--r--2020/rust/day04/Cargo.toml10
-rw-r--r--2020/rust/day04/src/main.rs159
3 files changed, 170 insertions, 1 deletions
diff --git a/2020/rust/Cargo.toml b/2020/rust/Cargo.toml
index 6ecd67b..4ef217f 100644
--- a/2020/rust/Cargo.toml
+++ b/2020/rust/Cargo.toml
@@ -4,7 +4,7 @@ members = [
"day01",
"day02",
"day03",
-# "day04",
+ "day04",
# "day05",
# "day06",
# "day07",
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),
+ }
+}