From b91819c4cf903e76bd2051f66766e8c13c4d225d Mon Sep 17 00:00:00 2001 From: Danila Fedorin Date: Fri, 4 Dec 2020 19:54:14 -0800 Subject: [PATCH] Reimplement day 4 using validators. --- day4.cr | 9 +++--- passports.cr | 83 ++++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 70 insertions(+), 22 deletions(-) diff --git a/day4.cr b/day4.cr index b00a064..626920e 100644 --- a/day4.cr +++ b/day4.cr @@ -15,12 +15,13 @@ end def part2 input = INPUT.clone passports = parse_passports(input) - total = passports.count do |passport| - DEFAULT_VALIDATORS.all? do |k, v| - passport[k]?.try { |s| v.call(s) } + valid_passports = [] of Passport + passports.each do |p| + if vp = Passport.from_hash?(p) + valid_passports << vp end end - puts total + puts valid_passports.size end part1 diff --git a/passports.cr b/passports.cr index 4ead063..ffeab74 100644 --- a/passports.cr +++ b/passports.cr @@ -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 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 range(range) : Validator(Int32, Int32) + Validator.new ->(i : Int32) { (range.includes? i) ? i : nil } 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 int : Validator(String, Int32) + Validator.new ->(s : String) { s.to_i32? } +end + +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