2020-12-04 - Passport Processing
(original .ipynb)
Day 4 puzzle input is a set of data representing attributes of identification documents - possibly passports - with each document terminated by a blank line (mine is here). Part 1 involves parsing the input and returning the number of these documents which contain all of the required fields. Part 2 involves taking these parsed documents and applying some more detailed validation to each document's attributes and counting how many documents pass all validation.
test_input_str = """ecl:gry pid:860033327 eyr:2020 hcl:#fffffd byr:1937 iyr:2017 cid:147 hgt:183cm iyr:2013 ecl:amb cid:350 eyr:2023 pid:028048884 hcl:#cfa07d byr:1929 hcl:#ae17e1 iyr:2013 eyr:2024 ecl:brn pid:760753108 byr:1931 hgt:179cm hcl:#cfa07d eyr:2025 pid:166559648 iyr:2011 ecl:brn hgt:59in""" puzzle_input_str = open("puzzle_input/day4.txt").read() required_fields = [ "ecl", "pid", "eyr", "hcl", "byr", "iyr", "hgt" ] def has_required_fields(passport): return all(field in passport for field in required_fields) def parse_passport(input_line): passport_elements = input_line.split(" ") passport = {} for element in passport_elements: k,v = element.split(":") passport[k] = v return passport def parse_passports(input_str): input_lines = input_str.split("\n") raw_passport_data = [] current_passport = [] for input_line in input_lines: input_line = input_line.strip() if len(input_line) == 0: raw_passport_data.append(" ".join(current_passport)) current_passport = [] else: current_passport.append(input_line) raw_passport_data.append(" ".join(current_passport)) passports = [parse_passport(passport_data) for passport_data in raw_passport_data] return [p for p in passports if has_required_fields(p)] assert 2 == len(parse_passports(test_input_str)) print(len(parse_passports(puzzle_input_str)))
200
import re def valid_passport(passport): validation_funcs = [ validate_byr, validate_iyr, validate_eyr, validate_hgt, validate_hcl, validate_ecl, validate_pid ] return all(validate(passport) for validate in validation_funcs) # byr (Birth Year) - four digits; at least 1920 and at most 2002. def validate_byr(passport): byr_str = passport["byr"] if not re.match("^\d{4}$", byr_str): return False try: byr = int(byr_str) return byr >= 1920 and byr <= 2002 except: return False # iyr (Issue Year) - four digits; at least 2010 and at most 2020. def validate_iyr(passport): iyr_str = passport["iyr"] if not re.match("^\d{4}$", iyr_str): return False try: iyr = int(iyr_str) return iyr >= 2010 and iyr <= 2020 and len(passport["iyr"]) == 4 except: return False # eyr (Expiration Year) - four digits; at least 2020 and at most 2030. def validate_eyr(passport): eyr_str = passport["eyr"] if not re.match("^\d{4}$", eyr_str): return False eyr = int(eyr_str, 10) return eyr >= 2020 and eyr <= 2030 def parse_hgt(hgt_str): end_hgt = -1 units = "" if not re.match("^\d+(cm|in)$", hgt_str): return None if "cm" in hgt_str: end_hgt = hgt_str.index("cm") units = "cm" if "in" in hgt_str: end_hgt = hgt_str.index("in") units = "in" if end_hgt > 0: return (int(hgt_str[:end_hgt]), units) return None # hgt (Height) - a number followed by either cm or in: # If cm, the number must be at least 150 and at most 193. # If in, the number must be at least 59 and at most 76. def validate_hgt(passport): hgt = parse_hgt(passport["hgt"]) if not hgt: return False value, units = hgt if units == "cm": return value >= 150 and value <= 193 elif units == "in": return value >= 59 and value <= 76 return False # hcl (Hair Color) - a # followed by exactly six characters 0-9 or a-f. def validate_hcl(passport): return parse_hcl(passport["hcl"]) != None def parse_hcl(hcl_str): if not re.match("^#[0-9a-f]{6}$", hcl_str): return None return int(hcl_str[1:], 16) valid_eye_colors = set("amb blu brn gry grn hzl oth".split(" ")) # ecl (Eye Color) - exactly one of: amb blu brn gry grn hzl oth. def validate_ecl(passport): return passport["ecl"] in valid_eye_colors def validate_pid(passport): pid = parse_pid(passport["pid"]) return pid != None # pid (Passport ID) - a nine-digit number, including leading zeroes. def parse_pid(pid_str): if not re.match("^\d{9}$", pid_str): return None return int(pid_str) def parse_and_validate_passports(input_str): passports = parse_passports(input_str) return [p for p in passports if valid_passport(p)] # byr valid: 2002 # byr invalid: 2003 assert validate_byr({"byr": "2002"}) assert not validate_byr({"byr": "2003"}) # hgt valid: 60in # hgt valid: 190cm # hgt invalid: 190in # hgt invalid: 190 assert validate_hgt({"hgt": "60in"}) assert validate_hgt({"hgt": "190cm"}) assert not validate_hgt({"hgt": "190in"}) assert not validate_hgt({"hgt": "190"}) # hcl valid: #123abc # hcl invalid: #123abz # hcl invalid: 123abc assert validate_hcl({"hcl":"#123abc"}) assert not validate_hcl({"hcl":"#123abz"}) assert not validate_hcl({"hcl":"123abc"}) # ecl valid: brn # ecl invalid: wat assert validate_ecl({"ecl":"brn"}) assert not validate_ecl({"ecl":"wat"}) # pid valid: 000000001 # pid invalid: 0123456789 assert validate_pid({"pid":"000000001"}) assert not validate_pid({"pid":"0123456789"}) print(len(parse_and_validate_passports(puzzle_input_str)))
116