BLOG: The Weekly Challenge #068

Sunday, Jul 12, 2020| Tags: Perl, Raku

HEADLINE

The Week #068 is my 23rd week in a row participating the weekly challenge. For the first time, I created a package to deal with the “Reorder List” task. It really helped me in thinking straight when dealing with linked list. With the help my favourite CPAN module Moo, creating class is like piece of cake as shown below.

package Node;

use Moo;

has v => (is => 'rw');
has c => (is => 'rw');

sub show_link {
    my ($self) = @_;

    my $head = $self;
    my @v = ();
    while ($head->c) {
        push @v, $head->v;
        $head = $head->c;
    }
    push @v, $head->v;

    return sprintf("%s", join ' -> ', @v);
}

I will talk more in detail later down below.

As you all know by now, I always tend to translate my Perl solution literally to get the Raku version. So this gave me the opportunity to create class in Raku which I never done so far if you ask about weekly challenge. I enjoyed creating Raku class to say the least. It has more meat than the Perl counterpart. I will share more about it down below.

Let me share my solutions to the Perl Weekly Challenge - 068.


TASK #1 › Zero Matrix

Submitted by Mohammad S Anwar


You are given a matrix of size M x N having only 0s and 1s.

Write a script to set the entire row and column to 0 if an element is 0.


For this task, I tried to be more organised than before and have a separate subroutine for each task. To begin with, first I came with sub get_matrix() to create a matrix given $rows and $cols.

Nothing special about the subroutine except for the fact, I introduced little hack where I forced to it have more 1s than 0s in the matrix so that I see meaningful end result. For this I created fixed set of 1s and 0s, so that when I randomly pick one, the probability of getting 1 is highter than 0.

sub get_matrix {
    my ($rows, $cols) = @_;

    # prabability of picking 1 is higher than 0 (80:20).
    my $min   = 0;
    my $max   = 9;
    my $array = [ 1, 0, 1, 1, 1, 1, 1, 0, 1, 1 ];

    my $matrix  = [];
    foreach my $r (0..$rows) {
        foreach my $c (0..$cols) {
            $matrix->[$r][$c] = $array->[int($min + rand($max - $min))];
        }
    }

    return $matrix;
}

Next in line was to have sub display_matrix() to print nicely laid out the given matrix with label. No rocket science here.

sub display_matrix {
    my ($label, $matrix) = @_;

    print "$label\n";
    foreach my $r (0..$#$matrix) {
        print sprintf("[ %s ]\n", join ', ', @{$matrix->[$r]});
    }
    print "\n";
}

Now it is time to introduce the real hero of the task sub make_zero_matrix(). The method receives the matrix generated using the given rows and cols from the command line. It creates new zero matrix based on the given matrix, so that it doesn’t get mix up. It is commented out nicely for anyone to read it.

sub make_zero_matrix {
    my ($matrix) = @_;

    my $rows = $#$matrix;
    my $cols = $#{$matrix->[0]};

    my $zero_matrix = [];
    foreach my $r (0..$rows) {
        foreach my $c (0..$cols) {
            if ($matrix->[$r][$c] == 0) {
                # make zero row
                $zero_matrix->[$r][$_] = 0 foreach (0..$cols);
                # make zero col
                $zero_matrix->[$_][$c] = 0 foreach (0..$rows);
            }
        }
    }

    # fill empty cells, if any.
    foreach my $r (0..$rows) {
        foreach my $c (0..$cols) {
            $zero_matrix->[$r][$c] = 1 unless
                defined $zero_matrix->[$r][$c];
        }
    }

    return $zero_matrix;
}

Time to do some Raku magic.

First thing first, I created sub get-matrix() to create a matrix given rows and cols. The only bit that needs attention is the use of pick(). I have used this in the past. It is mainly responsible to pick a random member of the given list. Rest of the code is usual Raku magic.

Honestly speaking, it looks elegant and clean.

sub get-matrix(Int $rows where { $rows >= 1 },
               Int $cols where { $cols >= 1 }) {

    # prabability of picking 1 is higher than 0 (80:20).
    my $min   = 0;
    my $max   = 9;
    my $array = [ 1, 0, 1, 1, 1, 1, 1, 0, 1, 1 ];

    my $matrix  = [];
    for 0..$rows -> $r {
        for 0..$cols -> $c {
            $matrix.[$r][$c] = $array.pick;
        }
    }

    return $matrix;
}

Even the sub display-matrix() looks smart, with $matrix.[$r].join(', ').

sub display-matrix($label, $matrix) {

    say $label;
    for 0..$matrix.elems - 1 -> $r {
        say sprintf("[ %s ]", $matrix.[$r].join(', '));
    }
}

The following sub make-zero-matrix() deals with main task i.e. making zero matrix.

sub make-zero-matrix($matrix) {

    my $rows = $matrix.elems - 1;
    my $cols = $matrix.[0].elems - 1;

    my $zero_matrix = [];
    for 0..$rows -> $r {
        for 0..$cols -> $c {
            if $matrix.[$r][$c] == 0 {
                # make zero row
                (0..$cols).map({ $zero_matrix.[$r][$_] = 0 });
                # make zero col
                (0..$rows).map({ $zero_matrix.[$_][$c] = 0 });
            }
        }
    }

    # fill empty cells, if any.
    for 0..$rows -> $r {
        for 0..$cols -> $c {
            $zero_matrix.[$r][$c] = 1 unless
                defined $zero_matrix.[$r][$c];
        }
    }

    return $zero_matrix;
}

With all the handy subroutines defined above, getting the final solution becomes school boy job.

use strict;
use warnings;

my $R = $ARGV[0];
my $C = $ARGV[1];

my $matrix;
if (defined $R && defined $C) {
    die "ERROR: Invalid rows [$R].\n" unless ($R =~ /^\d+$/ && $R >=2 );
    die "ERROR: Invalid cols [$C].\n" unless ($C =~ /^\d+$/ && $C >=2 );

    $matrix = get_matrix(--$R, --$C);
}
else {
    $matrix = [
        [1, 0, 1],
        [1, 1, 1],
        [1, 0, 1],
    ];
}

my $zero_matrix = make_zero_matrix($matrix);

display_matrix('Matrix:', $matrix);
display_matrix('Zero Matrix:', $zero_matrix);

Although, the Raku app is not exactly the same as Perl but you would agree with me, it has upper hand as far as the presentation is concerned.

use v6.d;

sub MAIN(Int :$R? is copy where { $R >= 2 } = 3,
         Int :$C? is copy where { $C >= 2 } = 3) {

    my $matrix      = get-matrix(--$R, --$C);
    my $zero_matrix = make-zero-matrix($matrix);

    display-matrix('Matrix:', $matrix);
    display-matrix('Zero Matrix:', $zero_matrix);
}

Time to do some testing, first comes Perl using friend like Test::More and Test::Deep.

use Test::More;
use Test::Deep;

is_deeply( make_zero_matrix([[1, 0, 1],[1, 1, 1],[1, 1, 1]]),
           [[0, 0, 0],[1, 0, 1],[1, 0, 1]],
           'testing example 1' );
is_deeply( make_zero_matrix([[1, 0, 1],[1, 1, 1],[1, 0, 1]]),
           [[0, 0, 0],[1, 0, 1],[0, 0, 0]],
           'testing example 2' );

done_testing;

Raku once gain wins the race without the need of external library.

use Test;

is-deeply make-zero-matrix([[1, 0, 1], [1, 1, 1], [1, 1, 1]]),
          [[0, 0, 0], [1, 0, 1], [1, 0, 1]],
          'testing example 1';
is-deeply make-zero-matrix([[1, 0, 1], [1, 1, 1], [1, 0, 1]]),
          [[0, 0, 0], [1, 0, 1], [0, 0, 0]],
          'testing example 2';

done-testing;

TASK #2 › Reorder List

Submitted by Mohammad S Anwar


You are given a singly linked list $L as below:

L0 →  L1 →  … →  Ln-1 →  Ln

Write a script to reorder list as below:

L0 →  Ln →  L1 →  Ln-1 →  L2 →  Ln-2 →

You are ONLY allowed to do this in-place without altering the nodes’ values.



Personally I enjoy task related to Linked List. I could have done with without actually creating linked list but I didn’t want to cheat. So I decided to create class Node with just two attributes v and c to hold value and child respectively. Using Moo, creating class is a child’s play as you can see below.

package Node;

use Moo;

has v => (is => 'rw');
has c => (is => 'rw');

sub show_link {
    my ($self) = @_;

    my $head = $self;
    my @v = ();
    while ($head->c) {
        push @v, $head->v;
        $head = $head->c;
    }
    push @v, $head->v;

    return sprintf("%s", join ' -> ', @v);
}

Once the class Node is ready, time to create sub reorder_list() to perform the task. In this method, I am doing two things distinctively i.e. prepare singly linked list and reorder in the end. It is nicely commented out for you to follow.

sub reorder_list {
    my ($list) = @_;

    $list =~ s/\s+//g;
    $list = [ split /\-\>/, $list ];
    my $head = Node->new(v => shift @$list);
    my $link = [ $head ];

    # prepare singly linked list
    foreach my $v (@$list) {
        my $node = Node->new(v => $v);
        $link->[-1]->c($node);
        push @$link, $node;
    }

    # reorder linked list
    my $i = 1;
    foreach (0 .. int($#$list/2)) {
        my $node = pop @$link;
        splice(@$link, $i, 0, $node);

        # remove child from the last node
        $link->[-1]->c(undef);

        # link new node to previous node
        $link->[$i-1]->c($node);

        # make the next node as child of new node
        $node->c($link->[$i+1]);

        $i += 2;
    }

    return $head;
}

For the first time, I am creating Raku class as far as the weekly challenge is concerned.

I must confess, what you see down below is not what I wrote in the first place. It has gone through few changes, thanks to many experts. Let me share in details. At first I didn’t have is rw assigned to the attribute, instead I had setter method. Jonathan Worthington suggested the change to make it cleaner.

@khalidelboray suggested instead of my @v = ($!v), I could simply do something like my @v = $!v.

When I made my code public, I received another cool suggestion by Moritz Lenz to replace my existing subroutine to show link.

class Node {
    has Int  $.v is rw;
    has Node $.c is rw;

    # my implementation
    method _show-link() {
        my $c = $!c;
        my @v = $!v;

        while defined $c {
            @v.push: $c.v;
            $c = $c.c;
        }

        return @v.join(' -> ');
    }

    # contributed by Moritz Lenz
    method show-link() {
        return join ' -> ', (self, *.c ...^ !*.defined).map: *.v;
    }
}

I had another issue with my setter method earlier as I couldn’t set Nil to the attribute c. I was trying to do $n->c(Nil) and keep getting error. I posted the error message and received the solution by @scimon. I was suggested to do $n->c(Node) instead.

With the introduction of is rw, I thought I can now make call like $n1->c($n2). I was wrong and corrected by @khalidelboray. I was told the correct way to perform the same is $n1.c = $n2. Also with is rw, I can easily set Nil like $n.c = Nil as suggested by Jonathan Worthington.

For me this is my best work in Raku so far.

sub reorder-list(Str $linked-list is copy) {

    $linked-list ~~ s:g/\s//;
    my @list = $linked-list.split('->');
    my $head = Node.new(v => @list.shift.Int);
    my @link = $head;

    # prepare singly linked list
    for @list -> $v {
        my $node = Node.new(v => $v.Int);
        @link.tail.c = $node;
        @link.push: $node;
    }

    # reorder linked list
    my Int $min = 0;
    my Int $max = (@list.elems/ 2).Int;
    my Int $i   = 1;
    for $min .. $max-1 {
        my $node = @link.pop;
        @link.splice($i, 0, $node);

        # remove child from the last node
        @link.tail.c = Nil;

        # link new node to previous node
        @link[$i - 1].c = $node;

        # make the next node as child of new node
        $node.c = @link[$i + 1];

        $i += 2;
    }

    return $head;
}

Time to solve the task, it literaly becomes one-liner, do you see chaining?

package main;

my $list = $ARGV[0]//'1 -> 2 -> 3 -> 4 -> 5';
print reorder_list($list)->show_link, "\n";

Literal translation of Perl solution.

sub MAIN(Str :$linked-list = '1 -> 2 -> 3 -> 4 -> 5') {
    reorder-list($linked-list).show-link.say;
}

Unit test to make it complete.

package main;

use Test::More;

is( reorder_list('1 -> 2 -> 3 -> 4')->show_link,
    '1 -> 4 -> 2 -> 3',
    'testing 1 -> 2 -> 3 -> 4' );
is( reorder_list('1 -> 2 -> 3 -> 4 -> 5')->show_link,
    '1 -> 5 -> 2 -> 4 -> 3',
    'testing 1 -> 2 -> 3 -> 4 -> 5' );
is( reorder_list('1 -> 2 -> 3 -> 4 -> 5 -> 6')->show_link,
    '1 -> 6 -> 2 -> 5 -> 3 -> 4',
    'testing 1 -> 2 -> 3 -> 4 -> 5 -> 6' );

done_testing;

and Raku, just so I don’t forget it.

use Test;

is reorder-list('1 -> 2 -> 3 -> 4').show-link,
   '1 -> 4 -> 2 -> 3',
   'testing 1 -> 2 -> 3 -> 4';
is reorder-list('1 -> 2 -> 3 -> 4 -> 5').show-link,
   '1 -> 5 -> 2 -> 4 -> 3',
   'testing 1 -> 2 -> 3 -> 4 -> 5';
is reorder-list('1 -> 2 -> 3 -> 4 -> 5 -> 6').show-link,
   '1 -> 6 -> 2 -> 5 -> 3 -> 4',
   'testing 1 -> 2 -> 3 -> 4 -> 5 -> 6';

done-testing;

That’s it for this week. Speak to you soon.

SO WHAT DO YOU THINK ?

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

Contact with me