Peter’s blog ✴ Week 101 ✴ 22 February 2021

THE WEEKLY CHALLENGE
Spiral triangle

The Perl Camel

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.

Examples


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. 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

Analysis

An interesting challenge, and my attempt at a reasonably compact solution.

First let's get the closest width and height. I start with the integer part of the square root, $j, of $count, the number of items to be displayed.

I then decrement $j until $count / $j is integral and set these two values as the width and height of the output matrix, being the closest pair of numbers whose product is $count. I made the larger one the height to maximise the size of matrix I could easily display in this blog, but swapping the width and height is equally valid.

Now I populate the matrix. The array pointed to by $steps contains the four possible $x and $y differences possible from one point to the next. The first point is required to be at (0, $height - 1), using the convention that $y increases downwards and $x increases to the right. The variable $s points to the pair of changes in @sets to be applied to ($x, $y) to get to the next point.

However, before using that next point, I check that it is not outside the bounds of the matrix and not already occupied. If that test fails, I increment $s (modulo 4) and thus get a new pair of increments from $steps - or in other words, the snail that is planting the numbers in the matrix turns left.

This algorithm will always find the next position to be filled, and since it is contained within this loop:

for $j (0 .. $width * $height - 1)

it will run out of blank positions exactly when the matrix is complete.

This is quite a complicated explanation of some quite dense coding, but I believe it is an efficient and scalable solution.

Try it 

Try running the script with any input:



example: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12

Script


#!/usr/bin/perl

# Blog: http://ccgi.campbellsmiths.force9.co.uk/challenge

use v5.26;    # The Weekly Challenge - 2021-02-22
use utf8;     # Week 101 - task 1 - Pack a spiral
use warnings; # Peter Campbell Smith
binmode STDOUT, ':utf8';
use Encode;

pack_a_spiral(1 .. 100);
pack_a_spiral(15 .. 21);
pack_a_spiral(951 .. 1049);
pack_a_spiral('aa' .. 'xw');
my @x;
push @x, 2 ** $_ for 0 .. 31;
pack_a_spiral(@x);

sub pack_a_spiral {
    
    my (@z, $w, $h, $count, $width, $height, $diff, $x, $y, $j, 
        @factors, $steps, @matrix, @steps, $s, @try, $cw);
    
    # initialise
    @z = @_;
    $count = @z;
    
    # get closest factors of $count
    for ($j = int(sqrt($count));; $j --) {
        if ($count / $j == int($count / $j)) {
            ($width, $height) = ($j, int($count / $j));
            last;
        }
    }
    
    # initialise values for spiral
    $steps = [[1, 0], [0, -1], [-1, 0], [0, 1]];
    $s = 0;
    ($x, $y) = (0, $height - 1);
    $cw = 0;
    
    # create spiral
    for $j (0 .. $width * $height - 1) {
        $matrix[$x][$y] = $z[$j];
        $cw = length($z[$j]) if length($z[$j]) > $cw;
        @try = ($x + $steps->[$s]->[0], $y + $steps->[$s]->[1]);
        
        # have we reached a corner?
        if ($try[0] < 0 or $try[0] >= $width
            or $try[1] < 0 or $try[1] >= $height
            or defined $matrix[$try[0]][$try[1]]) {
            $s = ($s + 1) % 4;
        }
        ($x, $y) = ($x + $steps->[$s]->[0], $y + $steps->[$s]->[1]);
    }
    $cw ++;

    # display
    say qq[\nInput:  $count items - $z[0] to $z[-1]];
    say qq[Output: dimensions $width × $height];
    for $y (0 .. $height - 1) {
        for $x (0 .. $width - 1) {
            printf (qq[%${cw}s], $matrix[$x][$y]);
        }
        say '';
    }
}

29 lines of code
Completed after the closing date and not submitted to GitHub

Output from script



Input:  100 items - 1 to 100
Output: dimensions 10 × 10
  28  27  26  25  24  23  22  21  20  19
  29  58  57  56  55  54  53  52  51  18
  30  59  80  79  78  77  76  75  50  17
  31  60  81  94  93  92  91  74  49  16
  32  61  82  95 100  99  90  73  48  15
  33  62  83  96  97  98  89  72  47  14
  34  63  84  85  86  87  88  71  46  13
  35  64  65  66  67  68  69  70  45  12
  36  37  38  39  40  41  42  43  44  11
   1   2   3   4   5   6   7   8   9  10

Input:  7 items - 15 to 21
Output: dimensions 1 × 7
 21
 20
 19
 18
 17
 16
 15

Input:  99 items - 951 to 1049
Output: dimensions 9 × 11
  977  976  975  974  973  972  971  970  969
  978 1007 1006 1005 1004 1003 1002 1001  968
  979 1008 1029 1028 1027 1026 1025 1000  967
  980 1009 1030 1043 1042 1041 1024  999  966
  981 1010 1031 1044 1049 1040 1023  998  965
  982 1011 1032 1045 1048 1039 1022  997  964
  983 1012 1033 1046 1047 1038 1021  996  963
  984 1013 1034 1035 1036 1037 1020  995  962
  985 1014 1015 1016 1017 1018 1019  994  961
  986  987  988  989  990  991  992  993  960
  951  952  953  954  955  956  957  958  959

Input:  621 items - aa to xw
Output: dimensions 23 × 27
 cs cr cq cp co cn cm cl ck cj ci ch cg cf ce cd cc cb ca bz by bx bw
 ct ge gd gc gb ga fz fy fx fw fv fu ft fs fr fq fp fo fn fm fl fk bv
 cu gf ji jh jg jf je jd jc jb ja iz iy ix iw iv iu it is ir iq fj bu
 cv gg jj me md mc mb ma lz ly lx lw lv lu lt ls lr lq lp lo ip fi bt
 cw gh jk mf os or oq op oo on om ol ok oj oi oh og of oe ln io fh bs
 cx gi jl mg ot qy qx qw qv qu qt qs qr qq qp qo qn qm od lm in fg br
 cy gj jm mh ou qz sw sv su st ss sr sq sp so sn sm ql oc ll im ff bq
 cz gk jn mi ov ra sx um ul uk uj ui uh ug uf ue sl qk ob lk il fe bp
 da gl jo mj ow rb sy un vu vt vs vr vq vp vo ud sk qj oa lj ik fd bo
 db gm jp mk ox rc sz uo vv wu wt ws wr wq vn uc sj qi nz li ij fc bn
 dc gn jq ml oy rd ta up vw wv xm xl xk wp vm ub si qh ny lh ii fb bm
 dd go jr mm oz re tb uq vx ww xn xw xj wo vl ua sh qg nx lg ih fa bl
 de gp js mn pa rf tc ur vy wx xo xv xi wn vk tz sg qf nw lf ig ez bk
 df gq jt mo pb rg td us vz wy xp xu xh wm vj ty sf qe nv le if ey bj
 dg gr ju mp pc rh te ut wa wz xq xt xg wl vi tx se qd nu ld ie ex bi
 dh gs jv mq pd ri tf uu wb xa xr xs xf wk vh tw sd qc nt lc id ew bh
 di gt jw mr pe rj tg uv wc xb xc xd xe wj vg tv sc qb ns lb ic ev bg
 dj gu jx ms pf rk th uw wd we wf wg wh wi vf tu sb qa nr la ib eu bf
 dk gv jy mt pg rl ti ux uy uz va vb vc vd ve tt sa pz nq kz ia et be
 dl gw jz mu ph rm tj tk tl tm tn to tp tq tr ts rz py np ky hz es bd
 dm gx ka mv pi rn ro rp rq rr rs rt ru rv rw rx ry px no kx hy er bc
 dn gy kb mw pj pk pl pm pn po pp pq pr ps pt pu pv pw nn kw hx eq bb
 do gz kc mx my mz na nb nc nd ne nf ng nh ni nj nk nl nm kv hw ep ba
 dp ha kd ke kf kg kh ki kj kk kl km kn ko kp kq kr ks kt ku hv eo az
 dq hb hc hd he hf hg hh hi hj hk hl hm hn ho hp hq hr hs ht hu en ay
 dr ds dt du dv dw dx dy dz ea eb ec ed ee ef eg eh ei ej ek el em ax
 aa ab ac ad ae af ag ah ai aj ak al am an ao ap aq ar as at au av aw

Input:  32 items - 1 to 2147483648
Output: dimensions 4 × 8
       8192       4096       2048       1024
      16384  134217728   67108864        512
      32768  268435456   33554432        256
      65536  536870912   16777216        128
     131072 1073741824    8388608         64
     262144 2147483648    4194304         32
     524288    1048576    2097152         16
          1          2          4          8

 

Any content of this website which has been created by Peter Campbell Smith is in the public domain