Perl Weekly Challenge: Week 53

Challenge 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 ]

This time I will do the Raku version first. The MAIN() sub just checks that the $angle is a valid value, calls the rotate() routine below and displays the result so I won't show it here. rotate() looks like this:

sub rotate(Int $angle) {

I suppose I could have made the matrix a parameter but the problem description only specifies one particular kind so I hardcoded it as an array of arrays.

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

I assigned the length of @matrix to a variable because we will be using it several times later. It is called $side because this is the length of a side the matrix. As a result my code will not work properly with a "rectangular" (i.e. 3 x 4 or 7 x 5) matrix. As ours is "square" this doesn't matter for now.

    my $side = @matrix.elems;

If $angle is 90° we need to rotate the matrix once, if 180° twice, and if 270° three times.

    for (0 ..^ $angle / 90) {

I created a new matrix and transposed the elements from the old one into it and then replaced the old one with it.

        my @newMatrix;
        for (0 ..^ $side) -> $row {
            for (0 ..^ $side) -> $col {
                @newMatrix[$col].unshift(@matrix[$row][$col]);
            }
        }
        @matrix = @newMatrix;

While this works, I had a dim memory in the corner of my mind that there is a way to rotate a matrix in place. Some googling later, I came up with this:

        for (0 ..^ $side / 2) -> $row {
            for ($row ..^ $side - $row - 1) -> $col {
                my $temp = @matrix[$row][$col];

                @matrix[$row][$col] = @matrix[$side - 1 - $col][$row];

                @matrix[$side - 1 - $col][$row] =
                    @matrix[$side - 1 - $row][$side - 1 - $col];

                @matrix[$side - 1 - $row][$side - 1 - $col] =
                    @matrix[$col][$side - 1 - $row];

                @matrix[$col][$side - 1 -$row] = $temp;
            }
        }

Finally we return the rotated matrix.

    }

    return @matrix;
}

(Full code on Github.)

This is the Perl version of rotate().

sub rotate {
    my ($angle) = @_;
    my @matrix = (
        [ 1, 2, 3 ],
        [ 4, 5, 6 ],
        [ 7, 8, 9 ],
    );

    my $side = scalar @matrix;

    for (1 .. $angle / 90) {
        for my $row (0 .. ($side / 2) - 1) {
            for my $col ($row .. ($side - $row - 1) - 1) {
                my $temp = $matrix[$row][$col];

                $matrix[$row][$col] = $matrix[$side - 1 - $col][$row];

               $matrix[$side - 1 - $col][$row] =
                    $matrix[$side - 1 - $row][$side - 1 - $col]; 

                $matrix[$side - 1 - $row][$side - 1 - $col] =
                    $matrix[$col][$side - 1 - $row]; 

                $matrix[$col][$side - 1 -$row] = $temp;
            }
        }
    }

    return @matrix;
}

(Full code on Github.)

Challenge 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

Once again, I'm describing my Raku solution first, and once again only showing the main part, the generate() routine.

sub generate(Int $n) {

I encoded the generation rules as a hash whose keys are vowels and values are the valid succeeding characters.

    my %rules = (
        a => [< e i >],
        e => [< i >],
        i => [< a e o u >],
        o => [< a u >],
        u => [< e o >]
    );

    my @generated;
    for (1 .. $n) -> $i {

If $n is 1, there is no need to do anything except list out the vowels. I didn't strictly speaking need to sort this list but it looks better if the results are in alphabetical order.

        if ($i == 1) {
            @generated = %rules.keys.sort;

If $n is 2 or more, we repeatedly replace each element in @generated (which at the start consists of five one-character strings: 'a', 'e', 'i', 'o', 'u' because of the $n = 1 case.) with one or more strings consisting of a copy of the existing element plus a value from the %rules hash where the key is the last character of the existing element.

Note the | character in the returned value of the outer map(). This is so the result we are adding to @generated is "flattened" into a individual values rather than a single array.

        } else {
            @generated = @generated.map({
                my $e = $_;
                | %rules{$e.substr(*-1, 1)}.values.map({  $e ~ $_; });
            });
        }
    }

    return @generated;
}

(Full code on Github.)

Here is the Perl version of the generate() routine.

sub generate {
    my ($n) = @_;

    my %rules = (
        a => [qw/ e i /],
        e => [qw/ i /],
        i => [qw/ a e o u /],
        o => [qw/ a u /],
        u => [qw/ e o /]
    );

    my @generated;
    for my $i (1 .. $n) {
        if ($i == 1) {
            @generated = sort keys %rules;
        } else {
            @generated = map {
                my $e = $_;
                map {  $e . $_; } @{ $rules{substr $e, -1, 1} };
            } @generated;
        }
    }

    return @generated;
}

(Full code on Github.)