Raku: Advent of Code 2020 - Day Four

The code below which solves the puzzle for Day Four of Advent of Code is very different from my first working version. Given today's Raku Advent Calendar post on Grammars, I decided to throw out my initial code and write a solution using grammars.

And after reading the 1st post for this year's Raku Advent Calendar by codesections, I decided to include the little bit of boilerplate which enables Raku's default command line interface. Today's script should show the influence of other Raku AoC solutions in the advent-of-raku-2020 github repository. Special mention goes out to mienaikage, who has most heavily influenced the code which follows.

[04.raku]

#!/usr/bin/env raku
use v6.d;

sub MAIN (
IO() :$input where *.f = $?FILE.IO.sibling('input'),
Int :$part where * == 1|2 = 1, # Solve Part One or Part Two?
--> Nil
) { ... }

Executed without any command line parameters it defaults to the equivalent of:

    $ 04.raku --input=input --part=1
where *.f will check that the input file exists (or default to 'input')
where * == 1|2 will cry fould if anything other than 1 or 2 is passed (or default to 1)

If Invalid parameter values are passed we automatically get a nice usage message:

    Usage:
      04b.raku [--input=<Any where { ... }>] [--part=<Int where { ... }>]

The Not bad for a couple lines of optional boilerplate.

Getting back to grammars. -The grammar rabbit hole is taking me longer than anticipated. I have eventually been able to arrive at a working solution. Mostly by shame facedly lifting liberally from mienaikage's solution when I got stuck.

I do not yet fully grok grammars... So rather than skipping a post today, I will post the current state of affairs. And then come back and revise this post and the code below as I come further up to speed on grammars.


#!/usr/bin/env raku
use v6.d;
#use Grammar::Tracer;

my @fields = <byr cid ecl eyr hcl hgt iyr pid>;

grammar Passport1 {
token TOP { [ <field> ':' \S+ ] ** 0..* % \s+ }
token field { @fields }
}

grammar Passport2 {
token TOP { <field> ** 0..* % \s+ }
token fs { ':' }
token year4d { <.digit> ** 4 }
token byr { 'byr' <.fs> (<.year4d>) <?{ 1920 <= $/[0].Int <= 2002 }> }
token cid { 'cid' <.fs> \S+ }
token ecl { 'ecl' <.fs> [ amb || blu || brn || gry || grn || hzl || oth ] }
token eyr { 'eyr' <.fs> (<.year4d>) <?{ 2020 <= $/[0].Int <= 2030 }> }
token hcl { 'hcl' <.fs> '#' <.xdigit> ** 6 }
token hgt { 'hgt' <.fs> [ ( <.digit> ** 3 ) <?{ 150 <= $/[0].Int <= 193 }> 'cm'
|| ( <.digit> ** 2 ) <?{ 59 <= $/[0].Int <= 76 }> 'in' ] }
token iyr { 'iyr' <.fs> (<.year4d>) <?{ 2010 <= $/[0].Int <= 2020 }> }
token pid { 'pid' <.fs> <digit> ** 9 }
token field { ( <byr> || <cid> || <ecl> || <eyr> || <hcl> || <hgt> || <iyr> || <pid> )
}
}

sub MAIN (
IO() :$input where *.f = $?FILE.IO.sibling('input'),
Int :$part where * == 1|2 = 1, # Solve Part One or Part Two?
--> Nil
) {
my @batch = $input.slurp.split("\n\n", :skip-empty);
if $part == 1 {
@batch.grep({ Passport1.parse(.trim)<field>>>.Str @fields <cid> }).elems.say;
} else {
@batch.grep({Passport2.parse(.trim)<field>
.grep({.defined})
.map({.subst(/\:.*/)}) @fields <cid> }).elems.say;
}
}

Comments

Popular posts from this blog

Raku: Setting up Raku (for Contributors)

Raku: Advent of Code 2020 - Day Twelve

Raku: Advent of Code 2020 - Day Thirteen