|
|
|
@ -13,27 +13,74 @@ def parse_passports(lines)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def validate_range(range)
|
|
|
|
|
->(s : String) { s.to_i32?.try { |i| range.includes? i } }
|
|
|
|
|
class Validator(T, R)
|
|
|
|
|
def initialize(@proc : Proc(T, R?))
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def >>(other)
|
|
|
|
|
Validator.new(->(i : T) { @proc.call(i).try { |j| other.@proc.call(j) } })
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def then(&block : R -> S?) forall S
|
|
|
|
|
Validator(T, S).new(->(i : T) { @proc.call(i).try { |j| block.call(j) } })
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def run(input)
|
|
|
|
|
@proc.call(input)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def validate_regex(regex)
|
|
|
|
|
->(s : String) { s.matches?(regex) }
|
|
|
|
|
class Hash(K,V)
|
|
|
|
|
def validate(k, vl)
|
|
|
|
|
self[k]?.try { |v| vl.run(v) }
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def range(range) : Validator(Int32, Int32)
|
|
|
|
|
Validator.new ->(i : Int32) { (range.includes? i) ? i : nil }
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def validate_unit_range(unit_ranges)
|
|
|
|
|
->(s : String) {
|
|
|
|
|
return false unless s.match(/^(\d+)([a-zA-Z]+)$/)
|
|
|
|
|
unit_ranges[$~[2]]?.try { |rng| validate_range(rng).call($~[1]) }
|
|
|
|
|
}
|
|
|
|
|
def int : Validator(String, Int32)
|
|
|
|
|
Validator.new ->(s : String) { s.to_i32? }
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
DEFAULT_VALIDATORS = {
|
|
|
|
|
"byr" => validate_range(1920..2002),
|
|
|
|
|
"iyr" => validate_range(2010..2020),
|
|
|
|
|
"eyr" => validate_range(2020..2030),
|
|
|
|
|
"hgt" => validate_unit_range({"cm" => (150..193), "in" => (59..76)}),
|
|
|
|
|
"hcl" => validate_regex(/^#[0-9a-f]{6}$/),
|
|
|
|
|
"ecl" => ->(s : String) { "amb blu brn gry grn hzl oth".split(" ").includes? s },
|
|
|
|
|
"pid" => ->(s : String) { s =~ /^[0-9]{9}$/ },
|
|
|
|
|
}
|
|
|
|
|
def regex(regex) : Validator(String, Regex::MatchData)
|
|
|
|
|
Validator(String, Regex::MatchData).new ->(s : String) { s.match(regex) }
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def oneof(strs : Array(A)) : Validator(A, A) forall A
|
|
|
|
|
Validator(String, String).new ->(s : String) { (strs.includes? s) ? s : nil }
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def length(len) : Validator(String, String)
|
|
|
|
|
Validator(String, String).new ->(s : String) { (s.size == len) ? s : nil }
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def year(rng) : Validator(String, Int32)
|
|
|
|
|
length(4) >> int >> range(rng)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def unit_range(map) : Validator(String, {String, Int32})
|
|
|
|
|
regex(/(\d+)([a-zA-Z]+)/).then do |md|
|
|
|
|
|
map[md[2]]?.try { |r| (int >> range(r)).run(md[1]).try { |i| {md[2], i} } }
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
class Passport
|
|
|
|
|
def initialize(
|
|
|
|
|
@byr : Int32, @iyr : Int32,
|
|
|
|
|
@eyr : Int32, @hgt : {String, Int32},
|
|
|
|
|
@hcl : String, @ecl : String, @pid : Int32)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def self.from_hash?(hash)
|
|
|
|
|
return nil unless byr = hash.validate("byr", year(1920..2002))
|
|
|
|
|
return nil unless iyr = hash.validate("iyr", year(2010..2020))
|
|
|
|
|
return nil unless eyr = hash.validate("eyr", year(2020..2030))
|
|
|
|
|
return nil unless hgt = hash.validate("hgt", unit_range({"cm" => (150..193), "in" => (59..76)}))
|
|
|
|
|
return nil unless hcl = hash.validate("hcl", regex(/^#([0-9a-f]{6})$/).then { |d| d[1] })
|
|
|
|
|
return nil unless ecl = hash.validate("ecl", oneof(["amb", "blu", "brn", "gry", "grn", "hzl", "oth"]))
|
|
|
|
|
return nil unless pid = hash.validate("pid", length(9) >> int)
|
|
|
|
|
Passport.new(byr, iyr, eyr, hgt, hcl, ecl, pid)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|