Perl Weekly Challenge: Week 204

Challenge 1:

Monotonic Arrays

You are given an array of integers.

Write a script to find out if the given array is Monotonic. Print 1 if it is otherwise 0.

An array is Monotonic if it is either monotone increasing or decreasing.

Monotone increasing: for i <= j , nums[i] <= nums[j]
Monotone decreasing: for i <= j , nums[i] >= nums[j]
Example 1
Input: @nums = (1,2,2,3)
Output: 1
Example 2
Input: @nums = (1,3,2)
Output: 0
Example 3
Input: @nums = (6,5,5,4)
Output: 1

Raku's supercharged version of the 'reduce' operator found in functional languages make this a one-liner. We can take almost any existing operator and enclose it in [] to make it apply to all the elements of an array. So the code below says if all the elements of the command line argument array are less than or equal to the element after or if all of them are greater than or equal to the one after, it is a monotonic array so print 1 otherwise print 0.

say ([>=] @*ARGS) || ([<=] @*ARGS) ?? 1 !! 0;

(Full code on Github.)

Perl is not blessed with such abundance so we have to do things the long way around. My first approach looked like this:

Set up a variable to hold the result.

my $result = 1;

Are we going up or down? Look at the first two elements. If the second is greater than or equal to the first, all the subsequent elements should be increasing. If it is less, subsequent elements should be less than or equal. Store the findings in the variable $comp; 1 if going up or 0 if going down.

my $comp = ($nums[1] >= $nums[0]) ? 1 : 0;

EDIT It just occurred to me that this is not going to be enough if both initial elements are equal.

Now for each pair of elements (except the first which we've already looked at which is why we're starting from 2.) we check if they are increasing or decreasing as they should. If they are not, we change $result to 0 and stop the loop. The array is not monotonic so there is no point in continuing.

for my $i (2 .. scalar @nums - 1) {
    if (($comp && $nums[$i] >= $nums[$i - 1]) || (!$comp && $nums[$i] <= $nums[$i - 1])) {
        $result = 0;
        break;
    }
}

And this works. However I wasn't satisfied with the if (well, unless) check. It seemed I could be made simpler even if not Raku levels of simple. What I did was to make $comp, instead of a scalar, a code reference to an anonymous subroutine that did the comparison like this:

my $comp = ($nums[1] >= $nums[0]) 
    ? sub { $_[0] >= $_[1] }
    : sub { $_[0] <= $_[1] };

Now the conditional looks like this:

unless (&$comp($nums[$i], $nums[$i - 1])) {

which is better I think.

It would have been even better if I could take a reference to an operator e.g.

my $comp = ($nums[1] >= $nums[0]) ? &{ >= } : &{ <= };

Then I could have written

unless ($nums[$i] &$comp $nums[$i - 1])) {

I could have sworn you can do that in Perl but apparently you cant.

(Full code on Github.)

Challenge 2:

Reshape Matrix

You are given a matrix (m x n) and two integers (r) and (c).

Write a script to reshape the given matrix in form (r x c) with the original value in the given matrix. If you can’t reshape print 0.

Example 1
Input: [ 1 2 ]
       [ 3 4 ]

    $matrix = [ [ 1, 2 ], [ 3, 4 ] ]
    $r = 1
    $c = 4

Output: [ 1 2 3 4 ]
Example 2
Input: [ 1 2 3 ]
       [ 4 5 6 ]

    $matrix = [ [ 1, 2, 3 ] , [ 4, 5, 6 ] ]
    $r = 3
    $c = 2

Output: [ [ 1, 2 ], [ 3, 4 ], [ 5, 6 ] ]

    [ 1 2 ]
    [ 3 4 ]
    [ 5 6 ]
Example 3
Input: [ 1 2 ]

    $matrix = [ [ 1, 2 ] ]
    $r = 3
    $c = 2

Output: 0

The hardest part about solving this challenge was deciding how to represent the input and output. For the input, I chose to make $r and $c be the first two command line arguments. Additional arguments will be strings of digits separated by spaces each of which represents a row in the input.

sub MAIN(
    $r,
    $c,
    *@args
) {

The first step is to parse those parameters and convert them into an array of integers. We want to get rid of all the sub-arrays (rows) so we "flatten" them with .flat()

    my @input = @args.map({ $_.split(/\s/).map({ $_.Int; }); }).flat;

Now we have a single list of integers. If this is shorter than $r multiplied by $c, we won't be able to reshape so we just print 0 and end the program.

    if $r * $c > @input.elems {
        say 0;
        exit;
    }

    my @output;

Now we reconstitute @input into an array of arrays where each sub-array is a row but this time the 'shape of the output array is determined by $r and $c.

    for 0 ..^ $r {
        my @temp;
        for 0 ..^ $c {
            @temp.push(@input.shift);
        }
        @output.push(@temp);
    }

The rest of the code merely prints the output in a way similar to that show in the spec.

    if (@output.elems > 1) {
        print '[ ';
    }

    @output.map({ '[ ' ~ @$_.join(q{, }) ~ ' ]' }).join(q{, }).print;

    if (@output.elems > 1) {
        print ' ]';
    }

    print "\n";
}

(Full code on Github.)

And here is the Perl version.

my $r = shift;
my $c = shift;

There is no need for flattening because Perl does that for you.

my @input = map { int $_; } map { split /\s+/, $_; } @ARGV;

if ($r * $c > scalar @input) {
    say 0;
    exit;
}

my @output;

for my $i (0 .. $r - 1) {
    my @temp;
    for my $j (0 .. $c - 1) {
        push @temp, shift @input;
    }

However sub-arrays have to included in the parent array by reference which can complicate working with them a little bit.

    push @output, \@temp;
}

if (scalar @output > 1) {
    print '[ ';
}

print join q{, }, map { '[ ' . (join q{, }, @{$_}) . ' ]' } @output;


if (scalar @output > 1) {
    print ' ]';
}

print "\n";

(Full code on Github.)