by Arne Sommer

# Zero Lines with Raku & Perl

[86] Published 6. August 2020.

This is my response to the Perl Weekly Challenge #072.

## Challenge #072.1: Trailing Zeroes

You are given a positive integer `\$N (<= 10)`.

Write a script to print number of trailing zeroes in `\$N!`.

Example 1 ```Input: \$N = 10 Output: 2 as \$N! = 3628800 has 2 trailing zeroes ``` Example 2 ```Input: \$N = 7 Output: 1 as \$N! = 5040 has 1 trailing zero Peak: [ 47, 32, 39, 36 ] ``` Example 3 ```Input: \$N = 4 Output: 0 as \$N! = 24 has 0 trailing zero ```

`!` is the faculty operator (in a matemathical sense, as neither Raku nor Perl has it). N Faculty is defined as all the consecutive integers from `1` to `N` multiplied together.

So e.g. 5 Faculty is the same as `1 * 2 * 3 * 4 * 5 = 120`.

We can use the Reduction Metaoperator `[]` to compute the value for us. In REPL:

```> [*] 1 .. 5; # -> 120 > [*] 1 .. 6; # -> 720 ```

Now we have the value, and we need to extract the number of trailing zeroes in it. We can do this with a regex:

```> 120 ~~ /(0*)\$/; say \$0.chars; # -> 1 > 121 ~~ /(0*)\$/; say \$0.chars; # -> 0 > 200 ~~ /(0*)\$/; say \$0.chars; # -> 2 ```

Then we can wrap it up in a program:

File zero-faculty ```#! /usr/bin/env raku subset PosInt of Int where * >= 1; # [1] unit sub MAIN (PosInt \$N, :v(:\$verbose)); # [2] my \$faculty = [*] 1 .. \$N; # [3] say ": \$N Faculty: \$faculty" if \$verbose; \$faculty ~~ /(0*)\$/; # [4] say \$0.chars; ```

[1] We use a custom type (set up with `subset`) to ensure that `\$N` is an integer, with the value 1 or higher.

[2] Note the way we can set up aliases. Khaled M. Elboray pointed this out for me after the previous challenge. This language keeps surprising me... See docs.raku.org/type/Signature#index-entry-argument_aliases for more information.

[3] Compute the value.

[4] Count the trailing zeroes.

See docs.raku.org/language/typesystem#index-entry-subset-subset for more information about `subset`.

Running it:

``` ./zero-faculty -v 1 : 1 Faculty: 1 0 \$ ./zero-faculty -v 2 : 2 Faculty: 2 0 \$ ./zero-faculty -v 3 : 3 Faculty: 6 0 \$ ./zero-faculty -v 4 : 4 Faculty: 24 0 \$ ./zero-faculty -v 5 : 5 Faculty: 120 1 \$ ./zero-faculty -v 6 : 6 Faculty: 720 1 ./zero-faculty -v 14 : 14 Faculty: 87178291200 2 ```

Looking good.

It is tedious to enter all those command lines. A shell script wrapper printing all the values from 1 to the specified limit could do the job, but it is better to extend the Raku program itself.

File: zero-faculty-upto ```#! /usr/bin/env raku subset PosInt of Int where * >= 1; unit sub MAIN (PosInt \$N, :v(:\$verbose), :\$u(\$upto); \$upto ?? (1 .. \$N).map({ say faculty-of(\$_) }) # [1] !! say faculty-of(\$N); # [2] sub faculty-of (\$value) # [3] { my \$faculty = [*] 1 .. \$value; say ": \$value Faculty: \$faculty" if \$verbose; \$faculty ~~ /(0*)\$/; return \$0.chars; } ```

[1] In «upto» mode, print all the values up to the limit. Note the use of `map` instead of a loop.

[2] One value only.

[3] Compute the actual value.

Running it:

```./zero-faculty-upto --upto 10 0 0 0 0 1 1 1 1 1 2 \$ ./zero-faculty-upto -u -v 10 : 1 Faculty: 1 0 : 2 Faculty: 2 0 : 3 Faculty: 6 0 : 4 Faculty: 24 0 : 5 Faculty: 120 1 : 6 Faculty: 720 1 : 7 Faculty: 5040 1 : 8 Faculty: 40320 1 : 9 Faculty: 362880 1 : 10 Faculty: 3628800 2 ```

But why bother computing the faculty each time, as we already have the previous value (printed on the line above it in the output)?

file: zero-faculty-upto-loop ```#! /usr/bin/env raku subset PosInt of Int where * >= 1; unit sub MAIN (PosInt \$N, :v(:\$verbose), :u(:\$upto)); my \$faculty = 1; for 1 .. \$N -> \$value { \$faculty *= \$value; if \$upto || \$value == \$N # [1] { say ": \$value Faculty: \$faculty" if \$verbose; \$faculty ~~ /(0*)\$/; say \$0.chars; } } ```

[1] Print the last value, and all the others if we have requested «upto» mode.

### A Perl Version

This is pretty much a straight forward translation from the last Raku version:

File: zero-faculty-perl ```#! /usr/bin/env perl use strict; use feature 'say'; my \$N = shift(@ARGV) // die 'Please specify \$N'; my \$verbose; my \$upto; while (\$N eq "--verbose" || \$N eq "-v" || \$N eq "--upto" || \$N eq "-u") { \$verbose++ if \$N eq "--verbose" || \$N eq "-v"; \$upto++ if \$N eq "--upto" || \$N eq "-u"; \$N = shift(@ARGV) // die 'Please specify \$N'; } die '\$N must be an integer >= 1' unless int(\$N) == \$N && \$N >= 1; my \$faculty = 1; for my \$value (1 .. \$N) { \$faculty *= \$value; if (\$upto || \$value == \$N) { say ": \$value Faculty: \$faculty" if \$verbose; \$faculty =~ /(0*)\$/; say length \$1; } } ```

### Raku! Bonus!

I said earlier that Raku did not have a Faculty operator. That is true, but it is very easy to add one. The reason this one is missing is perhaps just to make it easy for writers such as myself to show off the extensibility of Raku?

File: faculty ```#! /usr/bin/env raku sub postfix:<!>(\$n) { return [*] 1 .. \$n; } for 1 .. 20 -> \$int { say "\$int! = { \$int! }"; } ```

Running it:

```\$ ./faculty 1! = 1 2! = 2 3! = 6 4! = 24 5! = 120 6! = 720 7! = 5040 8! = 40320 9! = 362880 10! = 3628800 11! = 39916800 12! = 479001600 13! = 6227020800 14! = 87178291200 15! = 1307674368000 16! = 20922789888000 17! = 355687428096000 18! = 6402373705728000 19! = 121645100408832000 20! = 2432902008176640000 ```

Note that defining custom operators does not work in REPL.

## Challenge #072.2: Lines Range

You are given a text file name `\$file` and range `\$A` - `\$B` where `\$A <= \$B`.

Write a script to display lines range `\$A` and `\$B` in the given file.

Example

Input: ```\$ cat input.txt L1 L2 L3 L4 ... ... ... ... L100 ``` `\$A = 4 and \$B = 12`

Output: ```L4 L5 L6 L7 L8 L9 L10 L11 L12 ```

This is trivial in Raku:

File: line-range ```#! /usr/bin/env raku subset PosInt of Int where * >= 1; unit sub MAIN (Str \$filename, PosInt \$A, PosInt \$B where \$B >= \$A, # [1] :v(:\$verbose)); die "No such file \$filename" unless \$filename.IO.e; # [2] die "Not a file \$filename" unless \$filename.IO.f; # [2a] die "Cannot read from \$filename" unless \$filename.IO.r; # [2b] my \$count = 0; # [3] for \$filename.IO.lines -> \$line { \$count++; say ": Considering line \$count: \$line" if \$verbose; next if \$count < \$A; # [4] say \$line; last if \$count == \$B; # [5] } ```

[1] The constraints on the two variables `\$A` and `\$B` are easy to set up.

[2] Complain if the file does not exist (`IO.e`), if it is not a file (`IO.f`), and finally if it is not readable by the program (`IO.r`).

[3] Keep track of the line number.

[4] Before the requested start, skip it.

[5] After the requested end, exit.

We can simplity this considerably, with an array slice:

File: line-range-slice ```#! /usr/bin/env raku subset PosInt of Int where * >= 1; unit sub MAIN (Str \$filename, PosInt \$A, PosInt \$B where \$B >= \$A, :v(:\$verbose)); my @lines = \$filename.IO.lines; # [1] my \$lines = @lines.elems; # [3] my \$start = min(\$lines -1, \$A -1); # [4] my \$stop = min(\$lines -1, \$B -1); # [5] say ": \$start .. \$stop" if \$verbose; say @lines[\$start .. \$stop].join("\n"); # [2] ```

[1] Reading the whole file like this is generally not a problem, as it does so lazily. It will only read the lines from the file when actually needed.

[2] The array slice, simplicity itself... But we have to ensure that the indices exist, or we'll get an error.

[3] We cannot read more lines than actually exist, and `elems` gives us the number of lines in the file. The problem is that is has to read the whole file to be able to figure this out.

[4] Ensure that the first line to print actually exist. The `-1` part is caused by the array indices starting with zero, and the line numbers at 1.

[5] Ditto for the last one.

Running it:

```\$ ./line-range-slice line-range-slice 999 999 say @lines[\$start .. \$stop].join("\n"); \$ ./line-range-slice -v line-range-slice 999 999 : 13 .. 13 say @lines[\$start .. \$stop].join("\n"); ```

The program chooses to print the last line in the file, if both indices are out of bound. That is not good.

This program is unsuitable when run with very large files, as it reads the entire file before orinting anything. So we should discard it.

We can just go ahead with indices, and get rid of undefined values (missing rows) with `grep`:

File: line-range-grep: ```#! /usr/bin/env raku subset PosInt of Int where * >= 1; unit sub MAIN (Str \$filename, PosInt \$A, PosInt \$B where \$B >= \$A); say \$filename.IO.lines[\$A -1 .. \$B -1].grep( *.defined ).join("\n"); ```

We can put the `say` statement at the end of the last line, to make it even easier to read:

```\$filename.IO.lines[\$A -1 .. \$B -1].grep( *.defined ).join("\n").say; ```

But you are probably used to seeing output statements at the start of the line, so this change may actually reduce readability.

Verbose mode has gone as well, as there is nothing left to be verbose about.

Running it:

```\$ ./line-range-grep line-range-grep 1 1 #! /usr/bin/env raku ``` ```\$ ./line-range-grep line-range-grep 999 999 ```

Yes, we got an empty line. That is caused by the `say` statement.

Note that we get a large array with undefined values if we run it with a high `\$B` value on a file with a small number of lines. This is not very efficient.

So we can discard this one as well, and stick with «line-range».

### A Perl Version

This is a straight forward translation of the «line-range» Raku program.

File: line-range-perl ```#! /usr/bin/env perl use strict; use feature 'say'; my \$verbose; if (\$ARGV[0] eq "--verbose" || \$ARGV[0] eq "-v") { \$verbose++; shift(@ARGV); } my \$filename = shift(@ARGV) // die 'Please specify a file'; my \$A = shift(@ARGV) // die 'Please specify \$A'; my \$B = shift(@ARGV) // die 'Please specify \$B'; die '\$A must be an integer >= 1' unless int(\$A) == \$A && \$A >= 1; die '\$B must be an integer >= \$A' unless int(\$B) == \$B && \$B >= \$B; my \$count = 0; open my \$in, \$filename or die "\$filename: \$!"; while (my \$line = <\$in>) { \$count++; say ": Considering line \$count: \$line" if \$verbose; next if \$count < \$A; print \$line; last if \$count == \$B; } close \$in; ```

This one handles zero lines correct, as does the Raku version it is based on:

```\$ ./line-range-perl line-range-perl 999 999 ```

And that's it.