Raku: Advent of Code 2020 - Day Five

Below is my current solution to the puzzle for Day 5 in the 2020 Advent of Code. Code commentary follows the code...

#!/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
) {
given $part {
when 1 {
say $input.lines.map({ seat_id($_) }).max;
}
when 2 {
my @vacant = ( (0..1023) (-) $input.lines.map({ seat_id($_) }) ).keys;
say @vacant.grep({ !( $_+1 ~~ any @vacant ) and !( $_-1 ~~ any @vacant) })[0];
}
}
}

sub seat_id (Str $code --> Int) {
return +('0b' ~ $code.trans(<F B R L> => <0 1 1 0>));
}

I enjoyed being able to use the set difference operator (-) to quickly find all the possible seat ids which were missing from the input. Instead of using the 3 character (-) operator, I could also have used the unicode ∖. 

And since we were guaranteed that our adjacent seats would be occupied... It was also fairly easy to filter the missing seats from vacant list by using grep and the any junction to exclude seats with an adjacent vacancy.

Originally my seat_id subroutine used a for loop for each character F vs. B and R vs L in each section of the seat code. And then performed a bit shift before moving on to the next character. Building up the number character by character.

sub seat_id (Str $code --> Int) {
my ($row, $col) = (0,0);
for $code.substr(0..6).comb -> $r {
$row = $row +< 1;
$row += 1 if $r eq 'B';
};
for $code.substr(7..9).comb -> $c {
$col = $col +< 1;
$col +=1 if $c eq 'R';
}
return $row * 8 + $col;
}

I knew this could be improved. After all B's and R's were really just 1's and everything else were 0's. So next, I replaced the body of the subroutine with:

return +('0b' ~ $code.comb.map({ /<[BR]>/ ?? 1 !! 0 }).join);

But why use three separate methods to split, map, and rejoin the string, when the .trans method can get it done by itself?

return +('0b' ~ $code.trans(<F B R L> => <0 1 1 0>));

Nice!

codesections has pointed out that this could also be written as:

:2(.trans(<F B R L> => <0 1 1 0>))

or

:2(TR/FBRL/0110/)

more concise at the expense of being perhaps a bit more obfuscated.


 
FWIW... The short version of the solution code without the boilerplate is:
sub seat_id (Str $code --> Int) { return +('0b' ~ $code.trans(<F B R L> => <0 1 1 0>)) }
my @vacant = ( (0..1023) (-) 'input'.IO.lines.map({ seat_id($_) }) ).keys;
say 'Part One: ' ~ 'input'.IO.lines.map({ seat_id($_) }).max;
say 'Part Two: ' ~ @vacant.grep({ !( $_+1 ~~ any @vacant ) and !( $_-1 ~~ any @vacant) })[0];



I learn a lot by reading other people's code. I particularly like the approach taken by mienaikage. Instead of checking that adjacent seats are not vacant, they noticed that all missing seats at the front and back are contiguous. So you can check the range from min .. max seat ids found in the input against all seat ids found in the input and get the set difference. The short version updated with this approach is perhaps even easier to read and understand:

given 'input'.IO.lines.map({ .trans(<F B R L> => <0 1 1 0>).parse-base(2) }).List  {
say "part one: { .max }";
say "part two: { .minmax $_ }"
}

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