Vowelled Matrix with Raku

by Arne Sommer

Vowelled Matrix with Raku

[64] Published 26. March 2020.

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

Challenge #53.1: Rotate Matrix

Write a script to rotate the following matrix by given 90/180/270 degrees clockwise.

[ 1, 2, 3 ]
[ 4, 5, 6 ]
[ 7, 8, 9 ]

For example, if you rotate by 90 degrees then expected result should be like below

[ 7, 4, 1 ]
[ 8, 5, 2 ]
[ 9, 6, 3 ]

In my FC Matrix with Raku article (in response to Challenge #11.2), I started with the «Math::Matrix» module, and then did it manually with a shaped array. This time I'll do it with regular arrays. Or rather, an array of arrays.

File: matrix-rotation (partial)
subset Degree where * == any(90, 180, 270);             # [1]

unit sub MAIN (Degree $degree is copy = 90, :$verbose); # [2]

my @matrix = ((1,2,3), (4,5,6), (7,8,9));               # [3]

while $degree                                           # [4]
{
  @matrix = rot90(@matrix);                             # [4a]
  $degree -= 90;                                        # [4b]
}

say @matrix.raku;                                       # [5]

[1] A custom type with subset for the legal degrees. Note the absence of the typical of Int, and the use of a list of values with an any junction instead.

[2] Note is copy on the argument, so that we can change it.

[3] The matrix given in the challenge, specified as a list of lists.

[4] Rotate the matrix 90 degrees at a time, until we reach the correct one.

[5] Show the new matrix.

File: matrix-rotation (the rest)
sub rot90 (@matrix)                                     # [6]
{
  my @new;                                              # [7]

  for ^@matrix[0].elems -> $col                         # [8]
  {
    my @row;                                            # [9]
    for ^@matrix.elems -> $row                          # [10]
    {
      say ":c ", @matrix[$row][$col] if $verbose;
      @row.push(@matrix[$row][$col]);                   # [11]
    }
    say ":r ", @row.reverse if $verbose;
    @new.push(@row.reverse.list);                       # [12]
  }

  return @new;                                          # [13]
}

[6] This one does the rotation, 90 degrees to the left.

[7] The new matrix goes here.

[8] Iterate over the columns,

[9] • We collect the new row here.

[10] • Iterate over the rows,

[11] • • get the cell value and add it to the new row.

[12] • Add the new row, reversed (as the for loop does it the wrong way). Note the list coercer, as reverse gives a Sequence.

[13] Return the new array.

Running it:

$ raku matrix-rotation
[(7, 4, 1), (8, 5, 2), (9, 6, 3)]

$ raku matrix-rotation 90
[(7, 4, 1), (8, 5, 2), (9, 6, 3)]

$ raku matrix-rotation 180
[(9, 8, 7), (6, 5, 4), (3, 2, 1)]

$ raku matrix-rotation 270
[(3, 6, 9), (2, 5, 8), (1, 4, 7)]

The whould check that the rotation roundtrips. Add «360» degrees like this:

File: matrix-rotation (changes only)
subset Degree where * == any(90, 180, 270, 360);

Running it:

$ raku matrix-rotation 360
[(1, 2, 3), (4, 5, 6), (7, 8, 9)]

Looking good.

Math::Matrix

We can use the Math::Matrix module, even if it doesn't support rotation out of the box. The program is much simpler this time:

File: matrix-rotation-module
use Math::Matrix;

subset Degree where * == any(90, 180, 270);

unit sub MAIN (Degree $degree is copy = 90, :$verbose);

my $matrix = Math::Matrix.new(((1,2,3), (4,5,6), (7,8,9)));    # [1]

say "$matrix\n" if $verbose;

while $degre
{
  $matrix = Math::Matrix.new($matrix.list-columns>>.reverse);  # [2]
  $degree -= 90;
}

say $matrix;

[1] Set up the initial matrix.

[2] Rotate 90 degrees at a time. The «list-columns» method gives the new rows, in the wrong order (as in the first program). We apply reverse on each row (with >>.) in one go to fix that.

Running it:

$ raku matrix-rotation-module
  7  4  1
  8  5  2
  9  6  3

$ raku matrix-rotation-module 180
  9  8  7
  6  5  4
  3  2  1

$ raku matrix-rotation-module 270
  3  6  9
  2  5  8
  1  4  7

Challenge #53.2: Vowel Strings

Write a script to accept an integer 1 <= N <= 5 that would print all possible strings of size N formed by using only vowels (a, e, i, o, u).

The string should follow the following rules:

  1. a’ can only be followed by ‘e’ and ‘i’.
  2. e’ can only be followed by ‘i’.
  3. i’ can only be followed by ‘a’, ‘e’, ‘o’, and ‘u’.
  4. o’ can only be followed by ‘a’ and ‘u’.
  5. u’ can only be followed by ‘o’ and ‘e’.

For example, if the given integer N = 2 then script should print the following strings:

ae
ai
ei
ia
io
iu
ie
oa
ou
uo
ue

A recursive procedure seems like a good idea.

File: vowel-string
unit sub MAIN (Int $n where 1 <= $n <= 5, :$verbose);   # [1]

my @start = <a e i o u>;                                # [2]
my %next;                                               # [3]

%next<a> = <e i>;                                       # [3a]
%next<e> = <i>;                                         # [3e]
%next<i> = <a e o u>;                                   # [3i]
%next<o> = <a u>;                                       # [3o]
%next<u> = <o e>;                                       # [3u]

say ": ", %next       if $verbose;
say ": ", %next.raku  if $verbose;

do-it($_) for @start;                                   # [4]

sub do-it ($string)                                     # [5]
{
  if $string.chars == $n                                # [6]
  {
     say $string;                                       # [6a]
  }
  else                                                  # [7]
  {
    my $last = $string.substr(*-1);                     # [8]
    say ": $string -> { @(%next{$last}).join("|") }" if $verbose;
    do-it("$string$_") for @(%next{$last});             # [9]
  }
}

[1] Legal values: 1, 2, 3, 4 or 5.

[2] We start with these letters.

[3] A list of legal letters following the current one.

[4] Iterate over the starting letters.

[5] The procedure is recursive.

[6] If we have reached the requested string length, print the string (6a) (and return).

[7] If not,

[8] • get the last character in the current string. *-1 gives the index of the last character when used in substr.

[9] • add the letters following the last character, and call ourselves with the new string recursively.

Running it:

$ raku vowel-string 1
a
e
i
o
u

$ raku vowel-string 2
ae
ai
ei
ia
ie
io
iu
oa
ou
uo
ue

$ raku vowel-string 3
aei
aia
aie
aio
aiu
eia
eie
eio
eiu
iae
iai
iei
ioa
iou
iuo
iue
oae
oai
ouo
oue
uoa
uou
uei

$ raku vowel-string 4
aeia
aeie
aeio
aeiu
aiae
aiai
aiei
aioa
aiou
aiuo
aiue
eiae
eiai
eiei
eioa
eiou
eiuo
eiue
iaei
iaia
iaie
iaio
iaiu
ieia
ieie
ieio
ieiu
ioae
ioai
iouo
ioue
iuoa
iuou
iuei
oaei
oaia
oaie
oaio
oaiu
ouoa
ouou
ouei
uoae
uoai
uouo
uoue
ueia
ueie
ueio
ueiu

$ raku vowel-string 5
aeiae
aeiai
aeiei
aeioa
aeiou
aeiuo
aeiue
aiaei
aiaia
aiaie
aiaio
aiaiu
aieia
aieie
aieio
aieiu
aioae
aioai
aiouo
aioue
aiuoa
aiuou
aiuei
eiaei
eiaia
eiaie
eiaio
eiaiu
eieia
eieie
eieio
eieiu
eioae
eioai
eiouo
eioue
eiuoa
eiuou
eiuei
iaeia
iaeie
iaeio
iaeiu
iaiae
iaiai
iaiei
iaioa
iaiou
iaiuo
iaiue
ieiae
ieiai
ieiei
ieioa
ieiou
ieiuo
ieiue
ioaei
ioaia
ioaie
ioaio
ioaiu
iouoa
iouou
iouei
iuoae
iuoai
iuouo
iuoue
iueia
iueie
iueio
iueiu
oaeia
oaeie
oaeio
oaeiu
oaiae
oaiai
oaiei
oaioa
oaiou
oaiuo
oaiue
ouoae
ouoai
ououo
ououe
oueia
oueie
oueio
oueiu
uoaei
uoaia
uoaie
uoaio
uoaiu
uouoa
uouou
uouei
ueiae
ueiai
ueiei
ueioa
ueiou
ueiuo
ueiue

Looking good.

And that's it.