@xavdid does Advent of Code

Passport Processing

Published: 2020-12-04 Original Prompt

Part 1

Off the bat, the core challenge here is breaking this disorganized input into its component keys.

Let’s start by isolating each passport by splitting on empty lines:

passports = self.input.split("\n\n")
['ecl:gry pid:860033327 eyr:2020 hcl:#fffffd\nbyr:1937 iyr:2017 cid:147 hgt:183cm', 'iyr:2013 ecl:amb cid:350 eyr:2023 pid:028048884\nhcl:#cfa07d byr:1929', 'hcl:#ae17e1 iyr:2013\neyr:2024\necl:brn pid:760753108 byr:1931\nhgt:179cm', 'hcl:#cfa07d eyr:2025 pid:166559648\niyr:2011 ecl:brn hgt:59in']

The real rub comes from the prompt:

a sequence of key:value pairs separated by spaces or newlines

Having to work through that is an extra step, but is pretty doable. We’ll want to store fields in a dict so that we can quickly check how many keys we have. We don’t need values yet, but I’m guessing that for part 2, they’ll become relevant.

Rather than using the regular string.split method (which can only split on a single separator at a time), let’s use re.split, which lets us use regex to provide multiple separators at once. In our case, we want |\n, aka “space or newline”.

import re
re.split(r' |\n', passport)
['ecl:gry', 'pid:860033327', 'eyr:2020', 'hcl:#fffffd', 'byr:1937', 'iyr:2017', 'cid:147', 'hgt:183cm']

From there, a list comprehension lets us build a list of the keys and values which can be piped right into dict.

dict([s.split(":") for s in re.split(r" |\n", passport)])
{'ecl': 'gry', 'pid': '860033327', 'eyr': '2020', 'hcl': '#fffffd', 'byr': '1937', 'iyr': '2017', 'cid': '147', 'hgt': '183cm'}

From there, it’s pretty easy to write rules for validity. It either needs:

So for each cleaned passport:

cleaned = flatten_passport(passport)
if len(cleaned) == 8 or (len(cleaned) == 7 and "cid" not in cleaned):
num_valid += 1

This approach doesn’t check that the 8 keys are the correct ones, but that doesn’t seem to be an issue. Looks like we can safely assume that if a key is present, it’ll be one of the approved ones.

Part 2

Ah yep, there’s values validation!

Luckily, all the validation functions are pretty byte-sized. We can make a single little validator dict with python lambda functions. They’re just like regular functions, but less syntax and restricted to a single expression:

VALIDATORS = {
'byr': lambda x: len(x) == 4 and 1920 <= int(x) <= 2002,
...
}
VALIDATORS['byr']('1991') # True
VALIDATORS['byr']('19910') # False

We can easily loop through these functions and fail passports that don’t pass muster.

The validators mostly aren’t super interesting, but there are a couple of good regex in there:

matching hex values:

lambda x: bool(re.match(r"#[0-9a-f]{6}", x)),

matching heights, which was the only one I couldn’t reasonably fit in a lambda:

def valid_height(x: str) -> bool:
match = re.match(r"(\d{2,3})(cm|in)$", x)
if not match:
return False
size, unit = match.groups()
if unit == "cm":
return 150 <= int(size) <= 193
return 59 <= int(size) <= 76

And just like that, valid passports.