110 lines
2.8 KiB
Python
110 lines
2.8 KiB
Python
import sys
|
|
import typing
|
|
|
|
|
|
def main() -> int:
|
|
checked = [passport.is_valid() for passport in _read_passports(sys.stdin)]
|
|
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.BinaryIO,
|
|
) -> typing.Generator[None, None, Passport]:
|
|
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())
|