Count and Jump
with Raku and Perl

by Arne Sommer

Count and Jump with Raku and Perl

[107] Published 20. December 2020.

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

Challenge #091.1: Count Number

You are given a positive number $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 magic Comb

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>

A Perl Version

Perl does not have 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

Challenge #091.2: Jump Game

You are given an array of positive numbers @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.
File: jump-game-sans-zero
#! /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

Perl

This is a straight forward translation of the Raku version.

File: jump-game-perl
#! /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.