Download the raw code.
use v6;
# Solution to problem 3 of
# http://strangelyconsistent.org/blog/masaks-perl-6-coding-contest
# by Moritz Lenz, 2010-12-10
class SignedRange {
has ($.start, $.end, $.including);
method ACCEPTS($other) {
$.start <= $other <= $.end;
}
}
grammar Ranges::Grammar {
token TOP { ^ :s [<range> <.ws>]* $ }
rule range { <sign>\[ <start=int> '..' <end=int> \] }
token int { <.sign>? \d+ }
token sign { <[+\-]> }
}
class Ranges::Actions {
method TOP($/) { make $<range>ยป.ast };
method range($/) {
make SignedRange.new(
start => $<start>.Int,
end => $<end>.Int,
including => $<sign> eq '+',
);
}
}
sub parse-and-range-check(Cool $range-spec, Cool $tester) {
my $r = Ranges::Grammar.parse($range-spec, :actions(Ranges::Actions));
return "invalid input" unless $r && $tester ~~ m:s/^ \-? \d+ $/;
return 'no' unless $r.ast && $r.ast.[0];
given $r.ast.reverse.first({ $tester.Int ~~ $^x }) {
return $_ && .including ?? 'yes' !! 'no';
}
}
# the rest of the code is "just" tests. To run them,
# call this script with the 'test' argument:
# $ perl6 code test
multi MAIN() {
say parse-and-range-check get, get;
}
multi MAIN('test') {
use Test;
plan *;
ok my $r = Ranges::Grammar.parse('+[-12..34]', :actions(Ranges::Actions)),
"can parse string";
isa_ok $r.ast.[0], SignedRange, 'got the right object';
is $r.ast.[0].start, -12, 'right start';
is $r.ast.[0].end, 34, 'right end';
ok $r.ast.including, 'and it is including';
for '',
' ',
'+[-12..34]-[1..-3]',
' +[ -12 .. 34 ] -[ 1 .. -3 ]'
-> $x {
ok Ranges::Grammar.parse($x), "Can parse string '$x'";
}
for '+[]', '+[3..]', '+-[3..5]', '+[3.4..5]' -> $x {
nok Ranges::Grammar.parse($x), "Correctly failed to parse '$x'";
}
is parse-and-range-check('', ''), 'invalid input', 'needs a number';
is parse-and-range-check('', '4'), 'no', 'No ranges => "no"';
is parse-and-range-check('+[1..5]', '4'), 'yes', '4 in +[1..5]';
is parse-and-range-check('+[1..5] -[2..4]', '1'), 'yes', '+ and -';
is parse-and-range-check('+[1..5] -[2..4]', '2'), 'no', '+ and -';
is parse-and-range-check('-[2..4] +[1..5]', '1'), 'yes', '- and +';
is parse-and-range-check('-[2..4] +[1..5]', '2'), 'yes', '- and +';
is parse-and-range-check('-[2..4] +[1..5]', '-2'), 'no', '- and +';
done_testing;
}
# vim: set ft=perl6
This was the first submission I got, a couple of hours after the contest was announced. I still think it's the nicest one (across all tasks) in terms of readability.
Very minor nit: I would have preferred the adjective $.inclusive
to the
participle $.including
.
I think the comment # the rest of the code is "just" tests.
on line 43 is
misplaced.
This algorithm is linear, but it one-ups all the other submissions, including those that got the algorithm wrong, by being twice as fast on average. It all happens on line 38.
Giving your type an ACCEPTS
method and then smartmatching on line 38? Nice!
Like someone setting up a chain of dominoes, this code defines a class, a grammar, one more class, a sub, and then just does this:
multi MAIN() {
say parse-and-range-check get, get;
}
By that point, we're on line 49, not even breaking a sweat trying to keep things short. This is why I love Perl 6.