Raku: Advent of Code 2020 - Day Eleven

The puzzle for Day Eleven of Advent of Code involved a cellular automaton related to where people prefer to sit in a waiting room based on whether adjacent seats are empty or occupied.

My Raku solution follows. My initial solution worked, albeit slowly and while leaking memory. So instead of re-inventing a wheel poorly, I looked up the Raku example for the Game of Life on Rosetta Code... And reworked it to solve both parts of today's puzzles. [Updates to include code commentary will follow tomorrow]

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

class Automaton {
subset Layout of Str where {
# all rows same length and contain only . L or #
.lines>>.chars.unique == 1 and m/^^<[.L#]>+$$/
}
has Int ($.width, $.height);
has Bool $.changed is rw = False;
has Int $.tolerance is rw = 4;
has @.a;

multi method new (Layout $s, Int $tolerance? = 4) {
self.new(
:width( .pick.chars ),
:height( .elems ),
:a( .map({ [.comb] }) ),
:$tolerance
)
given $s.lines.cache;
}

method gist { join "\n", map { .join }, self.a }

method is_occupied (Int $r, Int $c, $d --> Bool) {
# out-of-bounds equals empty
return False unless ?(0 <= $r < self.height and 0 <= $c < self.width);

do given self.a[$r][$c] {
when '#' { True }
when 'L' { False }
when '.' { False }
}
}

method adj_occupied (Int $r, Int $c --> Int) {
+( (-1, -1), (-1, 0), (-1, +1),
( 0, -1), ( 0, +1),
( 1, -1), ( 1, 0), ( 1, +1)
).map({ self.is_occupied($r+.[0], $c+.[1], $_) }).grep({ ?$_ });
}

method seats_occupied {
self.a.map({ @$_.grep('#').elems }).sum;
}

# provide postfix ++
method succ {
my Bool $changed = False;
my $o = self.new(
:$!width,
:$!height,
:a(gather for ^self.height -> $r { take [
gather for ^self.width -> $c {
take do given self.a[$r][$c] {
# If empty (L) and no adjacent seats occupied -> becomes occupied
# If occupied (#) and 4+ adjacent seats occupied -> becomes empty
when '.' { '.' }
when 'L' {
if self.adj_occupied($r, $c) == 0 {
$changed = True;
'#';
} else {
'L';
}
}
when '#' {
if self.adj_occupied($r, $c) >= self.tolerance {
$changed = True;
'L';
} else {
'#';
}
}
}
}
]} ),
:$!tolerance
);
$o.changed = $changed if $changed;
$o;
}
}

class Automaton2 is Automaton {
method is_occupied (Int $r, Int $c, $d --> Bool) {
# out-of-bounds equals empty
return False unless ?(0 <= $r < self.height and 0 <= $c < self.width);

do given self.a[$r][$c] {
when '#' { True }
when 'L' { False }
when '.' { self.is_occupied($r+$d[0], $c+$d[1], $d) }
}
}
}

my $input = q:to/EOF/;
L.LL.LL.LL
LLLLLLL.LL
L.L.L..L..
LLLL.LL.LL
L.LL.LL.LL
L.LLLLL.LL
..L.L.....
LLLLLLLLLL
L.LLLLLL.L
L.LLLLL.LL
EOF

$input = 'input'.IO.slurp;
my $part1 = Automaton.new($input);
$part1.changed = True;
while ($part1.changed == True) {
say $part1++;
say '--';
}
say "";

my $part2 = Automaton2.new($input, 5);
$part2.changed = True;
while ($part2.changed == True) {
say $part2++;
say '--';
}

say "Part One: " ~ $part1.seats_occupied;
say "Part Two: " ~ $part2.seats_occupied;

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