with Raku and Perl

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

You are given a positive number

Write a script to count number and display as you read it.

Example 1

`$N`

.
Write a script to count number and display as you read it.

Example 1

Input: $N = 1122234
Output: 21321314
as we read "two 1 three 2 one 3 one 4"

Example 2
Input: $N = 2333445
Output: 12332415
as we read "one 2 three 3 two 4 one 5"

Example 3
Input: $N = 12345
Output: 1112131415
as we read "one 1 one 2 one 3 one 4 one 5"

I did just this (in addition to 97 other things) in my Centenary Sequences with Raku article. See the «Look-and-Say Sequences» section at the top of Part 8: Look-and-Say and Text.

The «look-and-say» procedure, as used below, is copied verbatim from the program with the same name.

File: count-number#! /usr/bin/env raku
subset PositiveInt of Int where * > 0; # [1]
unit sub MAIN (PositiveInt $N);
say look-and-say($N);
sub look-and-say ($input)
{
my $return = "";
for $input.comb: / (.) $0* / -> $batch # [2]
{
$return ~= $batch.chars ~ $batch.substr(0,1); # [3]
}
return $return;
}

[1] It does not make sense to allow positive *numbers* (as stated in the
challenge), so I restrict the input to positive *integers* only.

[2] As long as we can get blocks of identical letters (1 or more),

[3] • Add the count and the character.

The `comb`

method will, when used without arguments, return a Sequence
containing all the letters in the string separately:

> "122333444".comb.raku
("1", "2", "2", "3", "3", "3", "4", "4", "4").Seq

When given a number, the returned strings will have that length (or less, as in the final one):

> "122333444".comb(2)
("12", "23", "33", "44", "4").Seq

We can also use a Regex. The regex is applied to the original string, and whatever matches is the first value in the returned Sequence. Then this goes on, until there are no more characters in the string.

This one matches a single digit at a time, giving the same Sequence as initially. As long as there are only digits in the string:

> "122333444".comb(/\d/).raku
("1", "2", "2", "3", "3", "3", "4", "4", "4").Seq
> "122HELP333444ME!!!".comb(/\d/).raku
("1", "2", "2", "3", "3", "3", "4", "4", "4").Seq

The second one shows that it silently ignores characters that does nop match.

We want to extract groups of the same digit, and can do that with a backwards
reference (`$0`

) to the first match (`(.)`

) like this:

> "122333444".comb(/(.)$0*/).raku
("1", "22", "333", "444").Seq

We could have used `\d`

here, but `.`

is shorter. We have already
ensured that the input is a positive integer, so it does not really matter which one
we use here.

See
docs.raku.org/routine/comb for
more information about `comb`

.

Running it:

$ ./count-number 1122234
21321314
$ ./count-number 2333445
12332415
$ ./count-number 12345
1112131415

This does not work, as the input is not a positive number:

$ ./count-number 00
Usage:
./count-number <N>

`comb`

, and `split`

is no good as it removes
the delimiter character(s). It is possible to do this with a Regex (as shown in this
Rosetta Code
article - and included as «count-number-perl-regex» in the zip file), but I am unable
to come up with this kind of cleverness on my own.
So the hard way it is:

File: count-number-perl#! /usr/bin/env perl
use strict;
use warnings;
my $N = shift(@ARGV) || "";
die "Please specify a positive integer" unless $N =~ /^[1-9]\d*$/;
my @input = split("", $N);
my $current = shift(@input);
my $count = 1;
while (@input)
{
if ($input[0] eq $current)
{
shift(@input);
$count++;
}
else
{
print $count . $current;
$current = shift(@input);
$count = 1;
}
}
print $count . $current . "\n";

Running it gives the same result as the Raku version:

$ ./count-number-perl 1 2 1 2
11
$ ./count-number-perl 1122234
21321314
$ ./count-number-perl 2333445
12332415
$ ./count-number-perl 12345
1112131415

You are given an array of positive numbers

Write a script to decide if you can jump to the last index. Print 1 if you are able to reach the last index otherwise 0.

Example 1

File: jump-game-sans-zero
`@N`

, where value at each index
determines how far you are allowed to jump further.
Write a script to decide if you can jump to the last index. Print 1 if you are able to reach the last index otherwise 0.

Example 1

Input: @N = (1, 2, 1, 2)
Output: 1
as we jump one place from index 0 and then twoe places from index 1 to
reach the last index.

Example 2
Input: @N = (2,1,1,0,2)
Output: 0
it is impossible to reach the last index. as we jump two places from
index 0 to reach index 2, followed by one place jump from index 2 to
reach the index 3. once you reached the index 3, you can't go any
further because you can only jump 0 position further.

#! /usr/bin/env raku
subset PositiveInt of Int where * > 0; # [1]
unit sub MAIN (*@N where @N.elems > 0 && all(@N) ~~ PositiveInt; # [2]
my $index = 0;
loop # [3]
{
( say 1; last ) if $index == @N.end; # [4]
@N[$index].defined # [5]
?? ( $index += @N[$index] ) # [5a]
!! ( say 0; last); # [5b]
}

[1] Positive *numbers* does not make much sense, so I have restricted
the input to positive *integers*.

[2] Ensure that we have at least one element, and that they all satisfy the restriction set up in [1].

[3] An eternal loop. We have two exit stretegies, in [4] and [5].

[4] Have we reached the end of the array? If so print "1" (as in «hurrah») and exit.

[5] Have we jumped past the array end? If so print "0" (as in «bummer») and exit [5b]. If not, add the number at that position to the index [5a], ready for the next iteration (and check in [4]).

Note the postfix «if» in [4] and the ternary
«if» (`??`

and `!!`

) in [5]. You have probably read somewhere
that you can only have one expression *before* a postfix «if», and that
is *almost* true. You can only have one *block*, and using the
grouping operator `(`

and `)`

allows us to add as much code
as we want. And yes, you can use `{`

and `}`

instead, if
that makes you feel better.

{ say 1; last } if $index == @N.end; # [4x]

Running it:

$ ./jump-game 1 2 1 2
1
$ ./jump-game 2 1 1 0 2
Usage:
./jump-game [<N> ...]

Oops.

The problem is the «0», which is illegal as per the challenge. So the challenge
*should have said* «non-negative integers `@N`

».

Let us so just that:

File: jump-game#! /usr/bin/env raku
subset PositiveInt0 of Int where * >= 0;
unit sub MAIN (*@N where @N.elems > 0 && all(@N) ~~ PositiveInt0);
my $index = 0;
loop
{
(say 1; last ) if $index == @N.end;
@N[$index].defined && @N[$index] # [1]
?? ( $index += @N[$index] )
!! ( say 0; last);
}

[1] The last part is there to ensure program termination. We would have an eternal loop without it.

Running it:

$ ./jump-game 1 2 1 2
1
$ ./jump-game 2 1 1 0 2
0

#! /usr/bin/env perl
use strict;
use feature 'say';
use List::Util qw(all);
die "Non-negative integers only" unless all { $ ~= /^\d+$/ } @ARGV;
my $index = 0;
while (1)
{
if ($index == @ARGV -1) { say 1; last; }
if (defined $ARGV[$index] && $ARGV[$index])
{
$index += @ARGV[$index];
}
else
{
say 0; last;
}
}

Running it gives the same result as the Raku version:

$ ./jump-game-perl 1 2 1 2
1
$ ./jump-game-perl 2 1 1 0 2
0

And that's it.