Day 4! I did this challenge during lunch today.

Just let me relax on the beach already!

Just let me relax on the beach already!

I finished part 1 in 5:53, whole thing in 28:57. My big mistake in part 2; using re.match instead of re.fullmatch. I forgot that re.match only matches the start of a string, and doesn’t have to match the whole thing. This allowed a single invalid passport through the validation function. Had I used the right function, I would have finished the challenge in just under 20 minutes.

General structure notes: I think I did a pretty good job with structure on this challenge. I was able to reuse the validation function for the first puzzle with no changes, and I didn’t have any issues with parsing after writing it initially. I think one more function to abstract the passport ingest might be good, because I could see this getting reused later as part of another challenge. I added it after finishing the puzzle. For the more complicated validation criteria in part 2, I think the valid &= <condition> syntax is very readable. If there had been another layer or 2 of structure in the passport or conditions, I might have gone with a passport class + validation in the setters. That would have allowed for better error messages about what elements were invalid.

A thing that’s been working well is putting all the examples in their own input test file. I started doing it yesterday, and I think it’s been an effective strategy, because it takes only a single comment to change inputs. I think I’ll keep doing that as time goes on. I’ve been pondering a good strategy to add regressions for the inevitable code reuse in later. I’m leaning towards asserts. The usual arguments against asserts don’t really apply, because there won’t be any use of optimizations here. And they can be expressed in a single line, unlike a real test suite. The only downside I can think of is that they’ll run every time a particular file is run. Using pytest might be a good moderate option. But that’s a lot of effort for a time-limited test.

def is_valid_1(passport):
    valid = True

    for req in req_fields:
        valid &= (req in passport)

    return valid


def is_valid_2(passport):
    import re
    valid = is_valid_1(passport)

    valid &= (1920 <= int(passport.get('byr',0)) <= 2002)
    valid &= (2010 <= int(passport.get('iyr',0)) <= 2020)
    valid &= (2020 <= int(passport.get('eyr',0)) <= 2030)

    height = passport.get('hgt', '')
    try:
        heightval, heightunit = re.fullmatch('(\d+)(cm|in)', height).groups()
        if heightunit == 'cm':
            valid &= (150 <= int(heightval) <= 193)
        elif heightunit == 'in':
            valid &= (59 <= int(heightval) <= 76)
        else:
            valid &= False
    except AttributeError:
        valid &= False

    valid &= re.fullmatch('#[0-9a-f]{6}', passport.get('hcl', '')) is not None
    valid &= passport.get('ecl', '') in ('amb', 'blu', 'brn', 'gry', 'grn', 'hzl','oth')
    valid &= re.fullmatch('\d{9}', passport.get('pid', '')) is not None

    return valid

Github!