## Advent Calendar - December 5, 2021

Sunday, Dec 5, 2021| Tags: Perl ### | Day 4 | Day 5 | Day 6 |

The gift is presented by Simon Green. Today he is talking about his solution to “The Weekly Challenge - 101”. This is re-produced for Advent Calendar 2021 from the original post by `Simon Green`.

## Task #1: Pack a Spiral

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.

## My solution

This challenge is kinda of the reverse of task 2 from challenge 88. In that task we were given a spiral and had to unwind it.

This task can be broken up into three steps:

### 1. Work out the tightest grid.

``````* Work with \$y from 1 to one less than the array size.
* If the array size can be evenly divided by \$y and it is tighter than the current grid size, update \$max_x and \$max_y.
``````

### 2. Populate the grid

``````* Create a @direction array with the x/y offsets for right, up, left and down.
* Create a loop from 0 to \$#values.
* Put that value in the given location.
* If the next x/y value is either out of bounds or that position already has a value, change directions.
``````

### 3. Display the grid

``````* Since 0,0 represents the bottom left, we use the reverse function on the @grid array.
* I use sprintf to right justify numbers and left justify strings. This ensure that even if the lengths of the values are different, they will be correctly aligned.
``````

## Code

``````#!/usr/bin/env perl

use strict;
use warnings;
use feature 'say';
use List::Util 'max';

sub _get_tightest_grid {
my \$count = shift;
my \$max_x = \$count;
my \$max_y = 1;

for my \$y ( 2 .. \$count - 1 ) {
my \$x = \$count / \$y;
if ( \$count % \$y == 0 and abs( \$x - \$y ) < abs( \$max_x - \$max_y ) ) {
# We have found a tighter solution
( \$max_x, \$max_y ) = ( \$x, \$y );
}
}

return ( \$max_x, \$max_y );

}

sub main {
my @values = @_;
die "You must specify at least one value\n" unless scalar(@values);

# Work out the tightest grid. This is when the x + y values are the least
my ( \$max_x, \$max_y ) = _get_tightest_grid( scalar(@values) );

# The directions we can move (right, up, left, down)
my @directions = ( [ 1, 0 ], [ 0, 1 ], [ -1, 0 ], [ 0, -1 ] );

# Populate the grid
my @grid = ();
my \$x    = my \$y = my \$direction = 0;    # Bottom left, going rightward
foreach my \$i ( 0 .. \$#values ) {
\$grid[\$y][\$x] = \$values[\$i];

# Determine the next position
my \$next_x = \$x + \$directions[\$direction];
my \$next_y = \$y + \$directions[\$direction];

# We need to switch directions if we are out of bounds or the cell already has a value
if (   \$next_x >= \$max_x
or \$next_y >= \$max_y
or \$next_x == -1
or \$next_y == -1
or defined( \$grid[\$next_y][\$next_x] ) )
{
\$direction = ++\$direction % 4;
}

# Move to the next cell
\$x += \$directions[\$direction];
\$y += \$directions[\$direction];
}

# Display the output. Right aligned for all numbers, else left aligned
my \$max_length   = max( map { length(\$_) } @values );
my \$all_integers = grep { /^\d+\$/ } @values;
my \$format       = \$all_integers ? "\%\${max_length}d" : "\%-\${max_length}s";
foreach my \$row ( reverse @grid ) {
say join ' ', map { sprintf \$format, \$_ } @\$row;
}
}

main(@ARGV);
``````

## Examples

``````\$ ./ch-1.pl 1 2 3 4 5 6
6 5 4
1 2 3

\$ ./ch-1.pl 1 2 3 4 5 6 7 8 9 10 11 12
9  8  7  6
10 11 12  5
1  2  3  4

\$ ./ch-1.pl the quick brown fox jumps over the lazy dog
the   over  jumps
lazy  dog   fox
the   quick brown
``````

If you have any suggestion then please do share with us perlweeklychallenge@yahoo.com.