#!perl
###############################################################################
=comment
Perl Weekly Challenge 101
=========================
Task #1
-------
*Pack a Spiral*
Submitted by: Stuart Little
You are given an array @A of items (integers say, but they can be anything).
Your task is to pack that array into an MxN matrix spirally counterclockwise,
as tightly as possible.
'Tightly' means the absolute value |M-N| of the difference has to be as
small as possible.
Example 1:
Input: @A = (1,2,3,4)
Output:
4 3
1 2
Since the given array is already a 1x4 matrix on its own, but that's not as
tight as possible. Instead, you'd spiral it counterclockwise into
4 3
1 2
Example 2:
Input: @A = (1..6)
Output:
6 5 4
1 2 3
or
5 4
6 3
1 2
Either will do as an answer, because they're equally tight.
Example 3:
Input: @A = (1..12)
Output:
9 8 7 6
10 11 12 5
1 2 3 4
or
8 7 6
9 12 5
10 11 4
1 2 3
=cut
###############################################################################
#--------------------------------------#
# Copyright © 2021 PerlMonk Athanasius #
#--------------------------------------#
use strict;
use warnings;
use Const::Fast;
use enum qw( RIGHT UP LEFT DOWN );
use Regexp::Common qw( number );
const my $USAGE =>
"Usage:
perl $0 [ ...]
[ ...] A non-empty array of integers and/or integer ranges: I..J\n";
#------------------------------------------------------------------------------
BEGIN
#------------------------------------------------------------------------------
{
$| = 1;
print "\nChallenge 101, Task #1: Pack a Spiral (Perl)\n\n";
}
#==============================================================================
MAIN:
#==============================================================================
{
# (1) Populate and validate the array
my @A = get_array();
printf "Input: \@A = (%s)\n", join ',', @A;
# (2) Find M <= N such that M * N == @A.elems and |M - N| is a minimum
my ($M, $N) = find_dimensions( \@A );
# (3) Create and populate the M x N matrix
my $matrix = pack_matrix( \@A, $M, $N );
# (4) Print the matrix
print "\nOutput:\n\n";
print_matrix( $matrix );
}
#------------------------------------------------------------------------------
sub get_array
#------------------------------------------------------------------------------
{
scalar @ARGV > 0 or error( 'Empty array' );
my @A;
for my $item (@ARGV)
{
if (my ($lhs, $rhs) = $item =~ / ^ (.+) \.\. (.+) $ /x)
{
for ($lhs, $rhs)
{
/ ^ $RE{num}{int} $ /x
or error( qq[Item "$_" is not an integer] );
}
push @A, $lhs .. $rhs;
}
else
{
$item =~ / ^ $RE{num}{int} $ /x
or error( qq[Item "$item" is not an integer] );
push @A, $item;
}
}
return @A;
}
#------------------------------------------------------------------------------
sub find_dimensions
#------------------------------------------------------------------------------
{
my ($A) = @_;
my ($M, $N) = (1, scalar @$A);
my $root = int sqrt $N;
if ($root * $root == $N)
{
($M, $N) = ($root, $root);
}
else
{
for my $div1 (reverse 2 .. $root)
{
my $div2 = int($N / $div1);
if ($div1 * $div2 == $N)
{
($M, $N) = ($div1, $div2);
last;
}
}
}
return $M, $N;
}
#------------------------------------------------------------------------------
sub pack_matrix
#------------------------------------------------------------------------------
{
my ($A, $M, $N) = @_;
my $max_row = $M - 1;
my $min_row = 0;
my $max_col = $N - 1;
my $min_col = 0;
my $row = $max_row;
my $col = -1;
my $dir = RIGHT;
my @matrix;
for my $item (@$A)
{
if ($dir == RIGHT)
{
if (++$col > $max_col)
{
$col = $max_col;
$dir = UP;
--$row;
--$max_row;
}
}
elsif ($dir == UP)
{
if (--$row < $min_row)
{
$row = $min_row;
$dir = LEFT;
--$col;
--$max_col;
}
}
elsif ($dir == LEFT)
{
if (--$col < $min_col)
{
$col = $min_col;
$dir = DOWN;
++$row;
++$min_row;
}
}
else # DOWN
{
if (++$row > $max_row)
{
$row = $max_row;
$dir = RIGHT;
++$col;
++$min_col;
}
}
$matrix[ $row ][ $col ] = $item;
}
return \@matrix;
}
#------------------------------------------------------------------------------
sub print_matrix
#------------------------------------------------------------------------------
{
my ($matrix) = @_;
my $max_row = $#$matrix;
my $max_col = $#{ $matrix->[ 0 ] };
my @widths;
# (1) Calculate maximum column widths
for my $col (0 .. $max_col)
{
my $max = length $matrix->[ 0 ][ $col ];
for my $row (1 .. $max_row)
{
my $len = length $matrix->[ $row ][ $col ];
$max = $len if $len > $max;
}
push @widths, $max;
}
# (2) Print the matrix
for my $row (0 .. $max_row)
{
print ' ' x 5;
for my $col (0 .. $max_col)
{
printf " %*d", $widths[ $col ], $matrix->[ $row ][ $col ];
}
print "\n";
}
}
#------------------------------------------------------------------------------
sub error
#------------------------------------------------------------------------------
{
my ($message) = @_;
die "ERROR: $message\n$USAGE";
}
###############################################################################