summaryrefslogtreecommitdiff
path: root/day4/day4.py
blob: f64356c23ecc65b952282eb5320b2c52b3d9f4af (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
"""Day 4 code."""
import re
from itertools import chain, groupby

REQUIRED_FIELDS = set(["byr", "ecl", "eyr", "hcl", "hgt", "iyr", "pid"])
EYE_COLORS = ["amb", "blu", "brn", "gry", "grn", "hzl", "oth"]
HCL_PATTERN = r"^#([a-fA-F0-9]{6})$"
PID_PATTERN = "^([0-9]{9})$"


def passport_str_to_list(passport_str):
    """Convert string of passport data into list of key, values.

    The reqason we use tuple is so that when we eventually get a full list that
    presents all of key values of passport we can convert it into a dict easier.
    """
    pairs = passport_str.split(" ")
    return [tuple(x.split(":")) for x in pairs]


def passport_parser(itr):
    """Parse passport data into individual passports.

    Groupby allows us to generate a new group every time we encounter an empty
    line which signifies the boundary of each passport's data.
    """
    for k, groups in groupby(data, lambda x: x == ""):
        if k is False:
            yield chain.from_iterable(passport_str_to_list(x) for x in groups)


def passport_has_required_fields(passport):
    """Check if passport is valid."""
    return REQUIRED_FIELDS.issubset(set(passport.keys()))


data = map(str.strip, open("input"))
passports = [dict(x) for x in passport_parser(data)]

print("Part 1:", sum(map(passport_has_required_fields, passports)))


def byr_is_valid(passport):
    """Check birthyear is valid."""
    return 1920 <= int(passport["byr"]) <= 2002


def iyr_is_valid(passport):
    """Check issue year is valid."""
    return 2010 <= int(passport["iyr"]) <= 2020


def eyr_is_valid(passport):
    """Check expiration year is valid."""
    return 2020 <= int(passport["eyr"]) <= 2030


def hgt_is_valid(passport):
    """Check the hieght is valid."""
    hgt = passport["hgt"]
    if "cm" in hgt:
        val = int(hgt[: hgt.find("cm")])
        if 150 <= val <= 193:
            return True
    elif "in" in hgt:
        val = int(hgt[: hgt.find("in")])
        if 59 <= val <= 76:
            return True
    return False


def hcl_is_valid(passport):
    """Check hair color is valid."""
    return re.match(HCL_PATTERN, passport["hcl"])


def ecl_is_valid(passport):
    """Check if eye color is valid."""
    return passport["ecl"] in EYE_COLORS


def pid_is_valid(passport):
    """Check passport id is valid."""
    return re.match(PID_PATTERN, passport["pid"])


VALIDATION_TESTS = [
    byr_is_valid,
    iyr_is_valid,
    eyr_is_valid,
    hgt_is_valid,
    hcl_is_valid,
    ecl_is_valid,
    pid_is_valid,
]


def passport_is_valid(passport):
    """Check if a passport is valid."""
    return all(func(passport) for func in VALIDATION_TESTS)


passports_with_required_fields = filter(passport_has_required_fields, passports)
print("Answer 2", sum(map(passport_is_valid, passports_with_required_fields)))