Advent of Code 2020, day 4
It’s day 4… As before, all my solutions are available on https://git.sr.ht/~schnouki/advent-of-code.
Today we’re dealing with passport validation. Spoiler alert: as often with data validation issues, it’s pretty boring…
Okay, let’s parse those passports. Split input when there are 2 newlines, split each block on whitespace, parse key:value entries. Boring!
class Passport(dict):
    @classmethod
    def from_kv_str(cls, data: str) -> "Passport":
        res = cls()
        for field in data.split():
            k, v = field.split(":", 1)
            res[k] = v
        return res
data = [Passport.from_kv_str(block) for block in raw_data.split("\n\n")]
Part 1
In this part we just need to check if all required fields are present in the passport. Nothing about their values, even empty strings are OK. Meh.
class Passport(dict):
    @property
    def is_valid_p1(self) -> bool:
        return all(
            field in self for field in ("byr", "iyr", "eyr", "hgt", "hcl", "ecl", "pid")
        )
print(sum(1 for p in data if p.is_valid_p1))
Done. 0.18 ms. Boring.
Part 2
Now we need to validate data. A few years with min/max, a height in cm or in, a hex color, a value in a set, and a number of a specific length… Meh, I don’t even want to try to make that fun.
import re
RE_HEIGHT = re.compile(r"^(\d+)(cm|in)$")
RE_COLOR = re.compile(r"^#[0-9a-f]{6}$")
class Passport(dict):
    @property
    def is_valid_p2(self) -> bool:
        checks = [
            self._check_year("byr", 1920, 2002),
            self._check_year("iyr", 2010, 2020),
            self._check_year("eyr", 2020, 2030),
            self._check_height(),
            RE_COLOR.match(self.get("hcl", "")),
            self.get("ecl") in ("amb", "blu", "brn", "gry", "grn", "hzl", "oth"),
            len(self.get("pid", "")) == 9 and self.get("pid", "").isdigit(),
        ]
        return all(checks)
    def _check_year(self, field: str, min: int, max: int) -> bool:
        if field not in self:
            return False
        try:
            return min <= int(self[field]) <= max
        except ValueError:
            return False
    def _check_height(self) -> bool:
        if "hgt" not in self:
            return False
        if (m := RE_HEIGHT.match(self["hgt"])) :
            min, max = (150, 193) if m.group(2) == "cm" else (59, 76)
            return min <= int(m.group(1)) <= max
        return False
Validation is pretty minimal, but that’s enough for this case as we’re dealing with well-behaved input anyway. 0.53 ms. Only cool thing: I got to use Python’s new walrus operator. Yay?
Comments
Join the conversation by sending an email. Your comment will be added here and to the public inbox after moderation.