Python
#!/usr/bin/env python3
import collections.abc
import re
import typing
class PassportException(Exception):
def __init__(self, message: str, values: typing.Any = None):
self.message = message
self.values = values
super().__init__()
def __str__(self):
return f"{self.message}: {self.values}"
def main():
passports = parse_input("04-input.txt")
print(f"Number of valid passports: {validate_passports(passports)} "
f"of {len(passports)}")
class Passport:
def __init__(self, entry: str) -> None:
p = {}
for field in entry.split():
key, value = field.split(":")
p[key] = value
self.byr = p.get("byr", None)
self.iyr = p.get("iyr", None)
self.eyr = p.get("eyr", None)
self.hgt = p.get("hgt", None)
self.hcl = p.get("hcl", None)
self.ecl = p.get("ecl", None)
self.pid = p.get("pid", None)
self.cid = p.get("cid", None)
self.hgt_unit: str
self.hgt_val: int
def __str__(self):
if self.isvalid():
return f"valid Passport: {self.__dict__}"
return (f"invalid Passport: {self.__dict__}, error:"
f" {self.invalid_exception}")
def iscomplete(self) -> bool:
return (self.byr and self.iyr and self.eyr and self.hgt and self.hcl
and self.ecl and self.pid)
def isvalid(self) -> bool:
try:
if not self.iscomplete():
raise PassportException("Fields incomplete")
if not (self.byr.isdecimal() and self.iyr.isdecimal()
and self.eyr.isdecimal()):
raise PassportException("Nondecimal Year",
values={
"byr": self.byr,
"iyr": self.byr,
"eyr": self.eyr
})
if not (1920 <= int(self.byr) <= 2002
and 2010 <= int(self.iyr) <= 2020
and 2020 <= int(self.eyr) <= 2030):
raise PassportException("Invalid year",
values={
"byr": self.byr,
"iyr": self.byr,
"eyr": self.eyr
})
self.hgt_val = self.hgt[:-2]
self.hgt_unit = self.hgt[-2:]
if not self.hgt_val.isdecimal():
raise PassportException("Nondecimal Height value",
values={
"hgt": self.hgt,
"hgt_val": self.hgt_val,
"hgt_unit": self.hgt_unit
})
if self.hgt_unit == "cm":
if not 150 <= int(self.hgt_val) <= 193:
raise PassportException("Invalid height value (cm)",
values={
"hgt": self.hgt,
"hgt_val": self.hgt_val,
"hgt_unit": self.hgt_unit
})
elif self.hgt_unit == "in":
if not 59 <= int(self.hgt_val) <= 76:
raise PassportException("Invalid height value (inch)",
values={
"hgt": self.hgt,
"hgt_val": self.hgt_val,
"hgt_unit": self.hgt_unit
})
else:
raise PassportException("Invalid heigth unit",
values={
"hgt": self.hgt,
"hgt_val": self.hgt_val,
"hgt_unit": self.hgt_unit
})
if not re.fullmatch(r"#[0-9a-f]{6}", self.hcl):
raise PassportException("Invalid hair color", values=self.hcl)
if self.ecl not in ("amb", "blu", "brn", "gry", "grn", "hzl",
"oth"):
raise PassportException("Invalid eye color", values=self.ecl)
if not re.fullmatch(r"\d{9}", self.pid):
raise PassportException("Invalid passport id", values=self.pid)
except PassportException as e:
self.invalid_exception = e
return False
return True
def parse_input(filename: str) -> list[dict]:
passports = []
with open(filename, "r") as file:
for entry in file.read().split("\n\n"):
passports.append(Passport(entry))
return passports
def validate_passports(passports: collections.abc.Iterable[Passport]) -> int:
valid = 0
for p in passports:
valid += p.isvalid()
print(p)
return valid
def validate_fields(passport: dict[str, str]) -> bool:
mandatory = {"byr", "iyr", "eyr", "hgt", "hcl", "ecl", "pid"}
fields = set(passport.keys())
if fields >= mandatory:
return True
return False
if __name__ == "__main__":
main()