Four Corners with Raku

by Arne Sommer

Four Corners with Raku

[99] Published 31. October 2020.

This is my response to the Perl Weekly Challenge #084.

Challenge #084.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

The Maximum Negative Value

We need this limit as well, but the challenge does not specify it. So I looked it up, and IBM had the answer: -2147483648.


File: reverse-integer
#! /usr/bin/env raku

unit sub MAIN (Int $N);            # [1]

my $sign = $N < 0                  # [2]
  ?? "-"                           # [2a]
  !! "";                           # [2b]
  
my $new = $sign ~ $N.abs.flip;     # [3]

-2147483648 <= $new <= 2147483647  # [4]
  ?? say $new                      # [4a]
  !! say "0";                      # [4b]

[1] Ensure that the input is an integer.

[2] Extract the sign, as a string (i.e. "" for a positive value).

[3] Start with the sign, if any, then the number without the possible sign (abs), as a string, reversed (flip).

[4] If the number is within the limits, print it [4a], or fasil to zero [4b].

See docs.raku.org/routine/abs for more information about the abs method.

See docs.raku.org/routine/flip for more information about the flip method.

Running it:

$ ./reverse-integer 1234
4321

$ ./reverse-integer -1234
-4321

$ ./reverse-integer 1231230512
0

A Perl Version

This is a straight forward translation, so I'll present it without comments.

File: reverse-integer-perl
#! /usr/bin/env perl

use feature 'say';

my $N = $ARGV[0] // die "Please specify an integer";

die "Integer only" unless $N =~ /^\-?\d+$/;

my $sign = $N < 0
  ? "-"
  : "";
  
my $new = $sign . reverse(abs($N));

($new >= -2147483648 && $new <= 2147483647)
  ? say $new
  : say "0";

Running it gives the same result as the Raku version:

$ ./reverse-integer-perl 1234
4321

$ ./reverse-integer-perl -1234
-4321

$ ./reverse-integer-perl 1231230512
0

Challenge #084.2: Find Square

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.

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

The first problem is how to specify the matrices to the program. I have chosen to place them in their own (text) files, like this:

File: example1.txt
0 1 0 1
0 0 1 0
1 1 0 1
1 0 0 1

Reading a matrix from a file is a repetition of what I did in the second part of Primal Words with Raku, my response to the Perl Weekly Challenge #076.

File: find-square
#! /usr/bin/env raku

unit sub MAIN ($matrix where $matrix.IO.f && $matrix.IO.r = 'example1.txt',
	       :v(:$verbose));                                        # [1]

my @matrix = $matrix.IO.lines.map( *.lc.words.list );                 # [2]

die "Uneven matrix row length" unless [==] @(@matrix)>>.elems;        # [3]
die "0 and 1 only" unless all( @matrix>>.List.flat ) eq any( 0, 1 );  # [4]

my $rows    = @matrix.elems;                                          # [5]
my $cols    = @matrix[0].elems;                                       # [6]
my $matches = 0;                                                      # [7]

for 0 .. $rows -2 -> $row                                             # [8]
{
  for 0 .. $cols -2 -> $col                                           # [9]
  {
     for 1 .. $rows-1 -> $offset                                      # [10]
     {
       last unless defined @matrix[$row+$offset][$col+$offset];       # [11]
       check-box($row, $col, $offset);                                # [12]
     } 
  }
}

say $matches;                                                         # [13]

sub check-box ($row, $col, $offset)                                   # [12a]
{
  say ": [$row, $col] -> [{ $row+$offset }, { $col+ $offset}]" if $verbose;
      
  if @matrix[$row][$col]         == @matrix[$row+$offset][$col] ==
     @matrix[$row][$col+$offset] == @matrix[$row+$offset][$col+$offset] == 1
  {                                                                   # [14]
    $matches++;                                                       # [14a]
    say ": - is a match" if $verbose;
  }
}

[1] Ensure that the argument is a file name.

[2] Read the matrix into a two dimentional array.

[3] Ensure that all the rows have the same length.

[4] Ensure that the matrix contains nothing but zeroes and ones. We have to slap on >>.List here, as flat will not flatten the structure for us otherwise. See docs.raku.org/routine/flat#class_Any for details.

[5] The number of rows.

[6] The number of columns. We know that the roes have the same lengt (from [3]), so we can pick any row here.

[7] The number of matches, squares with one ibn the four corners.

[8] and [9] The idea is to iterate over all the possible starting positions for a square. We do this in [8] (one row) and [9] (starting positions on that row), starting with the first position (from the left) on the first (top) row. We stop one off the end of the lines, as we need at least two elements for a square.

[10] Then we iterate over the size of the square in [10], or rather the index offsets. Thus the offset 1 gives a 2x2 square. The upper limit is probably way off, but the check on the next line ([11]) takes care of that.

[11] Skip the innermost loop if the current square is outside of the matrix.

[12] Check if the current square has four corners with 1.

[13] Print the number of matching squares.

[14] The actual four courner check.

Running it:

$ ./find-square example1.txt 
1

$ ./find-square example2.txt 
4

$ ./find-square example3.txt 
0

The answers are correct, but we can use verbose mode to see why:

$ ./find-square -v example1.txt 
: Upper Left: [0, 0] -> Lower Right: [1, 1]
: Upper Left: [0, 0] -> Lower Right: [2, 2]
: Upper Left: [0, 0] -> Lower Right: [3, 3]
: Upper Left: [0, 1] -> Lower Right: [1, 2]
: Upper Left: [0, 1] -> Lower Right: [2, 3]
: - is a match
: Upper Left: [0, 2] -> Lower Right: [1, 3]
: Upper Left: [1, 0] -> Lower Right: [2, 1]
: Upper Left: [1, 0] -> Lower Right: [3, 2]
: Upper Left: [1, 1] -> Lower Right: [2, 2]
: Upper Left: [1, 1] -> Lower Right: [3, 3]
: Upper Left: [1, 2] -> Lower Right: [2, 3]
: Upper Left: [2, 0] -> Lower Right: [3, 1]
: Upper Left: [2, 1] -> Lower Right: [3, 2]
: Upper Left: [2, 2] -> Lower Right: [3, 3]
1

$ ./find-square -v example2.txt 
: Upper Left: [0, 0] -> Lower Right: [1, 1]
: - is a match
: Upper Left: [0, 0] -> Lower Right: [2, 2]
: Upper Left: [0, 0] -> Lower Right: [3, 3]
: - is a match
: Upper Left: [0, 1] -> Lower Right: [1, 2]
: Upper Left: [0, 1] -> Lower Right: [2, 3]
: - is a match
: Upper Left: [0, 2] -> Lower Right: [1, 3]
: Upper Left: [1, 0] -> Lower Right: [2, 1]
: Upper Left: [1, 0] -> Lower Right: [3, 2]
: Upper Left: [1, 1] -> Lower Right: [2, 2]
: Upper Left: [1, 1] -> Lower Right: [3, 3]
: Upper Left: [1, 2] -> Lower Right: [2, 3]
: Upper Left: [2, 0] -> Lower Right: [3, 1]
: Upper Left: [2, 1] -> Lower Right: [3, 2]
: Upper Left: [2, 2] -> Lower Right: [3, 3]
: - is a match
4

$ ./find-square -v example3.txt 
: Upper Left: [0, 0] -> Lower Right: [1, 1]
: Upper Left: [0, 0] -> Lower Right: [2, 2]
: Upper Left: [0, 0] -> Lower Right: [3, 3]
: Upper Left: [0, 1] -> Lower Right: [1, 2]
: Upper Left: [0, 1] -> Lower Right: [2, 3]
: Upper Left: [0, 2] -> Lower Right: [1, 3]
: Upper Left: [1, 0] -> Lower Right: [2, 1]
: Upper Left: [1, 0] -> Lower Right: [3, 2]
: Upper Left: [1, 1] -> Lower Right: [2, 2]
: Upper Left: [1, 1] -> Lower Right: [3, 3]
: Upper Left: [1, 2] -> Lower Right: [2, 3]
: Upper Left: [2, 0] -> Lower Right: [3, 1]
: Upper Left: [2, 1] -> Lower Right: [3, 2]
: Upper Left: [2, 2] -> Lower Right: [3, 3]
0

Note that the coordinates are zero based.

Not Verbose Enough?

The output we got from verbose mode is certainly better than nothing, but it is not very user friendly. We can print the whole matrix, with the currently inspected square highlighted.

Adding colour to the output is a repetition of what I did in e.g. the second part of The Email Queen with Raku, my response to the Perl Weekly Challenge #062.

I'll start with the output, and show the program afterwards (without comments). Successful squares are shown with green, unsuccesful ones in red:

$ ./find-square-verbose -vv example1.txt 
: Upper Left: [0, 0] -> Lower Right: [1, 1]
: [ 0 1 1 1 ]
: [ 0 0 1 0 ]
: [ 0 1 0 0 ]
: [ 1 0 1 1 ]

: Upper Left: [0, 0] -> Lower Right: [2, 2]
: [ 0 1 0 1 ]
: [ 0 0 1 0 ]
: [ 1 1 0 0 ]
: [ 1 0 1 1 ]

: Upper Left: [0, 0] -> Lower Right: [3, 3]
: [ 0 1 0 1 ]
: [ 0 0 1 0 ]
: [ 1 1 0 1 ]
: [ 1 0 0 1 ]

: Upper Left: [0, 1] -> Lower Right: [1, 2]
: [ 0 1 0 1 ]
: [ 1 0 1 0 ]
: [ 0 1 0 0 ]
: [ 1 0 1 1 ]

: Upper Left: [0, 1] -> Lower Right: [2, 3]
: - is a match
: [ 0 1 0 1 ]
: [ 1 0 1 0 ]
: [ 0 1 0 1 ]
: [ 1 0 1 1 ]

: Upper Left: [0, 2] -> Lower Right: [1, 3]
: [ 0 0 0 1 ]
: [ 1 0 1 0 ]
: [ 0 1 0 0 ]
: [ 1 0 1 1 ]

: Upper Left: [1, 0] -> Lower Right: [2, 1]
: [ 0 0 1 1 ]
: [ 0 0 1 0 ]
: [ 1 1 0 0 ]
: [ 1 0 1 1 ]

: Upper Left: [1, 0] -> Lower Right: [3, 2]
: [ 0 0 1 1 ]
: [ 0 0 1 0 ]
: [ 1 1 0 0 ]
: [ 1 0 0 1 ]

: Upper Left: [1, 1] -> Lower Right: [2, 2]
: [ 0 0 1 1 ]
: [ 1 0 1 0 ]
: [ 0 1 0 0 ]
: [ 1 0 1 1 ]

: Upper Left: [1, 1] -> Lower Right: [3, 3]
: [ 0 0 1 1 ]
: [ 1 0 1 0 ]
: [ 0 1 0 1 ]
: [ 1 0 0 1 ]

: Upper Left: [1, 2] -> Lower Right: [2, 3]
: [ 0 0 1 1 ]
: [ 1 0 1 0 ]
: [ 0 1 0 1 ]
: [ 1 0 1 1 ]

: Upper Left: [2, 0] -> Lower Right: [3, 1]
: [ 0 0 1 1 ]
: [ 1 0 1 0 ]
: [ 1 1 0 0 ]
: [ 1 0 1 1 ]

: Upper Left: [2, 1] -> Lower Right: [3, 2]
: [ 0 0 1 1 ]
: [ 1 0 1 0 ]
: [ 0 1 0 0 ]
: [ 1 0 0 1 ]

: Upper Left: [2, 2] -> Lower Right: [3, 3]
: [ 0 0 1 1 ]
: [ 1 0 1 0 ]
: [ 0 1 0 1 ]
: [ 1 0 0 1 ]

1
$ ./find-square-verbose -vv example2.txt 
: Upper Left: [0, 0] -> Lower Right: [1, 1]
: - is a match
: [ 1 1 0 1 ]
: [ 1 1 1 0 ]
: [ 0 0 1 1 ]
: [ 1 0 1 1 ]

: Upper Left: [0, 0] -> Lower Right: [2, 2]
: [ 1 1 0 1 ]
: [ 1 1 0 0 ]
: [ 0 1 1 1 ]
: [ 1 0 1 1 ]

: Upper Left: [0, 0] -> Lower Right: [3, 3]
: - is a match
: [ 1 1 0 1 ]
: [ 1 1 0 0 ]
: [ 0 1 1 1 ]
: [ 1 0 1 1 ]

: Upper Left: [0, 1] -> Lower Right: [1, 2]
: [ 1 1 0 1 ]
: [ 1 1 0 0 ]
: [ 0 0 1 1 ]
: [ 1 0 1 1 ]

: Upper Left: [0, 1] -> Lower Right: [2, 3]
: - is a match
: [ 1 1 0 1 ]
: [ 1 1 0 0 ]
: [ 0 1 1 1 ]
: [ 1 0 1 1 ]

: Upper Left: [0, 2] -> Lower Right: [1, 3]
: [ 1 1 0 1 ]
: [ 1 1 0 0 ]
: [ 0 0 1 1 ]
: [ 1 0 1 1 ]

: Upper Left: [1, 0] -> Lower Right: [2, 1]
: [ 1 1 0 1 ]
: [ 1 1 1 0 ]
: [ 0 1 1 1 ]
: [ 1 0 1 1 ]

: Upper Left: [1, 0] -> Lower Right: [3, 2]
: [ 1 1 0 1 ]
: [ 1 1 0 0 ]
: [ 0 1 1 1 ]
: [ 1 0 1 1 ]

: Upper Left: [1, 1] -> Lower Right: [2, 2]
: [ 1 1 0 1 ]
: [ 1 1 0 0 ]
: [ 0 1 1 1 ]
: [ 1 0 1 1 ]

: Upper Left: [1, 1] -> Lower Right: [3, 3]
: [ 1 1 0 1 ]
: [ 1 1 0 0 ]
: [ 0 1 1 1 ]
: [ 1 0 1 1 ]

: Upper Left: [1, 2] -> Lower Right: [2, 3]
: [ 1 1 0 1 ]
: [ 1 1 0 0 ]
: [ 0 0 1 1 ]
: [ 1 0 1 1 ]

: Upper Left: [2, 0] -> Lower Right: [3, 1]
: [ 1 1 0 1 ]
: [ 1 1 1 0 ]
: [ 0 1 1 1 ]
: [ 1 0 1 1 ]

: Upper Left: [2, 1] -> Lower Right: [3, 2]
: [ 1 1 0 1 ]
: [ 1 1 1 0 ]
: [ 0 1 1 1 ]
: [ 1 0 1 1 ]

: Upper Left: [2, 2] -> Lower Right: [3, 3]
: - is a match
: [ 1 1 0 1 ]
: [ 1 1 1 0 ]
: [ 0 0 1 1 ]
: [ 1 0 1 1 ]

4
$ ./find-square-verbose -vv example3.txt 
: Upper Left: [0, 0] -> Lower Right: [1, 1]
: [ 0 1 0 1 ]
: [ 1 0 1 0 ]
: [ 0 1 0 0 ]
: [ 1 0 0 1 ]

: Upper Left: [0, 0] -> Lower Right: [2, 2]
: [ 0 1 0 1 ]
: [ 1 0 1 0 ]
: [ 0 1 0 0 ]
: [ 1 0 0 1 ]

: Upper Left: [0, 0] -> Lower Right: [3, 3]
: [ 0 1 0 1 ]
: [ 1 0 1 0 ]
: [ 0 1 0 0 ]
: [ 1 0 0 1 ]

: Upper Left: [0, 1] -> Lower Right: [1, 2]
: [ 0 1 0 1 ]
: [ 1 0 1 0 ]
: [ 0 1 0 0 ]
: [ 1 0 0 1 ]

: Upper Left: [0, 1] -> Lower Right: [2, 3]
: [ 0 1 0 1 ]
: [ 1 0 1 0 ]
: [ 0 1 0 0 ]
: [ 1 0 0 1 ]

: Upper Left: [0, 2] -> Lower Right: [1, 3]
: [ 0 1 0 1 ]
: [ 1 0 1 0 ]
: [ 0 1 0 0 ]
: [ 1 0 0 1 ]

: Upper Left: [1, 0] -> Lower Right: [2, 1]
: [ 0 1 0 1 ]
: [ 1 0 1 0 ]
: [ 0 1 0 0 ]
: [ 1 0 0 1 ]

: Upper Left: [1, 0] -> Lower Right: [3, 2]
: [ 0 1 0 1 ]
: [ 1 0 1 0 ]
: [ 0 1 0 0 ]
: [ 1 0 0 1 ]

: Upper Left: [1, 1] -> Lower Right: [2, 2]
: [ 0 1 0 1 ]
: [ 1 0 1 0 ]
: [ 0 1 0 0 ]
: [ 1 0 0 1 ]

: Upper Left: [1, 1] -> Lower Right: [3, 3]
: [ 0 1 0 1 ]
: [ 1 0 1 0 ]
: [ 0 1 0 0 ]
: [ 1 0 0 1 ]

: Upper Left: [1, 2] -> Lower Right: [2, 3]
: [ 0 1 0 1 ]
: [ 1 0 1 0 ]
: [ 0 1 0 0 ]
: [ 1 0 0 1 ]

: Upper Left: [2, 0] -> Lower Right: [3, 1]
: [ 0 1 0 1 ]
: [ 1 0 1 0 ]
: [ 0 1 0 0 ]
: [ 1 0 0 1 ]

: Upper Left: [2, 1] -> Lower Right: [3, 2]
: [ 0 1 0 1 ]
: [ 1 0 1 0 ]
: [ 0 1 0 0 ]
: [ 1 0 0 1 ]

: Upper Left: [2, 2] -> Lower Right: [3, 3]
: [ 0 1 0 1 ]
: [ 1 0 1 0 ]
: [ 0 1 0 0 ]
: [ 1 0 0 1 ]

0

Note that I used the program's html mode (with «-h») to get the output on a format suitable for an html page.

File: find-square-verbose
#! /usr/bin/env raku

unit sub MAIN ($matrix where $matrix.IO.f && $matrix.IO.r = 'example1.txt',
               :h(:$html),
	       :vv(:$very-verbose),
	       :v(:$verbose) = $very-verbose);

my $blue  = "\e[44m";
my $green = "\e[42m";
my $red   = "\e[101m";
my $stop  = "\e[0m";

if $html
{
  $blue  = '<span class="text-light bg-primary">';
  $green = '<span class="text-light bg-success">';
  $red   = '<span class="text-light bg-danger">';
  $stop  = '</span>';
}

my @matrix = $matrix.IO.lines.map( *.lc.words.list );

die "Uneven grid row length" unless [==] @(@matrix)>>.elems;
die "0 and 1 only" unless all( @matrix>>.List.flat ) eq any( 0, 1 );

my $rows    = @matrix.elems;
my $cols    = @matrix[0].elems;
my $matches = 0;

for 0 .. $rows -2 -> $row
{
  for 0 .. $cols -2 -> $col
  {
     for 1 .. $rows-1 -> $offset
     {
       last unless defined @matrix[$row+$offset][$col+$offset];
       check-box($row, $col, $offset);
     } 
  }
}

say $matches;

sub check-box ($row, $col, $offset)
{
  say ": Upper Left: [$row, $col] -> Lower Right: [{ $row+$offset }, \
    { $col+ $offset}]" if $verbose;

  if @matrix[$row][$col]         == @matrix[$row+$offset][$col] ==
     @matrix[$row][$col+$offset] == @matrix[$row+$offset][$col+$offset] == 1
  {
    $matches++;
    say ": - is a match" if $verbose;
    highlight-matrix($row, $col, $offset, True) if $very-verbose;
  }
  else
  {
    highlight-matrix($row, $col, $offset, False) if $very-verbose;
  }
}

sub highlight-matrix ($row, $col, $offset, $hot)
{
  my $inside  = False;
  my $row-end = $row + $offset; 
  my $col-end = $col + $offset;

  my $start = $hot ?? $green !! $red;

  for ^$rows -> $r 
  {
    print ": [";  
    for ^$cols -> $c 
    {
      $inside = $row <= $r <= $row-end && $col <= $c <= $col-end; 

      print " "; 
      
      $inside    
        ?? print $start ~ @matrix[$r][$c] ~ $stop
	!! print @matrix[$c][$r];
    }
    $inside = False;     
    say " ]"; 
  }
  say "";    
}

And that's it.