06ee9705a8
s:contains-char? to s:contains/char? s:contains-string? to s:contains/string? a:contains-string? to a:contains/string? old names are now deprecated and will be removed after 2021.7. FossilOrigin-Name: 5a19d7aac514c5ba87963c5f0645f3daa8a8e3dc04546c0627fa046479ecd8dd
239 lines
7.3 KiB
Forth
239 lines
7.3 KiB
Forth
# Day 4: Passport Processing
|
|
|
|
You arrive at the airport only to realize that you grabbed your
|
|
North Pole Credentials instead of your passport. While these
|
|
documents are extremely similar, North Pole Credentials aren't
|
|
issued by a country and therefore aren't actually valid
|
|
documentation for travel in most of the world.
|
|
|
|
It seems like you're not the only one having problems, though;
|
|
a very long line has formed for the automatic passport scanners,
|
|
and the delay could upset your travel itinerary.
|
|
|
|
Due to some questionable network security, you realize you might
|
|
be able to solve both of these problems at the same time.
|
|
|
|
The automatic passport scanners are slow because they're having
|
|
trouble detecting which passports have all required fields. The
|
|
expected fields are as follows:
|
|
|
|
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 validated in batch files (your puzzle input).
|
|
Each passport is represented as 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:
|
|
|
|
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
|
|
|
|
hcl:#ae17e1 iyr:2013
|
|
eyr:2024
|
|
ecl:brn pid:760753108 byr:1931
|
|
hgt:179cm
|
|
|
|
hcl:#cfa07d eyr:2025 pid:166559648
|
|
iyr:2011 ecl:brn hgt:59in
|
|
|
|
The first passport is valid - all eight fields are present. The
|
|
second passport is invalid - it is missing hgt (the Height field).
|
|
|
|
The third passport is interesting; the only missing field is cid,
|
|
so it looks like data from North Pole Credentials, not a passport
|
|
at all! Surely, nobody would mind if you made the system
|
|
temporarily ignore missing cid fields. Treat this "passport" as
|
|
valid.
|
|
|
|
The fourth passport is missing two fields, cid and byr. Missing
|
|
cid is fine, but missing any other field is not, so this passport
|
|
is invalid.
|
|
|
|
According to the above rules, your improved system would report
|
|
2 valid passports.
|
|
|
|
Count the number of valid passports - those that have all
|
|
required fields. Treat cid as optional. In your batch file, how
|
|
many passports are valid?
|
|
|
|
----
|
|
|
|
I first create two data structures: an array of passport
|
|
strings, and a buffer to hold the current passport being
|
|
read in.
|
|
|
|
The `Passports` array is constructed using the `buffer:`
|
|
words.
|
|
|
|
~~~
|
|
'Passports d:create #1024 allot
|
|
'Passport d:create #1024 allot
|
|
|
|
&Passports n:inc buffer:set
|
|
~~~
|
|
|
|
Importing the passports is pretty easy. There's a blank line after
|
|
each entry is complete, so just gather the text until then, then
|
|
save the string and record a pointer into the `Passports` listing.
|
|
|
|
~~~
|
|
:add-data '_ s:append &Passport s:append &Passport s:copy ;
|
|
:record drop &Passport s:keep buffer:add #0 !Passport ;
|
|
:completed? dup s:length n:zero? ;
|
|
:finish &Passports n:inc s:length !Passports ;
|
|
|
|
:passports:load
|
|
[ completed? [ record ]
|
|
[ add-data ] choose ] file:for-each-line finish ;
|
|
|
|
'input-day-4 passports:load
|
|
~~~
|
|
|
|
Now that the passport database is loaded, we can simply check
|
|
each entry for the required fields, increasing a counter for
|
|
each one that proves valid.
|
|
|
|
~~~
|
|
:valid?
|
|
[ [ 'byr: s:contains/string? ]
|
|
[ 'iyr: s:contains/string? ]
|
|
[ 'eyr: s:contains/string? ] tri and and ]
|
|
|
|
[ [ 'hgt: s:contains/string? ]
|
|
[ 'hcl: s:contains/string? ]
|
|
[ 'ecl: s:contains/string? ] tri and and ]
|
|
|
|
[ 'pid: s:contains/string? ] tri and and ;
|
|
|
|
#0 &Passports [ valid? [ n:inc ] if ] a:for-each
|
|
~~~
|
|
|
|
----
|
|
|
|
# Part Two
|
|
|
|
The line is moving more quickly now, but you overhear airport
|
|
security talking about how passports with invalid data are
|
|
getting through. Better add some data validation, quick!
|
|
|
|
You can continue to ignore the cid field, but each other
|
|
field has strict rules about what values are valid for
|
|
automatic validation:
|
|
|
|
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.
|
|
|
|
Your job is to count the passports where all required fields
|
|
are both present and valid according to the above rules. Here
|
|
are some example values:
|
|
|
|
byr valid: 2002
|
|
byr invalid: 2003
|
|
|
|
hgt valid: 60in
|
|
hgt valid: 190cm
|
|
hgt invalid: 190in
|
|
hgt invalid: 190
|
|
|
|
hcl valid: #123abc
|
|
hcl invalid: #123abz
|
|
hcl invalid: 123abc
|
|
|
|
ecl valid: brn
|
|
ecl invalid: wat
|
|
|
|
pid valid: 000000001
|
|
pid invalid: 0123456789
|
|
|
|
Here are some invalid passports:
|
|
|
|
... snipped ...
|
|
|
|
Count the number of valid passports - those that have all
|
|
required fields and valid values. Continue to treat cid as
|
|
optional. In your batch file, how many passports are valid?
|
|
|
|
----
|
|
|
|
I'll start this by defining some things. An array of valid eye
|
|
colors, and some words for validating ranges for particular
|
|
fields.
|
|
|
|
~~~
|
|
{ 'amb 'blu 'brn 'gry 'hzl 'oth 'grn } 'EYE-COLORS const
|
|
|
|
:birth-year? s:to-number #1920 #2002 n:between? ;
|
|
:issue-year? s:to-number #2010 #2020 n:between? ;
|
|
:expire-year? s:to-number #2020 #2030 n:between? ;
|
|
:height-imperial? #59 #76 n:between? ;
|
|
:height-metric? #150 #193 n:between? ;
|
|
~~~
|
|
|
|
I'll store a count of the number of valid fields in a variable.
|
|
|
|
~~~
|
|
'Valid var
|
|
:record-result [ &Valid v:inc ] if ;
|
|
~~~
|
|
|
|
Then I implement the checks for each field.
|
|
|
|
~~~
|
|
:process-byr [ s:length #4 eq? ]
|
|
[ birth-year? ] bi and record-result ;
|
|
:process-iyr [ s:length #4 eq? ]
|
|
[ issue-year? ] bi and record-result ;
|
|
:process-eyr [ s:length #4 eq? ]
|
|
[ expire-year? ] bi and record-result ;
|
|
:process-hgt [ [ c:digit? ] s:filter s:to-number ]
|
|
[ #2 s:right 'cm s:eq? ] bi
|
|
[ height-metric? ] [ height-imperial? ] choose record-result ;
|
|
:process-hcl [ fetch $# eq? ]
|
|
[ s:length #7 eq? ]
|
|
[ [ 'abcdef0123456789 swap s:contains/char? ] s:filter s:length #6 eq? ] tri
|
|
and and record-result ;
|
|
:process-ecl EYE-COLORS a:contains/string? record-result ;
|
|
:process-pid [ s:length #9 eq? ]
|
|
[ [ c:digit? ] s:filter s:length #9 eq? ] bi and record-result ;
|
|
~~~
|
|
|
|
Processing each passport is just a matter of splitting it up
|
|
into fields and checking each. The final number of valid
|
|
passports will be left on the stack.
|
|
|
|
~~~
|
|
:split s:trim ASCII:SPACE s:tokenize ;
|
|
:get-field $: s:tokenize [ ] a:for-each swap ;
|
|
|
|
:validate-field 'byr [ process-byr ] s:case
|
|
'iyr [ process-iyr ] s:case
|
|
'eyr [ process-eyr ] s:case
|
|
'hgt [ process-hgt ] s:case
|
|
'hcl [ process-hcl ] s:case
|
|
'ecl [ process-ecl ] s:case
|
|
'pid [ process-pid ] s:case
|
|
drop-pair ;
|
|
|
|
:validate #0 !Valid split [ get-field validate-field ] a:for-each ;
|
|
#0 &Passports [ dup valid? [ validate @Valid #7 eq? [ n:inc ] if ] [ drop ] choose ] a:for-each
|
|
~~~
|
|
|