Ryan Thompson › Raku Weekly Review: Challenge - #047

Friday, Feb 21, 2020| Tags: raku

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-24Arne 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.

BlogThe 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        { '' }
    }
}

Blog047 – 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 combs 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;

BlogRoman 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 );

BlogRoman 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(',') }.";

BlogThe 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++;
}

Blog047 – 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;

BlogRoman 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, .saying 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 }

BlogGapful 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 SommerThe Roman Gap with Raku

Dave JacobyCounting from 100 is the Fun Part

Javier Luque047 – Perl Weekly Challenge

Laurent RosenfeldRoman Calculator and Gapful Numbers

Luca FerrariRoman Number Calculator and Gapful Numbers

Ryan ThompsonGapful Numbers

SO WHAT DO YOU THINK ?

If you have any suggestions or ideas then please do share with us.

Contact with me