Perl Weekly Challenge: Week 84

Challenge 1:

Reverse Integer

You are given an integer $N.

Write a script to reverse the given integer and print the result. Print 0 if the result doesn’t fit in 32-bit signed integer.

The number 2,147,483,647 is the maximum positive value for a 32-bit signed binary integer in computing.

Example 1:
Input: 1234
Output: 4321
Example 2:
Input: -1234
Output: -4321
Example 3:
Input: 1231230512
Output: 0

This is the code I wrote for Perl based on a C function I had written some time ago. In hindsight this could be a lot more perlish.

my $num = shift;
my $sign = 0;

if ($num < 0) {
    $sign = 1;
    $num = abs($num);
}

if ($num > 2_147_483_647) {
    say '0';
} else {

For instance I could have replaced the entire while loop by treating $num as a string and reversing it with the reverse() function.

    my $reversed = 0; 

    while ($num > 0) {
        $reversed = $reversed * 10 + $num % 10; 
        $num = int ($num / 10); 
    }

    say $sign ? q{-} : q{}, $reversed;
}

(Full code on Github.)

The same criticism applies to the Raku version but, oh well, atleast it works.

sub MAIN(Int $n) {

Because function parameters are immutable, $n needs to be copied to another variable if we want to slice and dice it.

    my $num = $n;

I could have used True and False for $sign as Raku has a boolean type.

    my $sign = 0;

    if ($num < 0) {
        $sign = 1;
        $num = abs($num);
    }

    if $num > 2_147_483_647 {
        say '0';
    } else {
        my $reversed = 0; 

        while $num > 0 {
            $reversed = $reversed * 10 + $num % 10;

In Perl I had to use the int() function to make sure the result of the division was an integer. Raku has an integer division operator, div so it's not necessary here.

            $num div= 10; 
        }

        say $sign ?? q{-} !! q{}, $reversed;
    }
}

(Full code on Github.)

Challenge 2:

Flip Array

You are given matrix of size m x n with only 1 and 0.

Write a script to find the count of squares having all four corners set as 1.

Example 1:
Input: [ 0 1 0 1 ]
       [ 0 0 1 0 ]
       [ 1 1 0 1 ]
       [ 1 0 0 1 ]

Output: 1
EXPLANATION

There is one square (3x3) in the given matrix with four corners as 1 starts at r=1;c=2.

[ 1 0 1 ]
[ 0 1 0 ]
[ 1 0 1 ]
Example 2:
Input: [ 1 1 0 1 ]
       [ 1 1 0 0 ]
       [ 0 1 1 1 ]
       [ 1 0 1 1 ]

Output: 4
EXPLANATION

There is one square (4x4) in the given matrix with four corners as 1 starts at r=1;c=1.

There is one square (3x3) in the given matrix with four corners as 1 starts at r=1;c=2.

There are two squares (2x2) in the given matrix with four corners as 1. First starts at r=1;c=1 and second starts at r=3;c=3.

Example 3:
Input: [ 0 1 0 1 ]
       [ 1 0 1 0 ]
       [ 0 1 0 0 ]
       [ 1 0 0 1 ]

Output: 0

I'll show you the Raku solution first.

sub MAIN(
    Str $file #= a file describing a matrix of 1's and 0's where every line
              #= is a row in the matrix.
) {
    my @matrix;

As entering a matrix on the command line would have been a bit awkward, I elected to read in text files where each row of 1's and 0's would be a separate line. This code reads such a file in and populates a 2d array, @matrix with the separated digits. If this code were being used in a production script, I would do a lot of checking to make sure it is in the correct format. As it stands, it just trusts the input will be usable.

    for $file.IO.lines -> $line {
        @matrix.push($line.comb);
    }

    my $squares = 0;

A double for loop sets up each row of the matrix as $m and each column as $n.

As we traverse the matrix, each element is checked if it is a 1. If it is, another loop is started, this time a C-style for loop; Raku reserves the for keyword for iteration over ranges and uses loop for the C-style one. This loop increments $side until we get to another 1 or we hit the right hand edge of the matrix.

If we did find a second 1, we have the top two corners of a square. We now go down $side rows (once again bounds checking) and then accross $side columns and see if there are 1's in those two locations. If there are, we have a square.

In production code I would consider 5 nested blocks as a code smell and try and refactor this into several functions.

    for 0 ..^ @matrix.elems -> $m {
        for 0 ..^ @matrix[$m].elems -> $n {
            if @matrix[$m][$n] == 1 {
                loop (my $side = 1; $n + $side < @matrix[$m].elems; $side++) {
                    if @matrix[$m][$n + $side] == 1
                        && $m + $side < @matrix.elems
                        && @matrix[$m + $side][$n] == 1
                        && @matrix[$m + $side][$n + $side] == 1 {
                        $squares++;
                    }
                } 
            }
        }
    }

    say $squares;
}

(Full code on Github.)

And this is Perl. The chief thing to note here is that Perl doesn't have real multidimensional arrays so our matrix is an array of array references which makes accessing elements a bit awkward.

my $file = shift // usage();

my @matrix;
open my $fn, '<', $file or die "$OS_ERROR\n";
while (my $line = <$fn>) {
    chomp $line;
    push @matrix, [ split //, $line ];
}
close $fn;

my $squares = 0;

for my $m (0 .. scalar @matrix - 1) {
    for my $n (0 .. (scalar @{$matrix[$m]} - 1)) {
        if ($matrix[$m]->[$n] == 1) {
            for (my $side = 1; $n + $side < scalar @{$matrix[$m]}; $side++) {
                if ($matrix[$m]->[$n + $side] == 1
                    && $m + $side < scalar @matrix
                    && $matrix[$m + $side]->[$n] == 1
                    && $matrix[$m + $side]->[$n + $side] == 1) {
                    $squares++;
                }
            } 
        }
    }
}

say $squares;

(Full code on Github.)