import sys import typing def main() -> int: checked = [] for passport in _read_passports(sys.stdin): if passport is None: checked.append(False) continue checked.append(passport.is_valid()) print(f"total={len(checked)} valid={checked.count(True)}") return 0 NoneString = typing.Optional[str] VALID_EYE_COLORS = ("amb", "blu", "brn", "gry", "grn", "hzl", "oth") class Passport: byr: NoneString = None cid: NoneString = None ecl: NoneString = None eyr: NoneString = None hcl: NoneString = None hgt: NoneString = None iyr: NoneString = None pid: NoneString = None def is_valid(self) -> bool: return ( self._is_year_in_range(self.byr, range(1920, 2003)) and self._is_year_in_range(self.iyr, range(2010, 2021)) and self._is_year_in_range(self.eyr, range(2020, 2031)) and self._has_valid_height() and self._has_valid_hair_color() and self.ecl in VALID_EYE_COLORS and self._has_valid_passport_id() ) def _has_valid_height(self) -> bool: if self.hgt is None: return False height_value = 0 height_value_string = self.hgt.replace("cm", "").replace("in", "") if not height_value_string.isdigit(): return False height = int(height_value_string) if self.hgt.endswith("cm") and height in range(150, 194): return True elif self.hgt.endswith("in") and height in range(59, 77): return True return False def _has_valid_hair_color(self) -> bool: if self.hcl is None: return False if not self.hcl.startswith("#"): return False hair_value = self.hcl.replace("#", "") if len(hair_value) != 6: return False try: _ = int(hair_value, 16) return True except ValueError: return False def _has_valid_passport_id(self) -> bool: return ( self.pid is not None and len(self.pid) == 9 and all([s.isdigit() for s in list(self.pid)]) ) def _is_year_in_range(self, value: NoneString, yr_range: range) -> bool: if value is None: return False if len(value) != 4: return False if not value.isdigit(): return False return int(value) in yr_range def _read_passports( instream: typing.TextIO, ) -> typing.Generator[typing.Optional[Passport], None, None]: cur = Passport() for i, line in enumerate(instream): line = line.strip() if line == "": yield cur cur = Passport() for pair in line.split(): attr, value = pair.split(":", 1) setattr(cur, attr, value) yield cur if __name__ == "__main__": sys.exit(main())