Andrew Shitov Weekly Review: Challenge - 072

Friday, Aug 14, 2020| Tags: Raku

Raku Solutions Weekly Review


Getting in Touch

Email › Email me (Andrew) 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.



Welcome to the Raku Review for Week 072 of The Weekly Challenge!. For a quick overview, go through the original tasks and recap of the weekly challenge.


Task 1. Trailing zeroes in a factorial

In this task, you need to count the number of trailing zeroes in the factorial of an integer number, where that number does not exceed 10. There are two obvious alternatives to the solution, one is pure mathematical (count the number of factors — powers of 5) and the second is using the fact that Raku can transparently switch between the numeric and string representation of a number. In the solutions sent to the Challenge, both approaches are demonstrated.

Pre-computed tables

The side-story is that if we know that the maximum input number is 10, then we can pre-compute the results and use a lookup table to access the answer directly. Such a solution was demonstrated by Simon Proctor (it was not submitted to the Challenge repository but nevertheless is worth mentionning):

    say <00000111112>.comb[@*ARGS[0]]

Another example that uses an idea of a lookup table is found in the solution by Athanasius:

my  constant @ZEROES =  #        N! #0s        N
                        (         1, 0 ),   #  0
                        (         1, 0 ),   #  1
                        (         2, 0 ),   #  2
                        (         6, 0 ),   #  3
                        (        24, 0 ),   #  4
                        (       120, 1 ),   #  5
                        (       720, 1 ),   #  6
                        (     5_040, 1 ),   #  7
                        (    40_320, 1 ),   #  8
                        (   362_880, 1 ),   #  9
                        ( 3_628_800, 2 );   # 10

# . . .
($factorial, $zeroes) = @ZEROES[ $N ];

It may seem an overweighted solution, but it is a good example of an approach for creating robust and production-ready code.

Computing a factorial

Let me list the different methods the participants use to compute a factorial.

Using a reduction metaoperator:

    my $f = [*] 1..$n;

With an explicit call of reduce. To understand the meaning of * * *, let me refer you to my post ”All the stars of Perl 6” in one of the past Advent calendars.

    my Int:D $n-factorial  = ($n...1).reduce(* * *);

Another form of the reduction operator, the so-called triangular reduction operator. It gives you not only the factorial of the given number but the whole array for the factorials for all the numbers from 1 to N:

    constant @fact = 1, |[\*] 1..* ;

The above array is lazy, and you can use an explicit lazy statement prefix together with (an already lazy) sequence:

    my $fac := lazy 1, { $^a * ++$ } ... *;

In this example, notice the use of an anonymous state variable $.

There is no doubt you can use a loop to compute a factorial:

    for 1 .. $N -> $value {
        $faculty *= $value;
    }

Or, in a different form, when you modify the value of $N that you got from the user.

    $N *= $_ for 1..$N-1;

And finally, another fascinating way is to modify the syntax of Raku and define your own postfix so that you can write $N! to compute a factorial:

    sub postfix:<!>( Int $n ) {
        [*](1..$n) ;
    }

Counting zeroes

The majority of the solutions use one or another form of a regex that matches the sequence of zeroes at the end of the string. For example:

    say ($f ~~ / 0+ $/ // '').chars;

Or:

    @fact[$n].match(/0*$/).chars

Or:

    my $zeros = ($fact ~~ /.*?(0*)$/)[0].comb.elems;

Or:

    my $trailing-zeroes = $N ~~ m/ <[1..9]>?(<[0]>+) /;

You can also do a trick and flip the string first. In this case, you will need to only look at the characters at the beginning of the string:

    my $numstr = ~($N!).flip;

Powers of 5

The second most often used method is to find the number of factors which are either 5 or powers of 5 (25, 125, etc.). Here is a possible implementation found in the solution by Javier Luque:

    loop ($i = 5; Int($N / $i) >= 1; $i *= 5) {
        $zeroes += Int($N / $i);
    }

Divide by 10

Finally, it is possible to divide the number by 10 until you get a non-integer number, as Jason Messer did it:

    while ($running > 0) {
        if ($running %% 10) {
            $running /= 10;
            ++$count;
        } else {
            last;
        }
    }

Video review

You can find the complete overview of the tasks in this video: www.youtube.com/watch?v=6fTIoe6hUIg.

Task 2. Lines range in a text file

The task is to print the lines with the numbers $A through $B from a text file.

To genereate a reference text file, we can use Raku itself:

    raku -e 'say "L$_" for 1..100' > input.txt

The most common feature of Raku used in the solutions to this task is the IO::Path::lines method. For example, in my solution, a range of lines is taken directly by slicing the sequence or lines:

    .say for 'input.txt'.IO.lines[$a-1 ..^ $b];

It is possible to work with line numbers directly with the help of either the kv or pairs method. For example, as in the solution by Ben Devies:

    .say for $file.slurp.lines.pairs.grep($a <= *.key + 1 <= $b).map(*.value);

Or as in Colin Crain’s code:

    .value.say if .key == $from-1 ff .key == $to-1 for $file.IO.lines.pairs;

The lines method takes an optional parameter $limit to prevent reading too many lines. The most interesting usage of this parameter is demonstrated in the solution by Jason Messer, where it is first used to skip the lines with line numbers below $A, and then to read the lines that are actually requested ($B - $A + 1):

    my $fh = open $fname, :chomp(False) or die($fh);
    $fh.lines($first_line - 1).eager;
    my $n = $last_line - ($first_line - 1);
    for ($fh.lines($n)) { .print }

Another interesting solution is offered by Jan Krnavek. Here, the minimum required number of lines is taken first, and then the first lines are skipped:

    $file.IO.lines.head($b).skip($a-1)».say

Video review

Watch the video to see the review of the solutions of the second task: www.youtube.com/watch?v=kU4dggl0P8g.



BLOGS



Andrew Shitov, Arne Sommer, Colin Crain, Jaldhar H. Vyas, Javier Luque, Laurent Rosenfeld, Luca Ferrari, Mohammad S Anwar, Roger Bell_West, Simon Proctor #1 and Simon Proctor #2.


If you want to participate in The Weekly Challenge, please contact us at perlweeklychallenge@yahoo.com.

SO WHAT DO YOU THINK ?

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

Contact with me