Advent of Code 2020 - Day 4

Good Morning AoC!

Day 4 arrived. https://adventofcode.com/2020/day/4

Continuing our journey to our vacation, we arrived to the airport as planned. But I forgot my Passport and grabbed my NPC (North Pole Credentials). The NPC is not a valid travel document. But there is more problems, the line to pass the airport security. They installed new auto magic passport scanners and a lot of problems.

Well, looks like the airport did not invested in network security and we got access to scanners, and we can help to solve this line.

The root cause for scanners been so slow is because they’re having trouble detecting which passports have all required fields.

  • byr (Birth Year)

  • iyr (Issue Year)

  • eyr (Expiration Year)

  • hgt (Height)

  • hcl (Hair Color)

  • ecl (Eye Color)

  • pid (Passport ID)

  • cid (Country ID)

Passport data is stored in plain text files. Each passport is a sequence of key:value pairs separated by spaces or newlines. Passports are separated by blank lines.

Here is an example batch file containing four passports:

 1ecl:gry pid:860033327 eyr:2020 hcl:#fffffd
 2byr:1937 iyr:2017 cid:147 hgt:183cm
 3
 4iyr:2013 ecl:amb cid:350 eyr:2023 pid:028048884
 5hcl:#cfa07d byr:1929
 6
 7hcl:#ae17e1 iyr:2013
 8eyr:2024
 9ecl:brn pid:760753108 byr:1931
10hgt:179cm
11
12hcl:#cfa07d eyr:2025 pid:166559648
13iyr:2011 ecl:brn hgt:59in
plain

We have to fix the validation. But we have to do a hack here, changing the cid field to optional, turning the NPC a valid document. Naughty me.

Task 1 - Fix the scanners

To solve this task I have to prepare all data to processing. First a simple string.split by "\n\n" (blank line) to get each passport. Then for each passport I perform various string.replace to format the password data as a JSON string. And finally, convert the JSON string to a Python dict object.

1import json 
2
3lines = [json.loads(json_string.replace(', ""}', '}')) for json_string in [
4	'{"' + line.replace('\n', ' ').replace(' ', '", "').replace(':', '": "') + '"}' 
5	for line in downloaded.GetContentString().replace('\r', '').split('\n\n')
6]]
python

Now we have all passport data in a list of dict objects each one representing a different passport or NPC.

Second step is to validate if all required fields are present on the document data. The implementation of the in operator by the list object supports key validation. So, it is just matter of create a List Comprehension to do this.

1valid_passports = [passport for passport in lines if all(
2	k in passport for k in ["byr", "iyr", "eyr", "hgt", "hcl", "ecl", "pid"])]
3print(len(valid_passports)
python

The all function in the conditional part of our List Comprehension is a built-in function that checks if all items in a list are True.

Task 1 done! Another 🌟 in the pocket!

Task 2 - Fix the scanner fix 🤦‍♂️

Long story short, We have to validate the data from the document, not only the required fields. The current rules 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. ¯_( ͡° ͜ʖ ͡°)_/¯

Ok, now we have business logic, how am I going to do to keep up with my challenge? Instead of create a simple loop and a clusterfuck of ifs, we can use some Python tricks to achieve this task. Python functions (def, generators and lambdas) can be assigned to variables. 🤔

What about create a dict object (key value structure) where all required fields are keys and the validation function is the value?

 1import re
 2MANDATORY_FIELDS = {
 3    "byr": lambda s: 1920 <= int(s) <= 2002, # Birth Year
 4    "iyr": lambda s: 2010 <= int(s) <= 2020, # Issue Year
 5    "eyr": lambda s: 2020 <= int(s) <= 2030, # Expiration Year
 6    "hgt": lambda s: (re.match(r'^(\d{2,})(in|cm)$', s) and 
 7                      ((150 if s[-2:] == 'cm' else 59) <= int(s[:-2]) <= 
 8                      (193 if s[-2:] == 'cm' else 76)), # Height
 9                     )  
10    "hcl": lambda s: re.match(r'^#[\da-f]{6}$', string), # Hair Color
11    "ecl": lambda s: s in {"amb", "blu", "brn", "gry", "grn", "hzl", "oth"}, # Eye 
12    "pid": lambda s: re.match(r'^\d{9}$', s), # Passport ID
13}
python

A special attention for the use of regular expressions to validate the string format and a short circuit in height validation. Now let’s create our List Comprehension (again?!) to validate all passport data using the all validation functions stored in the dict object.

1strict_valid_passports = [passport for passport in valid_passports if all(
2	func(passport[key]
3) for key, func in MANDATORY_FIELDS.items())]
4
5print(strict_valid_passports)
python

In this solution we can see that is possible to extract from a list two or more variables, that is really useful when you are processing data from tuples.

Task 2 done! 😷

See you tomorrow folks.

Cheers

Z

Comments