add advent of code examples (2020, days 1-5) (re #27)
FossilOrigin-Name: 54a80479319c1ce4c27fdae1169b09159fb624f6d10c5224a338273ec1ea160a
This commit is contained in:
parent
e61c0364df
commit
8b21ab556a
5 changed files with 811 additions and 0 deletions
106
example/advent-of-code-2020-day-1.retro
Normal file
106
example/advent-of-code-2020-day-1.retro
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
# Day 1: Report Repair
|
||||||
|
|
||||||
|
After saving Christmas five years in a row, you've decided
|
||||||
|
to take a vacation at a nice resort on a tropical island.
|
||||||
|
Surely, Christmas will go on without you.
|
||||||
|
|
||||||
|
The tropical island has its own currency and is entirely
|
||||||
|
cash-only. The gold coins used there have a little picture
|
||||||
|
of a starfish; the locals just call them stars. None of the
|
||||||
|
currency exchanges seem to have heard of them, but somehow,
|
||||||
|
you'll need to find fifty of these coins by the time you
|
||||||
|
arrive so you can pay the deposit on your room.
|
||||||
|
|
||||||
|
To save your vacation, you need to get all fifty stars by
|
||||||
|
December 25th.
|
||||||
|
|
||||||
|
Collect stars by solving puzzles. Two puzzles will be made
|
||||||
|
available on each day in the Advent calendar; the second
|
||||||
|
puzzle is unlocked when you complete the first. Each puzzle
|
||||||
|
grants one star. Good luck!
|
||||||
|
|
||||||
|
Before you leave, the Elves in accounting just need you to
|
||||||
|
fix your expense report (your puzzle input); apparently,
|
||||||
|
something isn't quite adding up.
|
||||||
|
|
||||||
|
Specifically, they need you to find the two entries that
|
||||||
|
sum to 2020 and then multiply those two numbers together.
|
||||||
|
|
||||||
|
For example, suppose your expense report contained the
|
||||||
|
following:
|
||||||
|
|
||||||
|
1721
|
||||||
|
979
|
||||||
|
366
|
||||||
|
299
|
||||||
|
675
|
||||||
|
1456
|
||||||
|
|
||||||
|
In this list, the two entries that sum to 2020 are 1721
|
||||||
|
and 299. Multiplying them together produces
|
||||||
|
1721 \* 299 = 514579, so the correct answer is 514579.
|
||||||
|
|
||||||
|
Of course, your expense report is much larger. Find the
|
||||||
|
two entries that sum to 2020; what do you get if you
|
||||||
|
multiply them together?
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
The first step is to get the input into something I
|
||||||
|
can work with. My sample input has 200 entries, so I
|
||||||
|
can fit them all on the stack, which means it's easy
|
||||||
|
to create an array with them.
|
||||||
|
|
||||||
|
Put your input into a file named `input-day-1`.
|
||||||
|
|
||||||
|
~~~
|
||||||
|
{ 'input-day-1 [ s:to-number ] file:for-each-line }
|
||||||
|
'INPUT const
|
||||||
|
~~~
|
||||||
|
|
||||||
|
And then a simple approach is to just loop through the
|
||||||
|
input twice, looking for any two values that add up to
|
||||||
|
2020.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
~~~
|
||||||
|
:2020? dup-pair + #2020 eq? ;
|
||||||
|
:display dup-pair * n:put nl ;
|
||||||
|
|
||||||
|
INPUT [ INPUT [ 2020? [ display ] if drop ] a:for-each drop ] a:for-each
|
||||||
|
~~~
|
||||||
|
|
||||||
|
The second part is just slightly different:
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
The Elves in accounting are thankful for your help; one
|
||||||
|
of them even offers you a starfish coin they had left
|
||||||
|
over from a past vacation. They offer you a second one
|
||||||
|
if you can find three numbers in your expense report that
|
||||||
|
meet the same criteria.
|
||||||
|
|
||||||
|
Using the above example again, the three entries that
|
||||||
|
sum to 2020 are 979, 366, and 675. Multiplying them
|
||||||
|
together produces the answer, 241861950.
|
||||||
|
|
||||||
|
In your expense report, what is the product of the
|
||||||
|
three entries that sum to 2020?
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
For this, I just add an additional iteration through the
|
||||||
|
input. This can be *slow*, but does work. To save time,
|
||||||
|
I `abort` after a match is found, so processing stops at
|
||||||
|
the first match.
|
||||||
|
|
||||||
|
~~~
|
||||||
|
:dup-three 'abc 'abcabc reorder ;
|
||||||
|
:2020? dup-three + + #2020 eq? ;
|
||||||
|
:display dup-three * * n:put nl abort ;
|
||||||
|
|
||||||
|
:inner INPUT [ 2020? [ display ] if drop ] a:for-each drop ;
|
||||||
|
|
||||||
|
INPUT [ INPUT [ inner ] a:for-each drop ] a:for-each
|
||||||
|
~~~
|
143
example/advent-of-code-2020-day-2.retro
Normal file
143
example/advent-of-code-2020-day-2.retro
Normal file
|
@ -0,0 +1,143 @@
|
||||||
|
# Day 2: Password Philosophy
|
||||||
|
|
||||||
|
Your flight departs in a few days from the coastal
|
||||||
|
airport; the easiest way down to the coast from
|
||||||
|
here is via toboggan.
|
||||||
|
|
||||||
|
The shopkeeper at the North Pole Toboggan Rental
|
||||||
|
Shop is having a bad day. "Something's wrong with
|
||||||
|
our computers; we can't log in!" You ask if you can
|
||||||
|
take a look.
|
||||||
|
|
||||||
|
Their password database seems to be a little
|
||||||
|
corrupted: some of the passwords wouldn't have been
|
||||||
|
allowed by the Official Toboggan Corporate Policy
|
||||||
|
that was in effect when they were chosen.
|
||||||
|
|
||||||
|
To try to debug the problem, they have created a
|
||||||
|
list (your puzzle input) of passwords (according
|
||||||
|
to the corrupted database) and the corporate
|
||||||
|
policy when that password was set.
|
||||||
|
|
||||||
|
For example, suppose you have the following list:
|
||||||
|
|
||||||
|
1-3 a: abcde
|
||||||
|
1-3 b: cdefg
|
||||||
|
2-9 c: ccccccccc
|
||||||
|
|
||||||
|
Each line gives the password policy and then the
|
||||||
|
password. The password policy indicates the lowest
|
||||||
|
and highest number of times a given letter must
|
||||||
|
appear for the password to be valid. For example,
|
||||||
|
`1-3 a` means that the password must contain `a` at
|
||||||
|
least 1 time and at most 3 times.
|
||||||
|
|
||||||
|
In the above example, 2 passwords are valid. The
|
||||||
|
middle password, cdefg, is not; it contains no
|
||||||
|
instances of b, but needs at least 1. The first and
|
||||||
|
third passwords are valid: they contain one a or
|
||||||
|
nine c, both within the limits of their respective
|
||||||
|
policies.
|
||||||
|
|
||||||
|
How many passwords are valid according to their
|
||||||
|
policies?
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
This is an easy problem. Just a little parsing and reduction.
|
||||||
|
|
||||||
|
`range` is defined to parse the limits of the character. This
|
||||||
|
just means splitting the string and converting the pieces to
|
||||||
|
a number.
|
||||||
|
|
||||||
|
The `reduce` word is slighly more complex. It takes the
|
||||||
|
password and the character and constructs a combinator for
|
||||||
|
use in filtering out other characters.
|
||||||
|
|
||||||
|
The remaining characters will be counted and compared to
|
||||||
|
the range.
|
||||||
|
|
||||||
|
Basically, an input line like:
|
||||||
|
|
||||||
|
1-3 a: abcde
|
||||||
|
|
||||||
|
Becomes:
|
||||||
|
|
||||||
|
'abcde [ $a eq? ] s:filter s:length #1 #3 n:between?
|
||||||
|
|
||||||
|
At this point, I just add up the flags (-1 for TRUE), and
|
||||||
|
use `n:abs` to get the final count.
|
||||||
|
|
||||||
|
~~~
|
||||||
|
:range
|
||||||
|
#0 a:fetch $- s:tokenize [ s:to-number ] a:for-each ;
|
||||||
|
|
||||||
|
:reduce
|
||||||
|
[ #2 a:fetch ] [ #1 a:fetch fetch &eq? curry ] bi s:filter ;
|
||||||
|
|
||||||
|
:process (na-n)
|
||||||
|
[ reduce s:length ] [ range ] bi n:between? + ;
|
||||||
|
|
||||||
|
#0 'input-day-2 [ ASCII:SPACE s:tokenize process ] file:for-each-line
|
||||||
|
n:abs
|
||||||
|
~~~
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
# Part 2
|
||||||
|
|
||||||
|
While it appears you validated the passwords
|
||||||
|
correctly, they don't seem to be what the Official
|
||||||
|
Toboggan Corporate Authentication System is expecting.
|
||||||
|
|
||||||
|
The shopkeeper suddenly realizes that he just
|
||||||
|
accidentally explained the password policy rules from
|
||||||
|
his old job at the sled rental place down the street!
|
||||||
|
The Official Toboggan Corporate Policy actually works
|
||||||
|
a little differently.
|
||||||
|
|
||||||
|
Each policy actually describes two positions in the
|
||||||
|
password, where 1 means the first character, 2 means
|
||||||
|
the second character, and so on. (Be careful; Toboggan
|
||||||
|
Corporate Policies have no concept of "index zero"!)
|
||||||
|
Exactly one of these positions must contain the given
|
||||||
|
letter. Other occurrences of the letter are irrelevant
|
||||||
|
for the purposes of policy enforcement.
|
||||||
|
|
||||||
|
Given the same example list from above:
|
||||||
|
|
||||||
|
1-3 a: abcde is valid: position 1 contains a and position 3 does not.
|
||||||
|
1-3 b: cdefg is invalid: neither position 1 nor position 3 contains b.
|
||||||
|
2-9 c: ccccccccc is invalid: both position 2 and position 9 contain c.
|
||||||
|
|
||||||
|
How many passwords are valid according to the new
|
||||||
|
interpretation of the policies?
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
So in this variation, I need to check two positions per
|
||||||
|
password.
|
||||||
|
|
||||||
|
Being lazy here, I'm using `reorder` a couple of times
|
||||||
|
to restructure the stack. The only thing to remember is
|
||||||
|
that indexing is one based, so the word that extracts
|
||||||
|
the positions needs to decrement to adjust for Retro's
|
||||||
|
zero based indexing.
|
||||||
|
|
||||||
|
~~~
|
||||||
|
:grab-characters
|
||||||
|
'abc 'abac reorder + fetch [ + fetch ] dip ;
|
||||||
|
|
||||||
|
:check
|
||||||
|
'abc 'abac reorder eq? [ eq? ] dip xor + ;
|
||||||
|
|
||||||
|
:positions
|
||||||
|
#0 a:fetch $- s:tokenize [ s:to-number n:dec ] a:for-each ;
|
||||||
|
|
||||||
|
:process (na-n)
|
||||||
|
[ #1 a:fetch fetch ] [ #2 a:fetch ] [ positions ] tri
|
||||||
|
grab-characters check ;
|
||||||
|
|
||||||
|
#0 'input-day-2 [ ASCII:SPACE s:tokenize process ] file:for-each-line
|
||||||
|
n:abs
|
||||||
|
~~~
|
178
example/advent-of-code-2020-day-3.retro
Normal file
178
example/advent-of-code-2020-day-3.retro
Normal file
|
@ -0,0 +1,178 @@
|
||||||
|
# Day 3: Toboggan Trajectory
|
||||||
|
|
||||||
|
With the toboggan login problems resolved, you set off
|
||||||
|
toward the airport. While travel by toboggan might be
|
||||||
|
easy, it's certainly not safe: there's very minimal
|
||||||
|
steering and the area is covered in trees. You'll need
|
||||||
|
to see which angles will take you near the fewest trees.
|
||||||
|
|
||||||
|
Due to the local geology, trees in this area only grow on
|
||||||
|
exact integer coordinates in a grid. You make a map (your
|
||||||
|
puzzle input) of the open squares (.) and trees (#) you can
|
||||||
|
see. For example:
|
||||||
|
|
||||||
|
..##.......
|
||||||
|
#...#...#..
|
||||||
|
.#....#..#.
|
||||||
|
..#.#...#.#
|
||||||
|
.#...##..#.
|
||||||
|
..#.##.....
|
||||||
|
.#.#.#....#
|
||||||
|
.#........#
|
||||||
|
#.##...#...
|
||||||
|
#...##....#
|
||||||
|
.#..#...#.#
|
||||||
|
|
||||||
|
These aren't the only trees, though; due to something you
|
||||||
|
read about once involving arboreal genetics and biome
|
||||||
|
stability, the same pattern repeats to the right many times:
|
||||||
|
|
||||||
|
..##.........##.........##.........##.........##.......
|
||||||
|
#...#...#..#...#...#..#...#...#..#...#...#..#...#...#..
|
||||||
|
.#....#..#..#....#..#..#....#..#..#....#..#..#....#..#.
|
||||||
|
|
||||||
|
You start on the open square (.) in the top-left corner and
|
||||||
|
need to reach the bottom (below the bottom-most row on your
|
||||||
|
map).
|
||||||
|
|
||||||
|
The toboggan can only follow a few specific slopes (you opted
|
||||||
|
for a cheaper model that prefers rational numbers); start by
|
||||||
|
counting all the trees you would encounter for the slope right
|
||||||
|
3, down 1:
|
||||||
|
|
||||||
|
From your starting position at the top-left, check the
|
||||||
|
position that is right 3 and down 1. Then, check the position
|
||||||
|
that is right 3 and down 1 from there, and so on until you go
|
||||||
|
past the bottom of the map.
|
||||||
|
|
||||||
|
The locations you'd check in the above example are marked here
|
||||||
|
with O where there was an open square and X where there was a
|
||||||
|
tree:
|
||||||
|
|
||||||
|
..##.........##.........##.........##.........##.......
|
||||||
|
#..O#...#..#...#...#..#...#...#..#...#...#..#...#...#..
|
||||||
|
.#....X..#..#....#..#..#....#..#..#....#..#..#....#..#.
|
||||||
|
|
||||||
|
In this example, traversing the map using this slope would
|
||||||
|
cause you to encounter 7 trees.
|
||||||
|
|
||||||
|
Starting at the top-left corner of your map and following a
|
||||||
|
slope of right 3 and down 1, how many trees would you
|
||||||
|
encounter?
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
This will need more memory than a default Retro build has,
|
||||||
|
so start by building a version with a much larger memory
|
||||||
|
footprint and support for 64-bit cells:
|
||||||
|
|
||||||
|
make CFLAGS="-DIMAGE_SIZE=8000000 -O2 -DBIT64"
|
||||||
|
|
||||||
|
I am doing this in a simple way, constructing a large enough
|
||||||
|
map that I can just iterate through.
|
||||||
|
|
||||||
|
Some math.
|
||||||
|
|
||||||
|
My input file has 323 lines with 31 characters per line. With
|
||||||
|
a horizontal distance of 3 per row, the last position will be
|
||||||
|
at 969 characters out. So the lines need to be a little over
|
||||||
|
31 times longer than they are by default.
|
||||||
|
|
||||||
|
The easy way to do this is to duplicate the pointer and use
|
||||||
|
`s:append` to concat them until I get it long enough. Five
|
||||||
|
iterations is plenty, but I'll do a bit more since I suspect
|
||||||
|
that I'll need a larger map for the second half.
|
||||||
|
|
||||||
|
~~~
|
||||||
|
:extend (s-s)
|
||||||
|
#7 [ dup s:append ] times s:keep ;
|
||||||
|
~~~
|
||||||
|
|
||||||
|
We also need to raise the limit on temporary string size.
|
||||||
|
|
||||||
|
~~~
|
||||||
|
#4096 !TempStringMax
|
||||||
|
~~~
|
||||||
|
|
||||||
|
With this, I can create an array for the inputs:
|
||||||
|
|
||||||
|
~~~
|
||||||
|
'input-day-3 'INPUT s:const
|
||||||
|
|
||||||
|
(Count_the_number_of_lines)
|
||||||
|
#0 INPUT [ drop n:inc ] file:for-each-line
|
||||||
|
|
||||||
|
(Create_the_array,_and_reserve_space)
|
||||||
|
'MAP d:create dup , allot
|
||||||
|
|
||||||
|
(Fill_in_the_array)
|
||||||
|
MAP n:inc INPUT [ extend over store n:inc ] file:for-each-line drop
|
||||||
|
~~~
|
||||||
|
|
||||||
|
Now all that's left is to quickly iterate through it, increasing a
|
||||||
|
counter for the horizontal distance.
|
||||||
|
|
||||||
|
~~~
|
||||||
|
#0 #0 MAP [ over + fetch $# eq? [ &n:inc dip ] if #3 + ] a:for-each
|
||||||
|
drop n:put nl
|
||||||
|
~~~
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
# Part 2
|
||||||
|
|
||||||
|
Time to check the rest of the slopes - you need to minimize the
|
||||||
|
probability of a sudden arboreal stop, after all.
|
||||||
|
|
||||||
|
Determine the number of trees you would encounter if, for each
|
||||||
|
of the following slopes, you start at the top-left corner and
|
||||||
|
traverse the map all the way to the bottom:
|
||||||
|
|
||||||
|
Right 1, down 1.
|
||||||
|
Right 3, down 1. (This is the slope you already checked.)
|
||||||
|
Right 5, down 1.
|
||||||
|
Right 7, down 1.
|
||||||
|
Right 1, down 2.
|
||||||
|
|
||||||
|
In the above example, these slopes would find 2, 7, 3, 4, and
|
||||||
|
2 tree(s) respectively; multiplied together, these produce the
|
||||||
|
answer 336.
|
||||||
|
|
||||||
|
What do you get if you multiply together the number of trees
|
||||||
|
encountered on each of the listed slopes?
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
This is pretty much the same, except for the last one, which
|
||||||
|
needs to ignore every other line.
|
||||||
|
|
||||||
|
I start by just running through each of the trivial slopes,
|
||||||
|
reducing down the products.
|
||||||
|
|
||||||
|
~~~
|
||||||
|
'Right var
|
||||||
|
|
||||||
|
:calculate
|
||||||
|
!Right
|
||||||
|
#0 #0 MAP [ over + fetch $# eq? [ &n:inc dip ] if @Right + ] a:for-each
|
||||||
|
drop ;
|
||||||
|
|
||||||
|
{ #1 #3 #5 #7 } #1 [ calculate * ] a:reduce
|
||||||
|
~~~
|
||||||
|
|
||||||
|
Then create a new MAP, discarding the odd lines.
|
||||||
|
|
||||||
|
~~~
|
||||||
|
{ #0 MAP [ swap dup n:odd? [ nip ] if n:inc ] a:for-each } 'MAP const
|
||||||
|
~~~
|
||||||
|
|
||||||
|
And finally run through the final slope, multiplying by the
|
||||||
|
other products to get the final answer.
|
||||||
|
|
||||||
|
~~~
|
||||||
|
#0 #0 MAP [ over + fetch $# eq? [ &n:inc dip ] if #1 + ] a:for-each
|
||||||
|
drop *
|
||||||
|
|
||||||
|
n:put nl
|
||||||
|
~~~
|
||||||
|
|
239
example/advent-of-code-2020-day-4.retro
Normal file
239
example/advent-of-code-2020-day-4.retro
Normal file
|
@ -0,0 +1,239 @@
|
||||||
|
# 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
|
||||||
|
~~~
|
||||||
|
|
145
example/advent-of-code-2020-day-5.retro
Normal file
145
example/advent-of-code-2020-day-5.retro
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
# Day 5: Binary Boarding
|
||||||
|
|
||||||
|
You board your plane only to discover a new problem: you dropped
|
||||||
|
your boarding pass! You aren't sure which seat is yours, and all
|
||||||
|
of the flight attendants are busy with the flood of people that
|
||||||
|
suddenly made it through passport control.
|
||||||
|
|
||||||
|
You write a quick program to use your phone's camera to scan all
|
||||||
|
of the nearby boarding passes (your puzzle input); perhaps you
|
||||||
|
can find your seat through process of elimination.
|
||||||
|
|
||||||
|
Instead of zones or groups, this airline uses binary space
|
||||||
|
partitioning to seat people. A seat might be specified like
|
||||||
|
FBFBBFFRLR, where F means "front", B means "back", L means
|
||||||
|
"left", and R means "right".
|
||||||
|
|
||||||
|
The first 7 characters will either be F or B; these specify
|
||||||
|
exactly one of the 128 rows on the plane (numbered 0 through
|
||||||
|
127). Each letter tells you which half of a region the given
|
||||||
|
seat is in. Start with the whole list of rows; the first
|
||||||
|
letter indicates whether the seat is in the front (0 through
|
||||||
|
63) or the back (64 through 127). The next letter indicates
|
||||||
|
which half of that region the seat is in, and so on until
|
||||||
|
you're left with exactly one row.
|
||||||
|
|
||||||
|
For example, consider just the first seven characters of
|
||||||
|
FBFBBFFRLR:
|
||||||
|
|
||||||
|
Start by considering the whole range, rows 0 through 127.
|
||||||
|
F means to take the lower half, keeping rows 0 through 63.
|
||||||
|
B means to take the upper half, keeping rows 32 through 63.
|
||||||
|
F means to take the lower half, keeping rows 32 through 47.
|
||||||
|
B means to take the upper half, keeping rows 40 through 47.
|
||||||
|
B keeps rows 44 through 47.
|
||||||
|
F keeps rows 44 through 45.
|
||||||
|
The final F keeps the lower of the two, row 44.
|
||||||
|
|
||||||
|
The last three characters will be either L or R; these
|
||||||
|
specify exactly one of the 8 columns of seats on the
|
||||||
|
plane (numbered 0 through 7). The same process as above
|
||||||
|
proceeds again, this time with only three steps. L means
|
||||||
|
to keep the lower half, while R means to keep the upper
|
||||||
|
half.
|
||||||
|
|
||||||
|
For example, consider just the last 3 characters of FBFBBFFRLR:
|
||||||
|
|
||||||
|
Start by considering the whole range, columns 0 through 7.
|
||||||
|
R means to take the upper half, keeping columns 4 through 7.
|
||||||
|
L means to take the lower half, keeping columns 4 through 5.
|
||||||
|
The final R keeps the upper of the two, column 5.
|
||||||
|
|
||||||
|
So, decoding FBFBBFFRLR reveals that it is the seat at row
|
||||||
|
44, column 5.
|
||||||
|
|
||||||
|
Every seat also has a unique seat ID: multiply the row by 8,
|
||||||
|
then add the column. In this example, the seat has ID
|
||||||
|
44 \* 8 + 5 = 357.
|
||||||
|
|
||||||
|
Here are some other boarding passes:
|
||||||
|
|
||||||
|
BFFFBBFRRR: row 70, column 7, seat ID 567.
|
||||||
|
FFFBBBFRRR: row 14, column 7, seat ID 119.
|
||||||
|
BBFFBBFRLL: row 102, column 4, seat ID 820.
|
||||||
|
|
||||||
|
As a sanity check, look through your list of boarding passes.
|
||||||
|
What is the highest seat ID on a boarding pass?
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
This is actually *really* easy. In this case, the boarding passes
|
||||||
|
ultimately correspond directly to a binary value for the seat id.
|
||||||
|
So, all that's needed is to convert the boarding pass to a string
|
||||||
|
representation of the binary value, then convert that to an actual
|
||||||
|
number.
|
||||||
|
|
||||||
|
Since Retro is decmal only, I started by bringing in some code to
|
||||||
|
work with other bases.
|
||||||
|
|
||||||
|
~~~
|
||||||
|
'Base var
|
||||||
|
|
||||||
|
:binary #2 !Base ;
|
||||||
|
:decimal #10 !Base ;
|
||||||
|
|
||||||
|
{{
|
||||||
|
'0123456789ABCDEF 'DIGITS s:const
|
||||||
|
'Number var
|
||||||
|
'Mod var
|
||||||
|
:convert (c-) &DIGITS swap s:index-of
|
||||||
|
@Number @Base * + !Number ;
|
||||||
|
:check-sign (s-s) dup fetch $- eq?
|
||||||
|
[ #-1 !Mod n:inc ] [ #1 !Mod ] choose ;
|
||||||
|
---reveal---
|
||||||
|
:s:to-number<with-base> (s-n)
|
||||||
|
#0 !Number check-sign [ convert ] s:for-each @Number @Mod * ;
|
||||||
|
}}
|
||||||
|
~~~
|
||||||
|
|
||||||
|
Then the rest was trivial.
|
||||||
|
|
||||||
|
~~~
|
||||||
|
:c:to-binary
|
||||||
|
$F [ $0 ] case
|
||||||
|
$B [ $1 ] case
|
||||||
|
$L [ $0 ] case
|
||||||
|
$R [ $1 ] case ;
|
||||||
|
:s:to-binary-rep [ c:to-binary ] s:map ;
|
||||||
|
:convert s:to-binary-rep binary s:to-number<with-base> ;
|
||||||
|
|
||||||
|
#0 'input-day-5 [ convert n:max ] file:for-each-line
|
||||||
|
n:put nl
|
||||||
|
~~~
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
# Part Two ---
|
||||||
|
|
||||||
|
Ding! The "fasten seat belt" signs have turned on. Time to find
|
||||||
|
your seat.
|
||||||
|
|
||||||
|
It's a completely full flight, so your seat should be the only
|
||||||
|
missing boarding pass in your list. However, there's a catch:
|
||||||
|
some of the seats at the very front and back of the plane don't
|
||||||
|
exist on this aircraft, so they'll be missing from your list as
|
||||||
|
well.
|
||||||
|
|
||||||
|
Your seat wasn't at the very front or back, though; the seats
|
||||||
|
with IDs +1 and -1 from yours will be in your list.
|
||||||
|
|
||||||
|
What is the ID of your seat?
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
This is also really easy. I just fill in a list of seats to see
|
||||||
|
which are used, then ignore the empty ones at the start before
|
||||||
|
returning the first empty one in the middle.
|
||||||
|
|
||||||
|
~~~
|
||||||
|
'Seats d:create #1024 allot
|
||||||
|
'input-day-5 [ convert &Seats + v:on ] file:for-each-line
|
||||||
|
&Seats n:inc [ fetch-next n:zero? ] while
|
||||||
|
[ fetch-next n:zero? ] until &Seats - n:put nl
|
||||||
|
~~~
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue