AOC 2020 - Day 4
Day 4! I did this challenge during lunch today.
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