Perl Weekly Challenge: Week 381

Challenge 1:

Same Row Column

You are given a n x n matrix containing integers from 1 to n.

Write a script to find if every row and every column contains all the integers from 1 to n.

Example 1
Input: @matrix = ([1, 2, 3, 4],
                  [2, 3, 4, 1],
                  [3, 4, 1, 2],
                  [4, 1, 2, 3],)
Output: true
Example 2
Input: @matrix = ([1])
Output: true
Example 3
Input: @matrix = ([1, 2, 5],
                  [5, 1, 2],
                  [2, 5, 1],)
Output: false

Elements are out of range 1..3.
Example 4
Input: @matrix = ([1, 2, 3],
                  [1, 2, 3],
                  [1, 2, 3],)
Output: false
Example 5
Input: @matrix = ([1, 2, 3],
                  [3, 1, 2],
                  [3, 2, 1],)
Output: false

To be honest, this is probably not my best work. There is probably a more elegant solution But this works at least.

The first problem is getting the matrix into the script. I did it via the command-line arguments. Each one is a string that represents a row in the matrix. The columns are separated by white space. So for e.g. example 3, they would look like "1 2 5" "5 1 2" "2 5 1".

We can reconstitute the matrix with the line below which splits each argument with .map() and .words creating a 2d array. Because command-line arguemnts are immutable in Raku, we have to define them in the MAIN() function signature like this: *@matrix is copy. The is copy trait makes them mutable again.

@matrix = @matrix.map({ [ .words ] });

$n is the length of a side of the matrix. As it is a square matrix, only one length is needed.

 my $n = @matrix[0].elems;

Here we define all the integers from 1 to $n.

 my @ints = 1 .. $n;

The last bit of setup is a Boolean variable to hold the result. It is initially set to True.

 my $isSame = True;

Now we take the index of each consecutive row and column ($side)...

for 0 ..^ $n -> $side {

...and sort the row in ascending numeric order then compare it to @ints element-by-element using the Z== operator. This returns a Boolean for every pair of elements compared so if e.g. $n = 4, we would get an array of four Booleans. Then we test if .any() of the array elements are False. The same procedure is performed for the column. If eaither of these contain a False value...

    if (@matrix[$side].sort({ $^a <=> $^b }) Z== @ints).any == False ||
    (@matrix[0 ..^ $n;$side].sort({ $^a <=> $^b }) Z== @ints).any == False {

...the result is False. There is no point in checking the remaining rows and columns so we break out of the loop.

        $isSame = False;
        last;
    }
}

Finally, we print the result.

say $isSame;

(Full code on Github.)

No need to worry about immutability in Perl.

my @matrix = map { [ split /\s+/ ] } @ARGV;

my $n = scalar @matrix;

Instead of an array of integers, we coalesce them into a string for reasons mentioned next.

my $ints = join q{}, 1 .. $n;
my $isSame = true;

Because we don't have Z== or .any() in Perl, we have to try a different tack. I chose to convert each row and column into strings which were then compared against $ints. This seemed to be the simplest way to leverage Perls' builtin feastures rather than add extra code to emulate Raku.

for my $side (0 .. $n - 1) {

    my $row = join q{}, sort{ $a <=> $b } @{$matrix[$side]};
    my $col = join q{}, sort { $a <=> $b } map { $_->[$side] } @matrix;

    if ($row ne $ints || $col ne $ints) {
        $isSame = false;
        last;
    }
}

say $isSame ? 'true' : 'false';

(Full code on Github.)

Challenge 2:

Smaller Greater Element

You are given an array of integers.

Write a script to find the number of elements that have both a strictly smaller and greater element in the given array.

Example 1
Input: @int = (2,4)
Output: 0

Not enough elements in the array.
Example 2
Input: @int = (1, 1, 1, 1)
Output: 0
Example 3
Input: @int = (1, 1, 4, 8, 12, 12)
Output: 2

The elements are 4 and 8.
Example 4
Input: @int = (3, 6, 6, 9)
Output: 2

Both instances of 6.
Example 5
Input: @int = (0, -5, 10, -2, 4)
Output: 3

The elements are 0, -2, and 4.

I was on firmer footing with this one. In fact, both the Raku and Perl solutions are one-liners.

First we bring in the input via command-line arguments and sort it in ascending numeric order. This is assigned to a new array, @a, to save some characters later on.

my @a = @*ARGS.sort({$^a <=> $^b});

We filter out elements which are not the smallest value or largest value with .grep() and count whatevers left with .elems(). This is the answer so we print it out with .say().

@a.grep({ $_ != @a[0] && $_ != @a[*-1] }).elems.say

(Full code on Github.)

The Perl version works the same and is almost the same length.

my @a = sort { $a <=> $b } @ARGV; say scalar grep {$_ != $a[0] && $_ != $a[-1] } @a

(Full code on Github.)