Laurent Rosenfeld Weekly Review: Challenge - 011

Sunday, Nov 17, 2019| Tags: Raku

Raku Solutions Weekly Review


Task #2: Displaying the Identity Matrix

This is derived in part from my blog post made in answer to the Week 11 of the Perl Weekly Challenge organized by Mohammad S. Anwar as well as answers made by others to the same challenge.

The challenge reads as follows:

Write a script to create an Identity Matrix for the given size. For example, if the size is 4, then create Identity Matrix 4x4. For more information about Identity Matrix, please read the Wikipedia page.

An identity matrix is a square matrix with ones on the main diagonal (top left to bottom right) and zeros everywhere else.

My Solutions

Let’s start with a boring plain-vanilla solution using two nested loops, as I would probably have to do in C, Pascal, Ada, or Java:

use v6;

sub MAIN (Int $size where * > 0) {
    my @matrix;
    $size--;
    for 0..$size -> $i {
        for 0..$size -> $j {
            @matrix[$i][$j] = $i == $j ?? 1 !! 0;
        }
    }
    say @matrix;
}

Here, we are using the MAIN subroutine to process the argument passed to the script (which, in this case, must be a strictly positive integer).

When the row index ($i) is equal to the column index ($j), we populate the item with a one. Otherwise, we set it to zero.

This works as expected, but it is a bit difficult to understand the printed output:

[[1 0 0] [0 1 0] [0 0 1]]

In such cases, it is useful to write an additional subroutine that can display the result in a way that is easy to understand. This is what the pretty-print subroutine below does:

use v6;

sub pretty-print (@matrix) {
    for @matrix -> @rows {
        say join " ", @rows;
    }
}
sub MAIN (Int $size where * > 0) {
    my @matrix;
    $size--;
    for 0..$size -> $i {
        for 0..$size -> $j {
            @matrix[$i][$j] = $i == $j ?? 1 !! 0;
        }
    }
    pretty-print @matrix;
}

The output now shows clearly an identity matrix:

$ perl6 matrix.p6 5
1 0 0 0 0
0 1 0 0 0
0 0 1 0 0
0 0 0 1 0
0 0 0 0 1

If the user omits to pass a parameter (or passes an invalid one), a convenient usage line is printed to the screen:

$ perl6 matrix.p6
Usage:
  matrix.p6 <size>

Identity Matrix in Functional Programming

Using the techniques of functional programming can make our code simpler.

A functional implementation in Raku / Perl 6 may look like this:

use v6;

sub prettify (@matrix) {
    return join "\n", map { join " ", $_}, @matrix;
}
sub MAIN (Int $size where * > 0) {
    my @matrix = map { my $i = $_; map { $i == $_ ?? 1 !! 0 }, 0..$size },  0..$size;
    say prettify @matrix;
}

Since we’re aiming at doing functional programming, we have replaced the pretty-print subroutine by a pure function, prettify, with no side effect: it does not print anything, but only returns a printable formatted string, which the user may then decide to print (as we did here), or may possibly do something else, such as storing it into a file for future use.

Note that the populating the identity matrix takes only one code line. The rest is really for prettifying the result and printing it.

We could also use data pipelines with chained method invocations:

sub prettify (@matrix) {
    @matrix.map({join(" ",$_)}).join("\n");
}
sub MAIN (Int $size where * > 0) {
    say prettify (1..$size).map( -> $i { (1..$size).map( { $_ == $i ?? 1 !! 0 })});
}

although I’m not really convinced this is any better in this specific case, as it is a bit tedious to get these pesky closing parentheses and curlies right.

Alternative Solutions

Arne Sommer provided not less than four solutions. One of them uses the Math::Matrix module, whose new-identity method just creates an identity matrix out-of-the-box! My favorite solution, however,might probably be this quite concise one, which populates a matrix with zeros and then overwrites the zeros of the first diagonal with ones:

unit sub MAIN (Int $size where $size > 0);

my @im[$size;$size] = 0 xx $size xx $size;
@im[$_;$_] = 1 for ^$size;
say @im;

Note the use of shaped arrays in Arne’s solution above. Take the time to study Arne’s other solutions or read his blog post (see below).

Fench Chang used the same strategy. His program populates a matrix with zeros and then overwrites the zeros of the first diagonal with ones:

sub MAIN(Int $n where * > 0) {
    my @a = [0 xx $n] xx $n;
    @a[$_][$_] = 1 for 0 .. $n - 1;
    @a.say;
}

Joelle Maslak also used the same strategy, although in a different manner, i.e. doing it row by row:

sub MAIN(UInt:D $size where * ≥ 1) {
    for ^$size -> $row {
        my @row = (^$size).map: { 0 };
        @row[$row] = 1;
        say @row.join(" ");
    }
}

Francis J. Whittle made a very imaginative solution. His program loops over the range (the matrix’s dimension) and, for each position, check if the row and column are equal, and finally coerces the Boolean result of that comparison into an integer, thus yielding 1 when they are equal, and 0 otherwise. The matrix is created in just one code line:

(^$n).map: -> $i { (^$n).map: -> $j { Int($j == $i) } };

Jo-Christian Oterhals also delivered a creative solution using an &idm code block that uses a gather ... take construct and returns an array of arrays.

my &idm = -> $size { gather for ^$size -> $y { take map { Int($_ == $y) }, ^$size } };
.join(' ').say for idm(4); # 4... 2... 16... or whatever...

The second line of the code above provided pretty printing of the resulting matrix:

1 0 0 0
0 1 0 0
0 0 1 0
0 0 0 1

Ruben Westerberg‘s solution is also quite creative. He created for each line an array consisting of one 1 and as many zeros are required by the matrix’s dimension, and used the rotate built-in function to place the 1 in its proper position. Here, populating the matrix and displaying it prettily take just one code line:

my $s=@*ARGS[0]//10;
(([1,|(0 xx $s-1)].rotate: -$++)  xx $s).map: *.join(" ").say;

Simon Proctor also succeeded to populate and display prettily the matrix in just one code line:

sub MAIN ( UInt \N where * > 0 ) {
    (1..N).map( -> $i { (1..N).map( -> $j { $j == $i ?? 1 !! 0 }).join(" ") } ).join("\n").say;
}

Jaldhar H. Vyas used a straight forward technique quite similar to my first solution, with two nested loops to populate the matrix’s cells with 1 when the row and column indexes are equal and with zeros otherwise

multi sub MAIN(
    Int $n where $n > 1#= the size of the identity matrix
) {

    for (0 .. $n - 1) -> $i {
        for (0 .. $n - 1) -> $j {
            print ($j == $i) ?? '1 ' !! '0 ';
        }
        print "\n";
    }
}

Ozzy used essentially the same strategy with two nested for loops:

sub MAIN (Int $dim)
{
    my @md[$dim;$dim];

    for 0..$dim-1 -> $i {
        for 0..$dim-1 -> $j {
            @md[$i;$j]= $i==$j ?? 1 !! 0;
            printf "%3i", @md[$i;$j];
        }
    printf "\n";
    }
}

SEE ALSO

Only one blog this time (besides mine):

Wrapping up

Please let me know if I forgot any of the challengers or if you think my explanation of your code misses something important (send me an e-mail or just raise an issue against this GitHub page).

SO WHAT DO YOU THINK ?

If you have any suggestions or ideas then please do share with us.

Contact with me