summaryrefslogtreecommitdiff
path: root/day4/day4.py
diff options
context:
space:
mode:
Diffstat (limited to 'day4/day4.py')
-rw-r--r--day4/day4.py104
1 files changed, 104 insertions, 0 deletions
diff --git a/day4/day4.py b/day4/day4.py
new file mode 100644
index 0000000..f64356c
--- /dev/null
+++ b/day4/day4.py
@@ -0,0 +1,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)))