## 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.