Advent of Code Day 4 - validating regex
Todays task (esp part 2) was the task the most resembles my daily work...
Part 1
For parsing the input I decided to also go the string/regex way. I concatenate the whole input into on long string, replace an empty line (i.e. two newlines) with a record seperator (|
, pipe), replace all remaning spaces and newlines (\s
) with a field seperator (;
), and then split the long string into an array on the record seperator.
my $in = join('',<STDIN>);
$in =~ s/\n\n/SEP/gm;
$in =~ s/\s/;/g;
my @d = split(/SEP/,$in);
my $valid;
for my $l (@d) {
my %p = map { split /:/ } split( /;/, $l);
$valid++ if (keys %p 8 || (keys %p 7 && !$p{cid}));
}
say $valid;
Now we can convert each line (eyr:2039;hcl:#cfa07d;hgt:171cm
) into an hash, by first splitting on the field seperator and then on the key/value seperator (:
).
Then we only need to check if the hash has either 8 keys, or 7 keys, none of which shall be cid
;
Now that I'm writing this I realized that I could choose :
as the field seperator, and thus do not need the two splits when converting a line to the hash, again using one of the slightly confusing features of Perl that is actually very powerful. You can assign a list to either an array or an hash, and if you do the latter, Perl will use one value for the key and the other for the value:
my %hash = ( "a", "apple", "b", "banana" ); # { a => 'apple', b => 'banana' }
So we don't need to first split into fields and then into key/value, we can do this in one go by replacing this
my %p = map { split /:/ } split( /;/, $l);
with
my %p = split( /:/, $l);
Which leads to bit more light golfing and:
Part 1 - No spaces
$_=join('',<STDIN>);s/\n\n/|/gm;s/\s/:/g;for(split/\|/){my%p=split/:/;$a++if(keys%p==8||(keys%p==7&&!$p{cid}))}say$a
Not as good as APL, but we're getting there :-)
Part 2
Now we just have to implement a few more validators, which translate very straight forward from the spec to some regex and value checks:
my $in = join('',<STDIN>);
$in =~ s/\n\n/SEP/gm;
$in =~ s/\s/;/g;
my @d = split(/SEP/,$in);
my $valid = 0;
for my $l (@d) {
my %p = map { split /:/ } split( /;/, $l);
next unless (keys %p 8 || (keys %p 7 && !$p{cid}));
next unless $p{byr} =~ /^\d{4}$/ && $p{byr} >= 1920 && $p{byr} <= 2002;
next unless $p{iyr} =~ /^\d{4}$/ && $p{iyr} >= 2010 && $p{iyr} <= 2020;
next unless $p{eyr} =~ /^\d{4}$/ && $p{eyr} >= 2020 && $p{eyr} <= 2030;
next unless $p{hcl} =~ /^#[0-9a-f]{6}$/;
next unless $p{ecl} =~ /^(amb|blu|brn|gry|grn|hzl|oth)$/;
next unless $p{pid} =~ /^\d{9}$/;
if ($p{hgt} =~ /^(\d\d\d)cm$/) {
next unless $1 >= 150 && $1 <= 193;
}
elsif ($p{hgt} =~ /^(\d\d)in$/) {
next unless $1 >= 59 && $1 <= 76;
}
else {
next;
}
$valid++;
}
say $valid;
One small "trick" was to test for height last, so we don't have to check for other invalid heights. I lost a few minutes here because I wrote two if-blocks (for centimeter and inches), which discard all valid passports. After using the valid example data and some debugging (see this commit) I found my error and submitted a valid result.
Stats & Links
- First 4 digit rank (for second task)!
- Time: 10:55 / 09:27
- Rank: 11732 / 7688
- https://adventofcode.com/2020/day/4
- https://github.com/domm/adventofcode2020