The Last Rotation with Raku

by Arne Sommer

The Last Rotation with Raku

[76] Published 3. June 2020.

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

Challenge #063.1: Last Word

Define sub last_word($string, $regexp) that returns the last word matching $regexp found in the given string, or undef if the string does not contain a word matching $regexp.

For this challenge, a “word” is defined as any character sequence consisting of non-whitespace characters (\S) only. That means punctuation and other symbols are part of the word.

The $regexp is a regular expression. Take care that the regexp can only match individual words! See the Examples for one way this can break if you are not careful.

Examples
last_word('  hello world',                qr/[ea]l/);      # 'hello'
last_word("Don't match too much, Chet!",  qr/ch.t/i);      # 'Chet!'
last_word("spaces in regexp won't match", qr/in re/);      #  undef
last_word( join(' ', 1..1e6),             qr/^(3.*?){3}/); # '399933'

Regexes look somewhat different in Raku, so I'll start with a program in Perl that works with the code examples given in the challenge (prefixed with say so that we can see the returned value):

File: lawo-perl
#! /usr/bin/env perl

use feature 'say';                                  # [1]
use feature 'signatures';                           # [1a]
no warnings qw(experimental::signatures);           # [1a]

my $verbose = (@ARGV && @ARGV[0] eq "--verbose");   # [2]

sub last_word ($string, $regex)                     # [3]
{
  say ": String: $string" if $verbose;
  
  for my $word (reverse split(/\s/, $string))       # [4]
  {
    say ": Word: $word (regex: $regex)" if $verbose;

    return $word if $word =~ $regex;                # [5]
  }
  return;                                           # [6]
}

say last_word('  hello world',                qr/[ea]l/);      # 'hello'
say last_word("Don't match too much, Chet!",  qr/ch.t/i);      # 'Chet!'
say last_word("spaces in regexp won't match", qr/in re/);      #  undef
say last_word( join(' ', 1..1e6),             qr/^(3.*?){3}/); # '399933'

[1] I have chosen to make the program look as mnuch like the Raku version as possible, hence the usage of say and procedure signatures [1a] which must enabled explicitly.

[2] The easiest way to detect a single command line argument, without using an argument passing module.

[3] The procedure answering the challenge.

[4] Split the string on \s (the opposite of \S), so that we end up with a list of «words» consisting of characters matching \S. Reverse the list (= look from the end), as we were asked for the last match in the input string.

[5] Does the current word match?

[6] We have reached the end (= the beginning) without any match. Say so.

Running it:

$ raku lawo-perl
hello
Chet!

399933

Printing an undefined value and a newline gives an empty line, so we are good.

Printing the regex (in verbose modue) gives a string that doesn't quite match what we specified. Oh well.

$ raku lawo-perl --verbose
: String:   hello world
: Word: world (regex: (?^:[ea]l))
: Word: hello (regex: (?^:[ea]l))
hello
: String: Don't match too much, Chet!
: Word: Chet! (regex: (?^i:ch.t))
Chet!
: String: spaces in regexp won't match
: Word: match (regex: (?^:in re))
: Word: won't (regex: (?^:in re))
: Word: regexp (regex: (?^:in re))
: Word: in (regex: (?^:in re))
: Word: spaces (regex: (?^:in re))

: String: 1 2 3 4 5 6 7 8 9 10 11 ... 999999 1000000
: Word: 1000000 (regex: (?^:^(3.*?){3}))
...
: Word: 399933 (regex: (?^:^(3.*?){3}))
399933

I have abridged the output to make it easier to see what is going on.

Then the Raku version:

File: lawo
#! /usr/bin/env raku

unit sub MAIN (:$verbose);

sub last_word ($string, Regex $regex)                          # [1]
{
  say ": String: $string" if $verbose;
  
  for $string.split(/\s/).reverse -> $word                     # [2]
  {
    say ": Word: $word (regex: { $regex.gist })" if $verbose;  # [3]

    return $word if $word ~~ $regex;
  }
  return;
}

say last_word('  hello world',                rx/<[ea]>l/);       # 'hello'
say last_word("Don't match too much, Chet!",  rx:i/ch.t/);        # 'Chet!'
say last_word("spaces in regexp won't match", rx:s/in re/);       #  undef
say last_word( join(' ', 1..1e6),             rx/^(3.*?) ** 3 /); # '399933'

[1] Note the type constraint on the regex argument.

[2] Note how much easier it is to read this line of chained instruction (where everything happens in a timely fashion from left to right), compared to the Perl version.

[3] Printing a regex doesn't work, but slapping on the gist or raku methods does the trick.

Running it:

$ raku lawo
hello
Chet!
Nil
399933

Note the Nil, which is Raku's undefined value. It should probably not be printed, but the task asked for return values. And we got them right.

With verbose mode:

$ raku lawo --verbose
: String:   hello world
: Word: world (regex: rx/<[ea]>l/)
: Word: hello (regex: rx/<[ea]>l/)
hello
: String: Don't match too much, Chet!
: Word: Chet! (regex: rx:i/ch.t/)
Chet!
: String: spaces in regexp won't match
: Word: match (regex: rx:s/in re/)
: Word: won't (regex: rx:s/in re/)
: Word: regexp (regex: rx:s/in re/)
: Word: in (regex: rx:s/in re/)
: Word: spaces (regex: rx:s/in re/)
Nil
: String: 1 2 3 4 5 6 7 8 9 10 11 ... 999999 1000000
: Word: 1000000 (regex: rx/^(3.*?) ** 3 /)
...
: Word: 399933 (regex: rx/^(3.*?) ** 3 /)
399933

This time we actually got the exact regex, which is good.

Regex Comparison

PerlPerl PrintRakuComment
qr/[ea]l/ (?^:[ea]l) rx/<[ea]>l/ qr has been renamed to rx, and <[ea]> instead of [ea].
qr/ch.t/i (?^i:ch.t) rx:i/ch.t/ The i (ignore case) adverb has been moved to the front of the regex, as it reads better for humans. Note that Perl Print concurs...
qr/in re/ (?^:in re) rx:s/in re/ Regexes in Raku ignores spaces by the default, but we can turn them on with the s (sigspace) adverb.
qr/^(3.*?){3}/ (?^:^(3.*?){3}) rx/^(3.*?) ** 3 / General quantifiers as ** 3 instead of {3}.

See docs.raku.org/language/regexes and docs.raku.org/language/regexes-best-practices for more infoprmation about Raku regexes.


Challenge #063.2: Rotate String

Given a word made up of an arbitrary number of x and y characters, that word can be rotated as follows: For the ith rotation (starting at i = 1), i % length(word) characters are moved from the front of the string to the end. Thus, for the string xyxx, the initial (i = 1) % 4 = 1 character (x) is moved to the end, forming yxxx. On the second rotation, (i = 2) % 4 = 2 characters (yx) are moved to the end, forming xxyx, and so on. See below for a complete example.

Your task is to write a function that takes a string of xs and ys and returns the minimum non-zero number of rotations required to obtain the original string. You may show the individual rotations if you wish, but that is not required.

Example
Input: $word = 'xyxx';
  • Rotation 1: you get yxxx by moving x to the end.
  • Rotation 2: you get xxyx by moving yx to the end.
  • Rotation 3: you get xxxy by moving xxy to the end.
  • Rotation 4: you get xxxy by moving nothing as 4 % length(xyxx) == 0.
  • Rotation 5: you get xxyx by moving x to the end.
  • Rotation 6: you get yxxx by moving xx to the end.
  • Rotation 7: you get xyxx by moving yxx to the end which is same as the given word.
Output: 7

I'll start with a Perl version this time as well:

File: rostr-perl
#! /usr/bin/env perl

use feature 'say';

use Getopt::Long;                                        # [1]

my $verbose;                                             # [1]

GetOptions ("verbose"  => \$verbose);                    # [1]

my $string = @ARGV[0] || 'xyxx';                         # [2]

die "Illegal input string" unless $string =~ /^[xy]+$/;  # [2a]

my $length  = length($string);
my $current = $string;

my $count = 0;

while (1)
{
  $count++;

  my $rotate = $count % $length;

  if ($rotate)
  {
    my $a = substr($current, $rotate);
    my $b = substr($current, 0, $rotate);

    $current = $a . $b;
    
    say ": Rotation $count: $current (moved '$b' to the end)" if $verbose;
  }
  elsif ($verbose)
  {
     say ": Rotation $count: $current (moved nothing)";
  }

  last if $current eq $string;
}

say "$count Rotations";

[1] This time I chose to use a module for parsing the command line options.

[2] Get the string from the command line, or use a default value. Die if the string is illegal.

The rest of the program should be self explanatory.

Running it:

$ perl rostr-perl 
7 Rotations

$ perl rostr-perl --verbose
: Rotation 1: yxxx (moved 'x' to the end)
: Rotation 2: xxyx (moved 'yx' to the end)
: Rotation 3: xxxy (moved 'xxy' to the end)
: Rotation 4: xxxy (moved nothing)
: Rotation 5: xxyx (moved 'x' to the end)
: Rotation 6: yxxx (moved 'xx' to the end)
: Rotation 7: xyxx (moved 'yxx' to the end)
7 Rotations

$ perl rostr-perl --verbose xxxyyxx
: Rotation 1: xxyyxxx (moved 'x' to the end)
: Rotation 2: yyxxxxx (moved 'xx' to the end)
: Rotation 3: xxxxyyx (moved 'yyx' to the end)
: Rotation 4: yyxxxxx (moved 'xxxx' to the end)
: Rotation 5: xxyyxxx (moved 'yyxxx' to the end)
: Rotation 6: xxxyyxx (moved 'xxyyxx' to the end)
6 Rotations

Then the Raku version:

File: rostr
#! /usr/bin/env raku

subset XY where /^<[xy]>+$/;                 # [1]

sub MAIN (XY $string = 'xyxx', :$verbose)    # [1]
{
  my $length  = $string.chars;
  my $current = $string;

  my $count = 0;

  loop                                       # [2]
  {
    $count++;

    my $rotate = $count % $length;

    if $rotate
    {
      my $a = $current.substr($rotate);
      my $b = $current.substr(0, $rotate);

      $current = $a ~ $b;
    
      say ": Rotation $count: $current (moved '$b' to the end)" if $verbose;
    }
    elsif $verbose
    {
      say ": Rotation $count: $current (moved nothing)";
    }

    last if $current eq $string;
  }

  say "$count Rotations";
}

[1] A custom type (with subset) makes for nice validation of the input.

[2] An eternal loop with loop without arguments.

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

Running it gives the same result as the perl version:

$ raku rostr-perl
7 Rotations

$ raku rostr-perl --verbose
: Rotation 1: yxxx (moved 'x' to the end)
: Rotation 2: xxyx (moved 'yx' to the end)
: Rotation 3: xxxy (moved 'xxy' to the end)
: Rotation 4: xxxy (moved nothing)
: Rotation 5: xxyx (moved 'x' to the end)
: Rotation 6: yxxx (moved 'xx' to the end)
: Rotation 7: xyxx (moved 'yxx' to the end)
7 Rotations

$ raku rostr --verbose xxxyyxx
: Rotation 1: xxyyxxx (moved 'x' to the end)
: Rotation 2: yyxxxxx (moved 'xx' to the end)
: Rotation 3: xxxxyyx (moved 'yyx' to the end)
: Rotation 4: yyxxxxx (moved 'xxxx' to the end)
: Rotation 5: xxyyxxx (moved 'yyxxx' to the end)
: Rotation 6: xxxyyxx (moved 'xxyyxx' to the end)
6 Rotations

$ raku rostr --verbose xxxyyxx
: Rotation 1: xxyyxxx (moved 'x' to the end)
: Rotation 2: yyxxxxx (moved 'xx' to the end)
: Rotation 3: xxxxyyx (moved 'yyx' to the end)
: Rotation 4: yyxxxxx (moved 'xxxx' to the end)
: Rotation 5: xxyyxxx (moved 'yyxxx' to the end)
: Rotation 6: xxxyyxx (moved 'xxyyxx' to the end)
6 Rotations

And that's it.