Is this acmeism?
This commit is contained in:
115
aoc2020/py/day04/solution.py
Normal file
115
aoc2020/py/day04/solution.py
Normal file
@@ -0,0 +1,115 @@
|
||||
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())
|
Reference in New Issue
Block a user