use std::collections::HashMap;
use std::env;
fn extract_keys<S: AsRef<str>>(ln: S) -> HashMap<String,String> {
let mut collected = HashMap::new();
for field in ln.as_ref().split_whitespace() {
let parts: Vec<_> = field.split(':').collect();
collected.insert(String::from(parts[0]), String::from(parts[1]));
};
collected
}
const REQUIRED: &[(&str, fn(String) -> bool)] =
&[
("byr", check_byr),
("iyr", check_iyr),
("eyr", check_eyr),
("hgt", check_hgt),
("hcl", check_hcl),
("ecl", check_ecl),
("pid", check_pid)
];
fn check_byr(ln: String) -> bool {
ln.len() == 4 && match ln.parse::<usize>() {
Ok(year) => year >= 1920 && year <= 2002,
Err(_) => false,
}
}
fn check_iyr(ln: String) -> bool {
ln.len() == 4 && match ln.parse::<usize>() {
Ok(year) => year >= 2010 && year <= 2020,
Err(_) => false,
}
}
fn check_eyr(ln: String) -> bool {
ln.len() == 4 && match ln.parse::<usize>() {
Ok(year) => year >= 2020 && year <= 2030,
Err(_) => false,
}
}
fn check_hgt(ln: String) -> bool {
match ln.find(|c: char| !c.is_digit(10)) {
Some(sep) => {
let (num, unit) = ln.split_at(sep);
match num.parse::<isize>() {
Ok(num) => {
if unit == "in" {
num >= 59 && num <= 76
} else if unit == "cm" {
num >= 150 && num <= 193
} else {
false
}
},
Err(_) => false,
}
},
None => false,
}
}
fn check_hcl(ln: String) -> bool {
if ln.len() != 7 {
return false;
}
let mut ci = ln.chars();
if ci.next() != Some('#') {
return false;
}
for c in ci {
if !c.is_digit(16) {
return false;
}
}
true
}
fn check_ecl(ln: String) -> bool {
for ex in ["amb","blu","brn","gry","grn","hzl","oth"].iter() {
if ln == *ex {
return true;
}
}
false
}
fn check_pid(ln: String) -> bool {
ln.len() == 9 && ln.chars().all(|c| c.is_digit(10))
}
#[derive(Clone,Copy,Debug)]
enum Document {
Passport,
NorthPole,
Invalid
}
struct DelimByEmpty<I> {
inner: I,
}
impl<I> DelimByEmpty<I> {
fn new(inner: I) -> Self {
DelimByEmpty { inner: inner }
}
}
impl<I: Iterator<Item=HashMap<Q,R>>, Q: Clone + std::cmp::Eq + std::hash::Hash, R: Clone> Iterator for DelimByEmpty<I> {
type Item = HashMap<Q,R>;
fn next(&mut self) -> Option<Self::Item> {
let mut result = HashMap::new();
loop {
match self.inner.next() {
None => {
if result.is_empty() {
return None;
} else {
return Some(result);
}
},
Some(line) => {
if line.is_empty() {
return Some(result);
} else {
for (key,val) in line.iter() {
result.insert(key.clone(), val.clone());
}
}
},
}
}
}
}
fn validate_line(found: HashMap<String,String>, version: usize) -> Document {
for (key,validator) in REQUIRED.iter() {
match found.get(&String::from(*key)) {
None => { return Document::Invalid; },
Some(value) => {
if version == 2 && !validator(value.clone()) {
return Document::Invalid;
}
},
}
};
if found.contains_key(&String::from("cid")) {
Document::Passport
} else {
Document::NorthPole
}
}
fn main() {
use std::io::BufRead;
let args: Vec<_> = env::args().collect();
let version: usize = args[2].parse().unwrap();
let result: i32 = DelimByEmpty::new(
std::io::BufReader::new(std::fs::File::open(&args[1]).unwrap())
.lines()
.map(Result::unwrap)
.map(extract_keys)
).map(|d| validate_line(d, version))
.map(|t| match t {
Document::Invalid => 0,
_ => 1,
})
.sum();
println!("{:}",result);
}