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