229 lines
6.9 KiB
Python
229 lines
6.9 KiB
Python
# --- Day 4: Passport Processing ---
|
|
|
|
# You arrive at the airport only to realize that you grabbed your North Pole
|
|
# Credentials instead of your passport. While these documents are extremely
|
|
# similar, North Pole Credentials aren't issued by a country and therefore
|
|
# aren't actually valid documentation for travel in most of the world.
|
|
|
|
# It seems like you're not the only one having problems, though; a very long
|
|
# line has formed for the automatic passport scanners, and the delay could
|
|
# upset your travel itinerary.
|
|
|
|
# Due to some questionable network security, you realize you might be able to
|
|
# solve both of these problems at the same time.
|
|
|
|
# The automatic passport scanners are slow because they're having trouble
|
|
# detecting which passports have all required fields. The expected fields are
|
|
# as follows:
|
|
|
|
# byr (Birth Year)
|
|
# iyr (Issue Year)
|
|
# eyr (Expiration Year)
|
|
# hgt (Height)
|
|
# hcl (Hair Color)
|
|
# ecl (Eye Color)
|
|
# pid (Passport ID)
|
|
# cid (Country ID)
|
|
|
|
# Passport data is validated in batch files (your puzzle input). Each passport
|
|
# is represented as a sequence of key:value pairs separated by spaces or
|
|
# newlines. Passports are separated by blank lines.
|
|
|
|
# Here is an example batch file containing four passports:
|
|
|
|
# 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
|
|
|
|
# The first passport is valid - all eight fields are present. The second
|
|
# passport is invalid - it is missing hgt (the Height field).
|
|
|
|
# The third passport is interesting; the only missing field is cid, so it looks
|
|
# like data from North Pole Credentials, not a passport at all! Surely, nobody
|
|
# would mind if you made the system temporarily ignore missing cid fields.
|
|
# Treat this "passport" as valid.
|
|
|
|
# The fourth passport is missing two fields, cid and byr. Missing cid is fine,
|
|
# but missing any other field is not, so this passport is invalid.
|
|
|
|
# According to the above rules, your improved system would report 2 valid
|
|
# passports.
|
|
|
|
# Count the number of valid passports - those that have all required fields.
|
|
# Treat cid as optional. In your batch file, how many passports are valid?
|
|
|
|
|
|
with open("files/P4.txt", "r") as f:
|
|
passports = [line.split() for line in f.read().strip().split("\n\n")]
|
|
|
|
|
|
def part_1() -> None:
|
|
valid_passports = 0
|
|
for passport in passports:
|
|
if len(passport) > 7:
|
|
valid_passports += 1
|
|
if len(passport) == 7:
|
|
valid_fields = 0
|
|
for field in passport:
|
|
if "cid" not in field:
|
|
valid_fields += 1
|
|
if valid_fields == 7:
|
|
valid_passports += 1
|
|
|
|
print(f"There are {valid_passports} valid passports")
|
|
|
|
|
|
# --- Part Two ---
|
|
|
|
# The line is moving more quickly now, but you overhear airport security
|
|
# talking about how passports with invalid data are getting through. Better add
|
|
# some data validation, quick!
|
|
|
|
# You can continue to ignore the cid field, but each other field has strict
|
|
# rules about what values are valid for automatic validation:
|
|
|
|
# byr (Birth Year) - four digits; at least 1920 and at most 2002.
|
|
# iyr (Issue Year) - four digits; at least 2010 and at most 2020.
|
|
# eyr (Expiration Year) - four digits; at least 2020 and at most 2030.
|
|
# 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.
|
|
# hcl (Hair Color) - a # followed by exactly six characters 0-9 or a-f.
|
|
# ecl (Eye Color) - exactly one of: amb blu brn gry grn hzl oth.
|
|
# pid (Passport ID) - a nine-digit number, including leading zeroes.
|
|
# cid (Country ID) - ignored, missing or not.
|
|
|
|
# Your job is to count the passports where all required fields are both present
|
|
# and valid according to the above rules. Here are some example values:
|
|
|
|
# byr valid: 2002
|
|
# byr invalid: 2003
|
|
|
|
# hgt valid: 60in
|
|
# hgt valid: 190cm
|
|
# hgt invalid: 190in
|
|
# hgt invalid: 190
|
|
|
|
# hcl valid: #123abc
|
|
# hcl invalid: #123abz
|
|
# hcl invalid: 123abc
|
|
|
|
# ecl valid: brn
|
|
# ecl invalid: wat
|
|
|
|
# pid valid: 000000001
|
|
# pid invalid: 0123456789
|
|
|
|
# Here are some invalid passports:
|
|
|
|
# eyr:1972 cid:100
|
|
# hcl:#18171d ecl:amb hgt:170 pid:186cm iyr:2018 byr:1926
|
|
|
|
# iyr:2019
|
|
# hcl:#602927 eyr:1967 hgt:170cm
|
|
# ecl:grn pid:012533040 byr:1946
|
|
|
|
# hcl:dab227 iyr:2012
|
|
# ecl:brn hgt:182cm pid:021572410 eyr:2020 byr:1992 cid:277
|
|
|
|
# hgt:59cm ecl:zzz
|
|
# eyr:2038 hcl:74454a iyr:2023
|
|
# pid:3556412378 byr:2007
|
|
|
|
# Here are some valid passports:
|
|
|
|
# pid:087499704 hgt:74in ecl:grn iyr:2012 eyr:2030 byr:1980
|
|
# hcl:#623a2f
|
|
|
|
# eyr:2029 ecl:blu cid:129 byr:1989
|
|
# iyr:2014 pid:896056539 hcl:#a97842 hgt:165cm
|
|
|
|
# hcl:#888785
|
|
# hgt:164cm byr:2001 iyr:2015 cid:88
|
|
# pid:545766238 ecl:hzl
|
|
# eyr:2022
|
|
|
|
# iyr:2010 hgt:158cm hcl:#b6652a ecl:blu byr:1944 eyr:2021 pid:093154719
|
|
|
|
# Count the number of valid passports - those that have all required fields and
|
|
# valid values. Continue to treat cid as optional. In your batch file, how many
|
|
# passports are valid?
|
|
|
|
|
|
def is_valid(passport: dict[str, str]) -> bool:
|
|
keys = ["byr", "iyr", "eyr", "hgt", "hcl", "ecl", "pid"]
|
|
for key in keys:
|
|
if key not in passport:
|
|
return False
|
|
if not any([passport[key] != 4 for key in ["byr", "iyr", "eyr"]]):
|
|
return False
|
|
if passport["byr"] < "1920" or passport["byr"] > "2002":
|
|
return False
|
|
if passport["iyr"] < "2010" or passport["iyr"] > "2020":
|
|
return False
|
|
if passport["eyr"] < "2020" or passport["eyr"] > "2030":
|
|
return False
|
|
if not any(["cm" in passport["hgt"], "in" in passport["hgt"]]):
|
|
return False
|
|
_hgt = int(passport["hgt"][:-2])
|
|
if "cm" in passport["hgt"]:
|
|
if _hgt < 150 or _hgt > 193:
|
|
return False
|
|
if "in" in passport["hgt"]:
|
|
if _hgt < 59 or _hgt > 76:
|
|
return False
|
|
if "#" not in passport["hcl"]:
|
|
return False
|
|
_hcl = passport["hcl"].split("#")[-1]
|
|
if len(_hcl) != 6:
|
|
return False
|
|
try:
|
|
int(_hcl, 16)
|
|
except ValueError:
|
|
return False
|
|
if not any(
|
|
[
|
|
passport["ecl"]
|
|
in [
|
|
"amb",
|
|
"blu",
|
|
"brn",
|
|
"gry",
|
|
"grn",
|
|
"hzl",
|
|
"oth",
|
|
]
|
|
]
|
|
):
|
|
return False
|
|
if len(passport["pid"]) != 9:
|
|
return False
|
|
return True
|
|
|
|
|
|
def part_2() -> None:
|
|
valid_passports = 0
|
|
for passport in passports:
|
|
_items: list[list[str]] = [
|
|
item.split(":") for item in passport if item
|
|
]
|
|
passport_dict: dict[str, str] = dict(_items)
|
|
if is_valid(passport_dict):
|
|
valid_passports += 1
|
|
print(f"There are {valid_passports} valid passports")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
part_1()
|
|
part_2()
|