SPOILER ALERT
This is a post with my solutions and learnings from the puzzle. Don't continue
reading if you haven't tried the puzzle on your own yet.
If you want to do the puzzle, visit adventofcode.com/2020/day/4.
My programming language of choice is python
and all examples below are in python.
Key learnings
- Debugging and testing
Todays puzzle has a number of specific requirements to follow. The key learning I took away from it too handle edge cases, follow specification and having a systematic way of debugging. A good practice would to create tests for the edge cases to ensure everything works as expected.
Puzzle
The challenge is to validate data for a thousand passport.
Example input for two 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
They fields are defined as:
- byr (Birth Year)
- iyr (Issue Year)
- eyr (Expiration Year)
- hgt (Height)
- hcl (Hair Color)
- ecl (Eye Color)
- pid (Passport ID)
- cid (Country ID)
Part 1
All fields except for Country ID (cid) are mandatory.
Validate that all mandatory fields are present.
Parse input
First step is to save the input in a local file and parse it in python:
# Open the input file
inputfile = open('04.input', 'r')
# Parse lines
data = [x.strip() for x in inputfile.readlines()]
Solution
def part1(data):
valid_passport_count = 0
required_fields = ['byr' ,'iyr' ,'eyr' ,'hgt' ,'hcl' ,'ecl' ,'pid']
# Variable to track number of required fields for current passport
current = 0
for line in data:
if line == '': # Empty line indicates new passport
if current == len(required_fields):
valid_passport_count += 1
current = 0
continue
for field in line.split():
key, val = field.split(':')
if key in required_fields:
current += 1
return valid_passport_count
print "Solution part 1: %d" % part1(data)
Part 2
The second part adds validation for each field. The requirements are:
- 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.
My Solution
This part will have as many variations of solutions as there are developers. The important part is to read carufully the requirements and test against edge-cases. I did my testing continuously by printing relevant variables while coding. The solution below is therefore not the whole "solution" as I see the process as important as the final code.
def valid(passport):
# Validate mandatory fields
fields = ['byr' ,'iyr' ,'eyr' ,'hgt' ,'hcl' ,'ecl' ,'pid']
for f in fields:
if(f not in passport):
return False
# Validate numerical
if not ( 1920 <= int(passport['byr']) <= 2002):
return False
if not ( 2010 <= int(passport['iyr']) <= 2020):
return False
if not ( 2020 <= int(passport['eyr']) <= 2030):
return False
# Validate Height
if 'cm' in passport['hgt'] and not (150 <= int(passport['hgt'][:-2]) <=193):
return False
elif 'in' in passport['hgt'] and not (59 <= int(passport['hgt'][:-2]) <= 76):
return False
if 'cm' not in passport['hgt'] and 'in' not in passport['hgt']:
return False
# Validate strings/enums
if passport['ecl'] not in ['amb', 'blu', 'brn','gry','grn','hzl','oth']:
return False
if re.match(r'^\#[0-9a-f]{6}$', passport['hcl']) is None:
return False
if re.match(r'^\d{9}$', passport['pid']) is None:
return False
return True
def part2(data):
valid_passport_count = 0
current = {}
for line in data:
if line == '':
if valid(current):
valid_passport_count += 1
current = {}
continue
for field in line.split():
field,val = field.split(':')
current[field] = val
return valid_passport_count
Comments
One edge-case I missed was the passport-id with 9 digits. I forgot to add start and end notation on the regex. A 10-digit passport-id was valid as it did in fact contain a 9-digit number. This off by one error was hard for me to find. The lesson learned is to test edge-cases.
Though it is not practical in a "competition" environment. Therefore I tested by using print
continuously to track what got passed and what didn't.
Thanks for reading!
I hope these solutions were helpful for you. Just ask if anything was hard to grasp.
Complete code can be found at: github.com/cNille/AdventOfCode/blob/master/2020/04.py
Top comments (10)
I re-used and extended the parser combinators I wrote for day 2 (see, that investment is paying off already!) but I'm not entirely happy with my solution. My instinct was Parse don't validate but with AoC you never know what's coming in part 2. The bit about cheating the system in the initial description made me think the second part might have to deal with the invalid records in some way. So I kept the
PasswordData
as a simpleHashMap
for maximum flexibility. Now I know the full requirements I might come back later and implement a proper parse-don't-validate approach.I'm guessing this is rust? I'm impressed by your structure! It's clear to understand the code and nice that you have tests! Hopefully I'll learn to add more tests as well some day 🙏 haha
Thanks. Yes, it's Rust. I like to approach these problems as if I was producing production code. That's the only type worth practicing, right? :-)
Only with regex
Really clean solution! Thanks for sharing! 🦄
Hello Christopher,
I just wanted to let you know that in your section with validate height you would run into some edge cases with the last if statement.
For example for my quiz input this solution wouldn't work.
You should consider the if elif and append an else at the end to catch all remaining cases.
If you want to highlight your python code here on dev.to just write python after the three backticks ;)
Cheers,
Christian
Thanks for the tip on highlighting! :D
An
else
wouldn't let the flow through to validate theValidate strings/enums
, so would have to reorder the code then. Could you share what the case is in your input that I don't catch? :)Hello Christopher,
I can only tell that your solution is off by 1 compared to mine at my input file.
I use this code right here:
My console output:
Maybe I have time tomorrow to find the specific case for you.
Cheers ;)
If anyone interested in Ruby solutions, here is my 2 cents. I like the simplicity of Ruby.
There's a flaw in your part 1, it assumes the last entry is invalid.
There is no new line at the EOF, so the last entry is not evaluated by your code.