Perl Weekly Challenge 72: One-Liners for Trailing Zeros and Line Ranges

These are some answers to the Week 72 of the Perl Weekly Challenge organized by Mohammad S. Anwar.

Spoiler Alert: This weekly challenge deadline is due in a few hours. This blog post offers some solutions to this challenge, please don’t read on if you intend to complete the challenge on your own.

Since both tasks in this week challenge are quite simple, I decided to use only one-liners to solve each task, both in Raku and in Perl.

Task 1: Trailing Zeros

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

Example 3:

Input: $N = 4
Output: 0 as $N! = 24 has 0 trailing zero

Trailing Zeroes in Raku

We start with actually computing $N! and counting the number of trailing zeros:

$ raku -e 'my $f = [*] 1.. @*ARGS[0]; say $f ~~ /(0+$)/ ??  $0.Str.chars !! 0;' 10
2

$ raku -e 'my $f = [*] 1.. @*ARGS[0]; say $f ~~ /(0+$)/ ??  $0.Str.chars !! 0;' 7
1

$ raku -e 'my $f = [*] 1.. @*ARGS[0]; say $f ~~ /(0+$)/ ??  $0.Str.chars !! 0;' 4
0

But it is a bit silly to compute $N!: to find the number of trailing zeros, we only need to find the number of fives in the prime factors of the list of numbers that get multiplied in the factorial product. And since we know that the input integer is less than or equal to 10, we only need to take the integer part of the division of the input number by 5 (that works well until 24 and breaks at 25 because of the two fives in the prime factors of 25):

$ raku -e 'say  (@*ARGS[0] / 5).Int;' 10
2

$ raku -e 'say  (@*ARGS[0] / 5).Int;' 7
1

$ raku -e 'say  (@*ARGS[0] / 5).Int;' 4
0

We could also use the integer division operator. But that doesn’t work as I would hope:

$ raku -e 'say  @*ARGS[0] div 5;' 10
Cannot resolve caller infix:<div>(Str:D, Int:D); none of these signatures match:
    (Int:D \a, Int:D \b --> Int:D)
    (int $a, int $b --> int)
  in block <unit> at -e line 1

Unfortunately, Raku doesn’t recognize the argument (10) as an integer, but “thinks” it is a string. To me, this is a bug: in my view, the program should be able to recognize an integer. It is of course quite easy to solve the problem by coercing the argument to an integer::

$ raku -e 'say  @*ARGS[0].Int div 5;' 10
2

Trailing Zeroes in Perl

We’ve seen in the Raku section above that, up to 24, we can just divide the argument by 5 (and the task specification says that the input argument should be less or equal to 10). So porting the second Raku one-liner to Perl is quite simple:

$ perl -E 'say int shift()/5;' 10
2

$ perl -E 'say int shift()/5;' 7
1

$ perl -E 'say int shift()/5;' 4
0

Task2: Line Ranges

You are given a text file name 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

Since this blog post is about Raku and Perl one-liners, let’s start with populating the input file with a one-liner. This can be done as follows in Raku:

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

Or with exactly the same one-liner in Perl:

$ perl -E 'say "L$_" for 1..100' > input.txt

Both produce the desired input file:

$ cat input.txt
L1
L2
L3
(Lines omitted for brevity)
L98
L99
L100

Line Ranges in Raku

We just read the file and print the lines when the line number is within the range. Note that using IO.lines.kv, line numbers start at 0; therefore, we need to shift the value by 1 to get the proper result.

$ raku -e 'sub MAIN (Int $a, Int $b where * > $a) {for "input.txt".IO.lines.kv -> $i, $j {$j.say if $b > $i >= $a-1}}' 7 10
L7
L8
L9
L10

We can make the one-liner script slightly shorter by removing the pointy block and using self-declared variables instead:

$ raku -e 'sub MAIN (Int $a, Int $b where * > $a) {for "input.txt".IO.lines.kv { $^d.say if $b > $^c >= $a-1}}' 7 10
L7
L8
L9
L10

I was hoping to use the ff flip-flop operator, but that did not lead to simpler code (contrary to Perl, as shown below).

Line Ranges in Perl

There ought to be a few things for which Perl is better than Raku. This seems to be the case for this task, where the .. flip-flop operator does all the work for us:

$ perl -ne 'print if 7..10' input.txt
L7
L8
L9
L10

Wrapping up

The next week Perl Weekly Challenge will start soon. If you want to participate in this challenge, please check https://perlweeklychallenge.org/ and make sure you answer the challenge before 23:59 BST (British summer time) on Sunday, August 16, 2020. And, please, also spread the word about the Perl Weekly Challenge if you can.

2 Comments

Task2: Line Ranges

Here is a shorter version:

raku -e 'say "input.txt".IO.lines[Range.new(|@*ARGS>>.Int) - 1]' 7 10

If we ignore the argument parsing we have thi:

"input.txt".IO.lines[Range.new(|[7, 10]) - 1]

Note the slip operator | and that adding/subtracting from an entire range is legal.

Leave a comment

About laurent_r

user-pic I am the author of the "Think Perl 6" book (O'Reilly, 2017) and I blog about the Perl 5 and Raku programming languages.