Continues from previous week.
Welcome to the Raku review for Week 047 of the Weekly Challenge! For a quick overview, go through the original tasks and recap of the weekly challenge.
Updates
2020-Feb-24 › Arne Sommer sent me the source code for his Number::Roman
library, which I’ve now reviewed.
2020-Feb-24 › Review added for Laurent Rosenfeld’s second solution. Sorry I missed it!
Getting in Touch
Email › Email me (Ryan) with any feedback about this review.
GitHub › Submit a pull request for any issues you may find with this page.
Twitter › Join the discussion on Twitter!
We’d greatly appreciate any feedback you’d like to give.
Table of Contents
Task 1
[ Arne Sommer | Jaldhar H. Vyas | Javier Luque | Kevin Colyer | Laurent Rosenfeld | Luca Ferrari | Mark Anderson | Markus Holzer | Noud Aldenhoven | Ruben Westerberg | Simon Proctor | Ulrich Rieke ]
Task 2
[ Alicia Bielsa | Arne Sommer | Athanasius | Colin Crain | Jaldhar H. Vyas | Javier Luque | Kevin Colyer | Laurent Rosenfeld | Luca Ferrari | Mark Anderson | Markus Holzer | Noud Aldenhoven | Ruben Westerberg | Ryan Thompson | Simon Proctor | Ulrich Rieke ]
Blogs
Task #1 - Roman Calculator
The first task is to write a script that accepts two Roman numbers and an arithmetic operator. It should then calculate the result and return it as another Roman number. For example:
perl ch-1.pl V + VI
XI
The Raku solutions for this task, while lengthy at an average of 66 lines, are still shorter than the Perl solutions by about 1/3. This is a very crude metric, but it feels about right, combined with my own sense of expressiveness in the Raku solutions.
A few people used modules, several re-used their solutions from Week 010, while the rest wrote all new Roman number routines.
Arne Sommer
2020-Feb-24: Arne sent me the source code for his Number::Roman
library. I will add my thoughts, below.
Arne Sommer’s solution uses a modified version of his Roman number code from Week 10, which Arne has put into a Number::Roman
library (not included with his submission) now available on Arne’s blog.
The library itself uses MONKEY-TYPING
pragma, which allows one to augment
existing classes. This can be dangerous, so the Raku docs recommend against it. I’m confident Arne knows this. Still, it allows Arne to build a convenient class whereby any Int
can be converted from-roman
, and any Str
can be converted to-roman
, with base
support as well:
use MONKEY-TYPING;
augment class Int {
method roman {
return to-roman(self);
}
multi method base("r") {
return self.roman;
}
}
augment class Str {
method from-roman {
return from-roman(self);
}
multi method parse-base("r") {
return self.from-roman;
}
}
Arne has also included a full OO implementation that has add
, sub
, mul
and div
methods. There are also more examples and variations. I can’t show it all here, but it’s available on his blog.
Back to the ch-1 solution, here is the main logic:
use Number::Roman :to, :from;
unit sub MAIN (Str $first, Str $operator, Str $second);
my $f = from-roman($first);
my $s = from-roman($second);
given $operator
{
when '+' { say to-roman($f + $s) };
when '-' { say to-roman($f - $s) };
when 'x' { say to-roman($f * $s) };
when '*' { say to-roman($f * $s) };
when '/' { say to-roman(Int($f) / Int($s)) };
}
given ... when
is a good choice here. Arne also provides some other solutions, such as one using multi MAIN
subs instead, which I’ll just show the signatures of:
multi MAIN (Str $first, "+", Str $second);
multi MAIN (Str $first, "-", Str $second);
multi MAIN (Str $first, '*', Str $second);
multi MAIN (Str $first, 'x', Str $second);
multi MAIN (Str $first, "/", Str $second);
Arne’s blog does have a code listing for Number::Roman
, which I would highly suggest reading, as he discusses several interesting aspects of this challenge and his solution.
Blog › The Roman Gap with Raku
Jaldhar H. Vyas
Jaldhar H. Vyas’s solution seems to mirror his Perl solution, whereby numbers go through a multi-step process of unprefix
, reorder
and normalize
steps:
sub unprefix(Str $num) {
my $unprefixed = $num;
my @from = qw/ CM CD XC XL IX IV /;
my @to = qw/ DCCCC CCCC LXXXX XXXX VIIII IIII /;
for 0 ..^ @from.elems -> $i {
$unprefixed = $unprefixed.subst(@from[$i], @to[$i], :g);
}
return $unprefixed;
}
sub reorder(Str $num) {
my %order = (
'M' => 0, 'D' => 1, 'C' => 2, 'L' => 3, 'X' => 4, 'V' => 5, 'I' => 6
);
return $num.comb.sort({ %order{$^a} <=> %order{$^b} }).join;
}
sub normalize(Str $num) {
my $normalized = $num;
my @from = qw/ IIIII IIII VV VIV XXXXX XXXX LL LXL CCCCC CCCC DD DCD /;
my @to = qw/ V IV X IX L XL C XC D CD M CM /;
for 0 ..^ @from.elems -> $i {
$normalized = $normalized.subst(@from[$i], @to[$i], :g);
}
return $normalized;
}
The MAIN
sub accepts the two numbers and an $op
, +
or -
. Pay close attention to how Jaldhar implements the operations:
multi sub MAIN(
Str $num1, #= Number in Roman numerals
Str $op where { /\+/ || /\-/ }, #= Arithmetic operation (+ or -)
Str $num2, #= Number in Roman numerals
) {
if $op eq '+' {
say normalize(reorder(unprefix($num1) ~ unprefix($num2)));
} elsif $op eq '-' {
my $un1 = unprefix($num1);
my $un2 = unprefix($num2);
while ($un2.chars) {
($un1, $un2) = largestOrder(|removeCommon($un1, $un2));
}
say normalize($un1);
}
}
These operations are very interesting, in that they don’t actually convert the Roman numbers to Arabic at any point! I believe Jaldhar is the only one to do it this way in this challenge.
For example, with the addition operator, Jaldhar simply concatenates the two Roman numbers before passing them through his normalization process. This works because his lower level subs are, by design, extremely permissive in what they accept, and the normalize
sub ensures the answer is put in proper Roman form.
Similarly, the subtraction operator works by expanding the numbers and removing common substrings.
Javier Luque
Javier Luque’s solution uses a dispatch table to implement the operations:
sub MAIN($equation) {
my %operators = (
'+' => -> $a , $b { rtoa($a) + rtoa($b) },
'-' => -> $a , $b { rtoa($a) - rtoa($b) },
'x' => -> $a , $b { rtoa($a) * rtoa($b) },
'/' => -> $a , $b { rtoa($a) / rtoa($b) },
);
if ($equation ~~ /^(\w+)\s*(\+|\-|x|\/)\s*(\w+)$/) {
say ator(%operators{$1}($0.Str, $2.Str).Int);
} else {
say "Invalid input";
}
}
Then, citing inspiration from the Perl 6 Examples site, Javier uses a multi
sub, rtoa
, to convert from Roman to Arabic, shown here, in part:
# Inspired by:
# https://examples.p6c.dev/categories/euler/prob089-andreoss.html
multi rtoa() { 0 }
multi rtoa(Str $r where $r.chars > 1 ) {
rtoa(| $r.comb)
}
multi rtoa('I', |a) { 1 + rtoa(|a) }
multi rtoa('I', 'V', |a) { 4 + rtoa(|a) }
multi rtoa('V', |a) { 5 + rtoa(|a) }
...
multi rtoa('C', 'M', |a) { 900 + rtoa(|a) }
multi rtoa('M', |a) { 1000 + rtoa(|a) }
The corresponding ator
sub uses given ... when
:
sub ator(Int $n) returns Str {
given $n {
when $n >= 1000 { 'M' ~ ator($n- 1000) }
when $n >= 900 { 'CM' ~ ator($n - 900) }
...
when $n >= 4 { 'IV' ~ ator($n - 4) }
when $n >= 1 { 'I' ~ ator($n - 1) }
default { '' }
}
}
Blog › 047 – Perl Weekly Challenge
Kevin Colyer
Kevin Colyer’s solution has toRoman
and fromRoman
subs that use arrays, and iterate over them pairwise. Here is the toRoman
sub::
sub toRoman($i is copy) {
my @t=[1000 , "M", 900 , "CM", 500 , "D", 400 , "CD", 100 , "C", 90 ,
"XC", 50 , "L", 40 , "XL", 10 , "X", 9 , "IX", 5 , "V", 4 , "IV", 1 , "I"];
my $rn;
return "Error - Negative Value" if $i < 0;
while $i {
for @t -> $d,$n {
if $i-$d>=0 {
$rn~=$n;
$i-=$d;
last;
}
}
}
return $rn;
}
Here, Kevin opportunistically subtracts the maximum value possible each time through the loop, and appends the Roman numeral to the result string.
Kevin’s main logic uses a given ... when
block to handle the four basic arithmetic operators:
#| Roman Numberal Calculator - Numeral1 +-/* Numeral2 (/ and * require quoting)
sub MAIN(Str $numeral1, Str $operation, Str $numeral2) {
my $n1=fromRoman($numeral1);
my $n2=fromRoman($numeral2);
given $operation {
when '+' { toRoman($n1+$n2).say };
when '-' { toRoman($n1-$n2).say };
when '*' { toRoman($n1*$n2).say };
when '/' { toRoman(Int($n1/$n2)).say };
default { say "Unknown operation '$operation'" }
}
}
Laurent Rosenfeld
Laurent Rosenfeld’s solution reuses the from-roman
and to-roman
code from his Week 10 solution. It wasn’t reviewed at that time, though, so I’ll highlight the from-roman
sub here:
subset Roman-str of Str where $_ ~~ /^<[IVXLCDMivxlcdm]>+$/;
my %rom-tab = < I 1 V 5 X 10 L 50 C 100 D 500 M 1000
IV 4 IX 9 XL 40 XC 90 CD 400 CM 900 >;
my @ordered_romans = reverse sort { %rom-tab{$_} }, keys %rom-tab;
sub from-roman (Roman-str $roman) {
my $numeric = 0;
my $prev_letter = "M";
for $roman.uc.comb -> $letter {
$numeric -= 2 * %rom-tab{$prev_letter}
if %rom-tab{$letter} > %rom-tab{$prev_letter};
$numeric += %rom-tab{$letter};
$prev_letter = $letter;
}
return $numeric;
}
I really like the use of the Roman-str
subset here, serving as argument validation. The sub itself comb
s over the input string, and if the next letter is larger (i.e., it has a larger value as a Roman numeral), then the current letter must be subtracted. Otherwise, it is added. Laurent’s logic is to always add the number, and then subtract it twice if the next letter is larger.
Laurent’s main logic uses string EVAL
, after validating the input, and supports any expression with at least two terms:
my @input;
for @*ARGS {
push @input, $_ if /<[-+*\/]>/;
push @input, from-roman $_ if m:i/<[ivxlcdm]>+/;
}
die "Need at least three parameters" if @input < 3; # we need at least 1 operator and two operands
my $result = EVAL join ' ', @input;
say "@*ARGS[] = ", to-roman $result;
Blog › Roman Calculator and Gapful Numbers
Luca Ferrari
Luca Ferrari’s solution has an interesting approach in his convert-roman-to-arabic
sub:
my %roman-to-arabic = :I(1), :V(5), :X(10), :L(50), :C(100), :D(500), :M(1000);
# Function to convert a roman number into an arabic one.
sub convert-roman-to-arabic( Str:D $roman ) {
my @arabic-digits = $roman.uc.comb.reverse.map: {
state $last = 0;
my $value = %roman-to-arabic{ $_ };
$value *= -1 if $value < $last;
$last = $value;
$value;
};
return [+] @arabic-digits;
}
Luca first reverses the string’s character array, then finds the value of each letter. If that value is less than the previous one, that means we’re in a subtractive part of the number (like IX
), so Luca flips the sign. The result is the sum of all of these values.
The main logic uses a do given ... when
block to feed the $result
, which is then converted back to a Roman number with convert-arabic-to-roman
.
die "Usage: $*PROGRAM <operand> <operator> <operand>" if @*ARGS.elems != 3;
my $operand-a = convert-roman-to-arabic( @*ARGS[0] );
my $operand-b = convert-roman-to-arabic( @*ARGS[2] );
my $result = do given @*ARGS[1].trim {
when '+' { $operand-a + $operand-b; }
when '-' { $operand-a - $operand-b; }
when '/' { $operand-a / $operand-b; }
when '*' { $operand-a * $operand-b; }
};
say convert-arabic-to-roman( $result );
Blog › Roman Number Calculator and Gapful Numbers
Mark Anderson
Mark Anderson’s solution provides two classes, that use grammars to assist with conversion. I’ll show one of the classes here, but the other works similarly:
grammar Roman2Arabic {
token TOP {
:my $*Arabic;
<Thousands>? <Hundreds>? <Tens>? <Ones>?
}
token Thousands { [M ** 1..3] }
token Hundreds { [C [C ** 1..2|D|M]?] || [D [C ** 1..3]?] }
token Tens { [X [X ** 1..2|C|L]?] || [L [X ** 1..3]?] }
token Ones { [I [I ** 1..2|V|X]?] || [V [I ** 1..3]?] }
}
grammar Arabic2Roman {
token TOP {
:my $*Roman;
<Thousands>? <Hundreds>? <Tens>? <Ones>?
}
token Thousands { <[123]> <?before \d ** 3> }
token Hundreds { \d <?before \d ** 2> }
token Tens { \d <?before \d> }
token Ones { \d }
}
class Roman2ArabicActions {
method TOP ($/) {
make $*Arabic;
}
method Thousands ($/) {
$*Arabic = 1000 * $/.chars;
}
method Hundreds ($/) {
state %lookup = <C CC CCC CD D DC DCC DCCC CM> Z=> [1 .. 9];
$*Arabic += %lookup{ $/ } * 100;
}
method Tens ($/) {
state %lookup = <X XX XXX XL L LX LXX LXXX XC> Z=> [1 .. 9];
$*Arabic += %lookup{ $/ } * 10;
}
method Ones ($/) {
state %lookup = <I II III IV V VI VII VIII IX> Z=> [1 .. 9];
$*Arabic += %lookup{ $/ };
}
}
There is also a calculator class that does all of the work of parsing and processing the operations:
grammar Calculator {
rule TOP {
:my @*Arabics;
:my $*Answer;
:my $*Operator;
<Roman> <Operator> <Roman>
}
token Roman { <[I V X L C D M]>+ }
token Operator { <[+ x / -]> }
}
class CalculatorActions {
method TOP ($/) {
make $*Answer if $*Answer;
}
method Roman ($match) {
push(@*Arabics, Roman2Arabic.parse($match, :actions(
Roman2ArabicActions)).made);
if ($*Operator) {
given $*Operator {
when "+" { $*Answer = [+] @*Arabics }
when "-" { $*Answer = [-] @*Arabics }
when "x" { $*Answer = [*] @*Arabics }
when "/" { $*Answer = [/] @*Arabics }
}
$*Answer = Arabic2Roman.parse($*Answer, :actions(
Arabic2RomanActions)).made;
}
}
method Operator ($/) {
$*Operator = $/;
}
}
After all that, the main logic is one statement:
sub MAIN(*@args) {
Calculator.parse(@args.Str, :actions(CalculatorActions)).made.say;
}
Markus Holzer
Markus Holzer’s solution implements additive Roman numbers only, meaning 9 = VIIII
. The short length of Markus’ solution really underscores how much more code is required to support the subtractive numbers:
subset Roman of Str where * ~~ / ^ M* D* C * L* X* V* I* $ /;
my @r = :M(1000), :D(500), :C(100), :L(50), :X(10), :V(5), :I(1);
my %r = @r.Hash;
sub r2d( $r is copy ) {
$r .= uc;
%r{$r} || [+] $r.comb.map({ %r{$_} });
}
sub d2r( $d is copy ) {
[~] gather while $d > 0 {
my ($k, $n) = @r.first( *.value <= $d ).kv;
take $k;
$d -= $n;
}
}
Markus also uses a multi
main sub to parse the arithmetic operators.
multi sub MAIN(Roman $n, '+', Roman $m) { say d2r( $n.&r2d + $m.&r2d ) }
multi sub MAIN(Roman $n, '-', Roman $m) { say d2r( $n.&r2d - $m.&r2d ) }
multi sub MAIN(Roman $n, "×", Roman $m) { say d2r( $n.&r2d × $m.&r2d ) }
multi sub MAIN(Roman $n, '÷', Roman $m) { say d2r( ($n.&r2d ÷ $m.&r2d).Int ) }
Noud Aldenhoven
Noud Aldenhoven’s solution defines a @roman-symbols
array that can then be iterated over pairwise:
my @roman-symbols = [
1_000, "M",
900, "CM",
500, "D",
...
4, "IV",
1, "I"
];
sub to-roman($i) {
for @roman-symbols -> $k, $v {
if ($i >= $k) {
return $v ~ to-roman($i - $k);
}
}
return '';
}
The %operators
are a dispatch table:
my %operators =
'+' => { $_[0] + $_[1] },
'*' => { $_[0] * $_[1] },
'-' => { $_[0] - $_[1] },
'/' => { Int($_[0] / $_[1]) },
'%' => { $_[0] % $_[1] },
;
And the MAIN
sub is then just a single statement:
sub MAIN($a, $op, $b) {
say to-roman(%operators{$op}((from-roman($a), from-roman($b))));
}
Ruben Westerberg
Ruben Westerberg’s solution is an interesting one. The romanToDecimal
sub is relatively standard, but decimalToRoman
is interesting:
sub decimalToRoman ($input) {
my @digits=$input.comb;
my @p=<I X C M>;
my @h=<V L D>;
my $roman= join '', do for @digits.kv {
my $power=@digits-$^k-1;
#decimalToRoman($^v,@digits-$^k-1);
my @out;
my $base=@p[$power];
my $half=@h[$power];
given $^v {
when 1..3 {
@out.append: $base xx $_;
}
when 4 {
@out.push: $base;
@out.push: $half;
}
when 5 {
@out.push: $half;
}
when 6..8 {
@out.push: $half;
@out.append: $base xx ($_- 5);
}
when 9 {
@out.push: $base;
@out.push: @p[$power+1];
}
}
|@out;
}
}
Ruben is using .kv
to split the decimal number into key/value pairs, so @digits - $^k - 1
is the power of 10. He then makes a distinction between @p
(the powers, i.e., 1, 10, 100, and 1000), and @h
(half of a power, i.e., 5, 50, and 500).
Ruben’s main logic uses given ... when
for the operators, and also accepts either Roman or Arabic numbers in the input, which is one of those “why not” features, given it is so easy to implement:
sub MAIN(Str $operand1,Str $operator,Str $operand2){
#convert to decimal
my ($o1,$o2)=($operand1,$operand2).map({
when /<[MDCLXVI]>/ {romanToDecimal($_)};
when /\d+/ {$_};
"Not a digit or roman numeral";
});
my $result= do {
given $operator {
when "+" {$o1+$o2};
when "-" {$o1-$o2};
when "/" {Int($o1/$o2)};
when "*" {$o1*$o2};
"Unknown";
}
};
put decimalToRoman($result);
}
Simon Proctor
Simon Proctor’s solution appears to be partially influenced by his Week 10 solution, but the from-roman
sub has been significantly modified:
subset RomanStr of Str where * ~~ /^ <[M C D X L V I]>+ $/;
sub from-roman( RomanStr $roman is copy ) {
my %roman-map = (:1000M, :900CM, :500D, :400CD, :100C, :90XC,
:50L, :40XL, :10X, :9IX, :5V, :4IV, :1I );
my $out = 0;
while my $match = $roman ~~ s!^ "M" | "CM" | "D" | "CD" |
"C" | "XC" | "L" | "XL" |
"X" | "IX" | "V" | "IV" | "I"
!! {
$out += %roman-map{$match};
}
$out;
}
Simon’s used the method of defining all possible Roman numerals and their valid pairs, and then matching them in descending order. This is a concise and effective algorithm.
What is perhaps most insightful about Simon’s solution is his handling of MAIN
. Often with programs like this that accept commandline input, you won’t know if you’ll be given a single string, like "VII + X"
, or whether each argument will be separated, like "VII", "+", "X"
, so handling both is permissive and often the most sensible thing to do. Here’s Simon’s approach:
subset RomanStr of Str where * ~~ /^ <[M C D X L V I]>+ $/;
subset RomanInt of Int where 0 < * < 3001;
subset Operator of Str where * ~~ /^ ( "*" | "+" | "-" | "/" ) $ /;
multi sub MAIN (
RomanStr $i, Operator $operator, RomanStr $j
) {
say "$i $operator $j = {perf-op( $operator, $i, $j )}";
}
multi sub MAIN (
Str $compound where m!^ (<[M C D X L V I]>+) " "? ( "*" | "+" | "-" | "/" ) " "? (<[M C D X L V I]>+) $!;
) {
MAIN( $0.Str, $1.Str, $2.Str );
}
I admit to not taking advantage of Raku’s more advanced signature features enough in my own code, so I love seeing examples like this that remind me of their potential.
Ulrich Rieke
Ulrich Rieke’s solution uses Slang::Roman
for access to the to-roman
function.
The MAIN
logic uses given ... when
to perform the arithmetic operations:
sub MAIN( Str $entry ) {
if $entry ~~ /^^(<[IVXLCM]>+) \s+ (<[\+\-\/\*]>) \s+ (<[IVXLCM]>+)$$/ {
my $first_operand = romanToArab( ~$0 ) ;
my $second_operand = romanToArab( ~$2 ) ;
my $result ;
given ( ~$1 ) {
when '+' { $result = $first_operand + $second_operand }
when '-' { $result = $first_operand - $second_operand }
when '/' { $result = $first_operand div $second_operand }
when '*' { $result = $first_operand * $second_operand }
}
say to-roman( $result ) ;
}
else {
say "erroneous entry!" ;
}
}
Task #2 - Gapful Numbers
The Gapful numbers are defined by OEIS sequence A108343
This task had us write a script to print the first 20 Gapful numbers. Gapful numbers are numbers, 100 or greater, that are divisible by the concatenation of their first and last digits. So, for example, 100 is a gapful number because 100 is divisible by 10. 102 is not, because 101 is not divisible by 12.
The solutions for task #2 were all broadly similar, in that they fetch the first and last digit using features like comb
, substr
, or a regex, and then check for divisibility using the modulo (%
) or divisibility (%%
) operators.
Most solutions simply used a basic loop, while some created a lazy infinite sequence.
Alicia Bielsa
Alicia Bielsa has been a Perl contributor to the Challenge since Week 4. Her solution here is her first Raku solution submitted for the challenge!
The solution uses a slice of $currentNumber.comb
to get the first (0
) and last (*-1
) digits. Those are concatenated together and then the modulo (%
) operator checks for divisibility.
sub MAIN () {
my $totalGapfulNumbers = 20;
my $totalGapfulNumbersFound = 0;
my $currentNumber = 100;
while ($totalGapfulNumbersFound < $totalGapfulNumbers ) {
my ( $firstDigit, $lastDigit ) = $currentNumber.comb[0,*-1];
my $firstLastNumber = $firstDigit~$lastDigit;
if ($currentNumber % $firstLastNumber == 0 ) {
say "Gapful number : $currentNumber";
$totalGapfulNumbersFound++;
}
$currentNumber++;
}
}
Congratulations on your first Raku submission, Alicia. I very much hope we’ll see more.
Arne Sommer
Arne Sommer’s solution first defines a lazy $gapful
list, using substr
and the %%
divisibility operator to grep
for gapful numbers. The following line simply takes the first 20 terms and prints them.
my $gapful := (100 .. *).grep( { $_ %% ( .substr(0,1) ~ .substr(*-1,1) ) });
say "First 20 Gapful numbers: { $gapful[^20].join(',') }.";
Blog › The Roman Gap with Raku
Athanasius
Athanasius’s solution loops and uses a combination of split
and modulo to get the first and last digits. Athanasius also uses the modulo operator to check for divisibility:
while $count < TARGET {
my UInt $div = ($num.split('', :skip-empty))[0] * 10 + $num % 10;
if $num++ % $div == 0 {
@gapfuls.push: $num - 1;
++$count;
}
}
Colin Crain
Colin Crain’s solution also defines a lazy list, but uses .comb.head
and .comb.tail
to get the first and last digits:
my @o2 = (100..*).grep({ $_ %% (.comb.head ~ .comb.tail) });
say @o2[$_] for (0..19);
Jaldhar H. Vyas
Jaldhar H. Vyas’s solution uses an intermediate @digits
array, and then passes the first and last elements of that to .join
before checking for divisibility with %%
. Interestingly, the whole thing works within one gather ... take
block:
#!/usr/bin/perl6
(gather {
for (100 .. Inf) -> $number {
my @digits = $number.comb;
if $number %% (@digits[0], @digits[*-1]).join(q{}) {
take $number;
}
}
})[0 .. 19].join(', ').say;
Javier Luque
Javier Luque’s solution uses a regex to pull out the first and last digits, and %%
to check for divisibility:
# Test: perl6 ch-2.p6
my $n = 100;
my $p = 0;
while ($p < 20) {
$n ~~ /^(\d)\d*(\d)$/;
my $g_divisor = $0 ~ $1;
if ($n %% $g_divisor) {
$p++;
say $n ~ ' / ' ~ $g_divisor ~
' = ' ~ ($n / $g_divisor);
}
$n++;
}
Blog › 047 – Perl Weekly Challenge
Kevin Colyer
Kevin Colyer’s solution makes a lazy list using substr
and %%
:
my @gapful = (100...Inf)
.grep: {
$_ %% ( $_.substr( 0, 1 ) * 10 + $_.substr( *-1, 1 ) )
};
As with Arne’s solution, printing the first 20 elements is now easy:
@gapful[^20]>>.say;
Laurent Rosenfeld
2020-Feb-24: I missed this solution of Laurent’s during the original review. My apologies, Laurent!
Laurent Rosenfeld’s solution is a one-liner:
perl6 -e 'say (grep { $_ %% .comb[0,*-1].join}, 100..*)[0..19];'
This is a compact example of the lazy sequence we’ve seen before, with both the concise comb[0,*-1]
syntax and use of the divisibility (%%
) operator.
Luca Ferrari
Luca Ferrari’s solution uses a regex with named captures:
my @found;
for 100 .. Inf {
$_ ~~ / ^ $<first>=\d \d+ $<last>=\d $ /;
my $divisor = ( $/<first> ~ $/<last> ).Int;
@found.push: $divisor if $_ %% $divisor && ! @found.grep: { $_ == $divisor };
last if @found.elems == $limit;
}
"Here it is what I found, first $limit Gapful Numbers:\n".say;
@found.sort.join( "\n\t" ).say;
Blog › Roman Number Calculator and Gapful Numbers
Mark Anderson
Mark Anderson’s solution uses a regex with numbered captures:
for (100 .. Inf) -> $dividend {
state $count;
$dividend ~~ /^ (\d) \d+ (\d) $/;
my $divisor = $0 ~ $1;
if ($dividend %% $divisor) {
say $dividend;
$count++;
}
last if $count == 20;
}
Markus Holzer
Markus Holzer’s solution is the most concise yet, .say
ing the first 20 elements of a lazy sequence that uses a slice of comb
to get the digits:
.say for (100..*).grep({ $_ %% $_.comb[0,*-1].join })[^20]
Noud Aldenhoven
Noud Aldenhoven’s solution uses a lazy sequence as well, with more explicit coercion:
my @gapful = (100..Inf).grep({
$_ % Int(Str($_).comb[0] ~ Str($_).comb[*-1]) == 0
});
@gapful[^20].say;
Ruben Westerberg
Ruben Westerberg’s solution also uses a lazy list, but with a comb
slice:
my $gapful=(100..Inf).grep({$_%% .comb[0,*-1].join.Int});
put $gapful[0..19];
Ryan Thompson
My solution uses a lazy sequence, too, but I decided to split is-gapful
into its own sub, for maximum utility and perhaps slightly better readability:
my @gapful = (100..∞).grep: &is-gapful;
say @gapful[^20];
sub is-gapful( Int \n ) { n ≥ 100 and n %% n.comb[0,*-1].join }
Blog › Gapful Numbers
Simon Proctor
Simon Proctor’s solution finds the first $count
gapful numbers in the following single statement:
.say for (100..*).grep( { $^a %% $^a.comb[0,*-1].join.Int } )[^$count]
Ulrich Rieke
Ulrich Rieke’s solution also breaks isGapful
into its own sub, and then uses a lazy sequence to print the first 20 gapful numbers:
sub isGapful( Int $num is copy --> Bool ) {
my $first = $num.Str.comb.Array.shift ;
my $last = $num.Str.comb.Array.pop ;
my $divisor = ( $first ~ $last ).Int ;
return $num %% $divisor ;
}
say (100, 101 ... *).grep( { isGapful( $_ ) } )[^20] ;
$first = $num.Str.comb.Array.shift
goes through a couple of explicit coercions in order to get the first element (and $last
is similar). Something like $first = $num.comb.tail
would also be semantically equivalent, here.
See Also
Blogs this week:
Arne Sommer › The Roman Gap with Raku
Dave Jacoby › Counting from 100 is the Fun Part
Javier Luque › 047 – Perl Weekly Challenge
Laurent Rosenfeld › Roman Calculator and Gapful Numbers
Luca Ferrari › Roman Number Calculator and Gapful Numbers
Ryan Thompson › Gapful Numbers