module day4.part2.main; import std; void main() { readText("input").parsePassports.countValidPassports.writeln; } alias Passport = string[string]; auto parsePassports(string input) { return input.splitter("\n\n").map!(passport => passport.splitter().map!((entry) { auto keyAndValue = entry.split(':'); return tuple(keyAndValue[0], keyAndValue[1]); }).assocArray); } enum Field : string { birthYear = "byr", issueYear = "iyr", expirationYear = "eyr", height = "hgt", hairColor = "hcl", eyeColor = "ecl", passportID = "pid", countryID = "cid", } bool isValidYear(int min, int max)(string yr) { if (yr.length != 4 || yr.canFind!(not!isDigit)) return false; auto year = yr.to!int; return year >= min && year <= max; } alias isValidBirthYear = isValidYear!(1920, 2002); alias isValidIssueYear = isValidYear!(2010, 2020); alias isValidExpirationYear = isValidYear!(2020, 2030); bool isValidHeight(string hgt) { auto match = hgt.matchFirst(r"^(\d+)(cm|in)$"); if (!match || match.length != 3) return false; auto number = match[1].to!int; switch (match[2]) { case "cm": return number >= 150 && number <= 193; break; case "in": return number >= 59 && number <= 76; default: return false; } } bool isValidHairColor(string hcl) { return hcl.matchFirst(r"^#[0-9a-f]{6}$").to!bool; } bool isValidEyeColor(string ecl) { return ["amb", "blu", "brn", "gry", "grn", "hzl", "oth"].canFind(ecl); } bool isValidPassportID(string pid) { return pid.matchFirst(r"^\d{9}$").to!bool; } enum requiredFields = [ Field.birthYear, Field.issueYear, Field.expirationYear, Field.height, Field.hairColor, Field.eyeColor, Field.passportID ]; bool isValidPassport(Passport passport) { return requiredFields.all!(field => field in passport) && isValidBirthYear(passport[Field.birthYear]) && isValidIssueYear(passport[Field.issueYear]) && isValidExpirationYear(passport[Field.expirationYear]) && isValidHeight(passport[Field.height]) && isValidHairColor(passport[Field.hairColor]) && isValidEyeColor(passport[Field.eyeColor]) && isValidPassportID(passport[Field.passportID]); } auto countValidPassports(R)(R r) if (isInputRange!R && is(ElementType!R == Passport)) { return r.filter!isValidPassport.count; } unittest { auto input = `eyr:1972 cid:100 hcl:#18171d ecl:amb hgt:170 pid:186cm iyr:2018 byr:1926 iyr:2019 hcl:#602927 eyr:1967 hgt:170cm ecl:grn pid:012533040 byr:1946 hcl:dab227 iyr:2012 ecl:brn hgt:182cm pid:021572410 eyr:2020 byr:1992 cid:277 hgt:59cm ecl:zzz eyr:2038 hcl:74454a iyr:2023 pid:3556412378 byr:2007`; auto passPorts = input.parsePassports; assert(passPorts.countValidPassports == 0); } unittest { auto input = `pid:087499704 hgt:74in ecl:grn iyr:2012 eyr:2030 byr:1980 hcl:#623a2f eyr:2029 ecl:blu cid:129 byr:1989 iyr:2014 pid:896056539 hcl:#a97842 hgt:165cm hcl:#888785 hgt:164cm byr:2001 iyr:2015 cid:88 pid:545766238 ecl:hzl eyr:2022 iyr:2010 hgt:158cm hcl:#b6652a ecl:blu byr:1944 eyr:2021 pid:093154719`; auto passPorts = input.parsePassports; assert(passPorts.countValidPassports == 4); }