Advent of Code: Day 4 – Passport Processing

As the name implies, Day 4 is all about processing passports. You are given the following fields and you’ll have to do some verification steps using them:

byr (Birth Year)
iyr (Issue Year)
eyr (Expiration Year)
hgt (Height)
hcl (Hair Color)
ecl (Eye Color)
pid (Passport ID)
cid (Country ID)

!!! SPOILERS AHEAD !!!

You should stop reading if you haven’t solved this puzzle yet!

Part 1

The first part of the challenge is to verify that all the fields are present. You will go through the input file and count all the valid passports.

My rather simplistic solution to this problem is as follows:

def process(lines: str) -> bool:
    """
    Part 1
    """
    d = lines.split(' ')
    dd = dict([item.split(':') for item in d if item])
    keys = ['byr', 'iyr', 'eyr', 'hgt', 'hcl', 'ecl', 'pid']
    for key in keys:
        if key not in dd:
            return False
    return True

def count_passports(data: str) -> int:
    pport = {}
    s = ''
    count = 0
    passports = []
    for line in data.split('\n'):
        line = line.strip()
        if line == '':
            # process
            count += bool(process(s))
            passports.append(s)
            s = ''
        s += ' ' + line
    print(f"Valid passports: {count}")
    return count

if __name__ == '__main__':
    with open('input.txt') as f:
        data = f.read()
    count_passports(data)

Here I parsed the line out and transformed it into a Python dictionary in my process() function. Then I looped over the keys and verified that all the required ones were present. If any of the keys were not present, then  I returned False.

Part 2

This part of the challenge was quite a bit more complex. Now I had to code validation for the following:

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.

I took the most direct approach with this problem and used a series of conditional statements:

def process2(lines: str) -> bool:
    """
    Part 2
    """
    d = lines.split(' ')
    dd = dict([item.split(':') for item in d if item])

    keys = ['byr', 'iyr', 'eyr', 'hgt', 'hcl', 'ecl', 'pid']
    for key in keys:
        if key not in dd:
            return False

    if not any([dd[key] != 4 for key in ['byr', 'iyr', 'eyr']]):
        return False

    if '1920' > dd['byr'] or dd['byr'] > '2002':
        return False
    if dd['iyr'] < "2010" or dd['iyr'] > "2020":
        return False
    if dd['eyr'] < "2020" or dd['eyr'] > "2030":
        return False
    if not any(['in' in dd['hgt'], 'cm' in dd['hgt']]):
        return False
    if 'cm' in dd['hgt']:
        height = int(dd['hgt'][:-2])
        if height < 150 or height > 193:
            return False
    if 'in' in dd['hgt']:
        height = int(dd['hgt'][:-2])
        if height < 59 or height > 76:
            return False

    if '#' not in dd['hcl']:
        return False
    hcl = dd['hcl'].split('#')[-1]
    if len(hcl) != 6:
        return False

    try:
        int(hcl, 16)
    except ValueError:
        return False
    eye_colors = ['amb', 'blu', 'brn', 'gry', 'grn', 'hzl', 'oth']
    if dd['ecl'] not in eye_colors:
        return False
    if len(dd['pid']) != 9:
        return False

    if dd['pid'] == '182693366':
        print()
    return True


def count_passports(data: str) -> int:
    pport = {}
    s = ''
    count = 0
    passports = []
    for line in data.split('\n'):
        line = line.strip()
        if line == '':
            # process
            count += bool(process2(s))
            passports.append(s)
            s = ''
        s += ' ' + line
    print(f"Valid passports: {count}")
    return count

if __name__ == '__main__':
    with open('input.txt') as f:
        data = f.read()
    count_passports(data)

This works, but it is definitely a boring solution. I saw someone else use the voluptuous package to create a schema that they then used to validate everything. You could also create a class that does the verification steps too.

All my code is also on Github if you’d like to check it out.