1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
|
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 mut re: HashMap<String, Regex> = HashMap::new();
// byr (Birth Year) - four digits; at least 1920 and at most 2002.
re.insert(String::from("byr"), Regex::new(r"^(19[2-9][0-9])|(200[0-2])$")?);
// iyr (Issue Year) - four digits; at least 2010 and at most 2020.
re.insert(String::from("iyr"), Regex::new(r"^20(1[0-9])|(20)$")?);
// eyr (Expiration Year) - four digits; at least 2020 and at most 2030.
re.insert(String::from("eyr"), Regex::new(r"^20(2[0-9])|(30)$")?);
// 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.
re.insert(String::from("hgt"),
Regex::new(r"^1(([5-8][0-9])|(9[0-3]))cm|((59)|(6[0-9])|(7[0-6]))in$")?);
// hcl (Hair Color) - a # followed by exactly six characters 0-9 or a-f.
re.insert(String::from("hcl"), Regex::new(r"^#[0-9a-f]{6}$")?);
// ecl (Eye Color) - exactly one of: amb blu brn gry grn hzl oth.
re.insert(String::from("ecl"), Regex::new(r"^(amb|blu|brn|gry|grn|hzl|oth)$")?);
// pid (Passport ID) - a nine-digit number, including leading zeroes.
re.insert(String::from("pid"), Regex::new(r"^\d{9}$")?);
// cid (Country ID) - ignored, missing or not.
'validate: for passport in input {
for key in &["byr", "iyr", "eyr", "hgt", "hcl", "ecl", "pid"] {
match (passport.get(*key), re.get(*key)) {
(Some(val), Some(re)) => if ! re.is_match(val) {
continue 'validate;
},
_ => continue 'validate,
}
}
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),
}
}
|