Skip to content

Commit 5089068

Browse files
committed
Day 22 in Raku
1 parent eaad176 commit 5089068

9 files changed

+668
-0
lines changed

2021/day22/day22.raku

+154
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
#!/usr/bin/env raku
2+
# Copyright 2021 Google LLC
3+
#
4+
# Use of this source code is governed by an MIT-style
5+
# license that can be found in the LICENSE file or at
6+
# https://opensource.org/licenses/MIT.
7+
#
8+
# https://adventofcode.com/2021/day/22
9+
use v6.d;
10+
use fatal;
11+
12+
class Cuboid {
13+
has Range $.x;
14+
has Range $.y;
15+
has Range $.z;
16+
has Bool $.on;
17+
has Cuboid @.ignored;
18+
19+
method Str(Cuboid:D: --> Str) {
20+
"({$!x.gist},{$!y.gist},{$!z.gist} {$!on ?? 'on' !! 'off'} ignoring {+@!ignored})"
21+
}
22+
23+
method size(--> Int) { $!x.elems * $!y.elems * $!z.elems - [+] @!ignored».size }
24+
25+
method overlaps(Cuboid $other) {
26+
$!x.min <= $other.x.max && $!x.max >= $other.x.min &&
27+
$!y.min <= $other.y.max && $!y.max >= $other.y.min &&
28+
$!z.min <= $other.z.max && $!z.max >= $other.z.min
29+
}
30+
31+
method ignore(Cuboid $other) {
32+
return unless self.overlaps($other);
33+
.ignore($other) for @!ignored;
34+
@!ignored .= grep(*.size > 0);
35+
@!ignored.push($other.clamp(self));
36+
}
37+
38+
method clamp(Cuboid $bounds --> Cuboid) {
39+
my %axes;
40+
for <x y z> -> $axis {
41+
my $a = self."$axis"();
42+
my $b = $bounds."$axis"();
43+
%axes{$axis} = (max($a.min, $b.min))..(min($a.max, $b.max));
44+
}
45+
Cuboid.new(:x(%axes<x>), :y(%axes<y>), :z(%axes<z>), :$!on,
46+
:ignored(@!ignored.map(*.clamp($bounds))))
47+
}
48+
}
49+
50+
# Each line is "on|off x=range,y=range,z=range" where range is upper and lower
51+
# integers with .. separators.
52+
grammar InputFormat {
53+
rule TOP { <line>+ }
54+
token ws { <!ww>\h* }
55+
token num { \-?\d+ }
56+
token range { <num>\.\.<num> }
57+
token axis { <[ x y z ]> }
58+
token switch { on | off }
59+
rule line { ^^ <switch> (<axis>\=<range>) ** 3..3 % \, $$ \n }
60+
}
61+
62+
class Actions {
63+
method TOP($/) { make $<line>».made }
64+
method num($/) { make $/.Int }
65+
method range($/) { make ($<num>[0].made)..($<num>[1].made) }
66+
method axis($/) { make $/.Str }
67+
method switch($/) { make $/.Str }
68+
method line($/) {
69+
my %axes = $0.map({$_<axis>.made => $_<range>.made});
70+
make Cuboid.new(:x(%axes<x>), :y(%axes<y>), :z(%axes<z>), :on($<switch> eq 'on'))
71+
}
72+
}
73+
74+
# The input defines a sequence of switches in 3D space which are flipped on or off.
75+
class Solver {
76+
has Str $.input is required;
77+
has $.parsed = InputFormat.parse($!input, :actions(Actions.new)) || die 'Parse failed';
78+
has @.lines = $!parsed<line.made;
79+
80+
method run-cubes($clamp --> Int) {
81+
my @on;
82+
for @.lines -> $line {
83+
my $clamped = $clamp ?? $line.clamp($clamp) !! $line;
84+
next if $clamped.size == 0;
85+
.ignore($clamped) for @on;
86+
@on .= grep(*.size > 0);
87+
@on.push($clamped) if $line.on;
88+
}
89+
[+] @on».size
90+
}
91+
}
92+
93+
# Count the number of "on" switches ignoring anything outside the -50..50 ranges.
94+
class Part1 is Solver {
95+
method solve( --> Str(Cool)) {
96+
self.run-cubes(Cuboid.new(:x(-50..50), :y(-50..50), :z(-50..50), :on));
97+
}
98+
}
99+
100+
# Count the number of "on" switches everywhere.
101+
class Part2 is Solver {
102+
method solve( --> Str(Cool)) { self.run-cubes(Nil) }
103+
}
104+
105+
class RunContext {
106+
has $.input-file;
107+
has $.input;
108+
has %.expected is rw;
109+
has @.passed;
110+
111+
method run-part(Solver:U $part) {
112+
my $num = $part.^name.comb(/\d+/).head;
113+
my $expected = $.expected«$num» // '';
114+
say "Running Day22 part $num on $!input-file expecting '$expected'";
115+
my $start = now;
116+
my $solver = $part.new(:$!input);
117+
my $result = $solver.solve();
118+
my $end = now;
119+
put $result;
120+
"Part $num took %.3fms\n".printf(($end - $start) * 1000);
121+
@!passed.push($result eq 'TODO' || $expected && $expected eq $result);
122+
if $expected {
123+
if $expected eq $result {
124+
say "\c[CHECK MARK] PASS with expected value '$result'";
125+
} else {
126+
say "\c[CROSS MARK] FAIL expected '$expected' but got '$result'";
127+
}
128+
}
129+
$*OUT.flush;
130+
$*ERR.flush;
131+
}
132+
}
133+
134+
sub MAIN(*@input-files) {
135+
my $exit = all();
136+
for @input-files -> $input-file {
137+
if $input-file.IO.slurp -> $input {
138+
my $context = RunContext.new(:$input, :$input-file);
139+
if (my $expected-file = $input-file.IO.extension('expected')).f {
140+
for $expected-file.lines {
141+
$context.expected«$0» = $1.trim if m/part (\d+) \: \s* (.*)/;
142+
}
143+
}
144+
$context.run-part(Part1);
145+
say '';
146+
$context.run-part(Part2);
147+
$exit &= all($context.passed);
148+
} else {
149+
say "EMPTY INPUT FILE: $input-file";
150+
}
151+
say '=' x 40;
152+
}
153+
exit $exit ?? 0 !! 1;
154+
}

2021/day22/input.actual.expected

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
part1: 658691
2+
part2: 1228699515783640

0 commit comments

Comments
 (0)