Array programming techniques#

There are a few things one can do to make APL look more… APL. What really characterises “classic” code is control structures and especially loops. Modern APL has control structures, too, and loops can easily be done with ¨. So those are really the features you want to avoid.

Try to think of differentiation between cases in terms of any of:

  • Boolean masks

  • Mathematical relationships

  • Commonality between cases

FizzBuzz#

Maybe FizzBuzz would be a good example. The classic approach (other than “I don’t think that’s possible”!) is a loop. Possibly two loops, an outer one for N and an inner one for the 3, 5 list. Instead, let’s try processing the entire list ⍳35 at once, using any one or more of the above.

To start off, we can find which numbers are divisible by 3 or 5 with an outer product:

]rows on ⍝ don't wrap output cells
Was OFF
mask0=3 5∘.|⍳35
0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0
0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1

which gives us a nice mask for when we need Fizz and when we need Buzz, but when do we need the number itself? Let’s create an additional row in the mask array that holds 1 if neither of the Fizz or Buzz mask holds a 1:

(⍪⊢)mask
1 1 0 1 0 0 1 1 0 0 1 0 1 1 0 1 1 0 1 0 0 1 1 0 0 1 0 1 1 0 1 1 0 1 0
0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0
0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1

So far, everything has been pretty clean. Things will start to get dirty now because FizzBuzz essentially is a mixed-type problem, but we can still try to stick with Array operations until the very end.

We can zero out unwanted numbers by multiplying the mask with the numbers,

(35)×@1(⍪⊢)mask
1 2 0 4 0 0 7 8 0 0 11 0 13 14 0 16 17 0 19 0 0 22 23 0 0 26 0 28 29 0 31 32 0 34 0
0 0 1 0 0 1 0 0 1 0  0 1  0  0 1  0  0 1  0 0 1  0  0 1 0  0 1  0  0 1  0  0 1  0 0
0 0 0 0 1 0 0 0 0 1  0 0  0  0 1  0  0 0  0 1 0  0  0 0 1  0 0  0  0 1  0  0 0  0 1

If we split that up a bit, we end up with

nums35
mat(⍪⊢)0=3 5∘.|nums
mat×@1nums

The next step is to replace all 1s in row 2 with ‘Fizz’, and the 1s in row 3 with ‘Buzz’:

mat('Fizz')@@2mat
mat('Buzz')@@3mat
mat
┌─┬─┬────┬─┬────┬────┬─┬─┬────┬────┬──┬────┬──┬──┬────┬──┬──┬────┬──┬────┬────┬──┬──┬────┬────┬──┬────┬──┬──┬────┬──┬──┬────┬──┬────┐
│1│2│0   │4│0   │0   │7│8│0   │0   │11│0   │13│14│0   │16│17│0   │19│0   │0   │22│23│0   │0   │26│0   │28│29│0   │31│32│0   │34│0   │
├─┼─┼────┼─┼────┼────┼─┼─┼────┼────┼──┼────┼──┼──┼────┼──┼──┼────┼──┼────┼────┼──┼──┼────┼────┼──┼────┼──┼──┼────┼──┼──┼────┼──┼────┤
│0│0│Fizz│0│0   │Fizz│0│0│Fizz│0   │0 │Fizz│0 │0 │Fizz│0 │0 │Fizz│0 │0   │Fizz│0 │0 │Fizz│0   │0 │Fizz│0 │0 │Fizz│0 │0 │Fizz│0 │0   │
├─┼─┼────┼─┼────┼────┼─┼─┼────┼────┼──┼────┼──┼──┼────┼──┼──┼────┼──┼────┼────┼──┼──┼────┼────┼──┼────┼──┼──┼────┼──┼──┼────┼──┼────┤
│0│0│0   │0│Buzz│0   │0│0│0   │Buzz│0 │0   │0 │0 │Buzz│0 │0 │0   │0 │Buzz│0   │0 │0 │0   │Buzz│0 │0   │0 │0 │Buzz│0 │0 │0   │0 │Buzz│
└─┴─┴────┴─┴────┴────┴─┴─┴────┴────┴──┴────┴──┴──┴────┴──┴──┴────┴──┴────┴────┴──┴──┴────┴────┴──┴────┴──┴──┴────┴──┴──┴────┴──┴────┘

Now we have to combine everything by joining vertically and then removing the 0s:

]dinput
FizzBuzz{
    nums
    mat(⍪⊢)0=3 5∘.|nums
    mat×@1nums
    mat('Fizz')@@2mat
    mat('Buzz')@@3mat
    mat()@(0)mat
    ,mat
}
FizzBuzz 35
┌─┬─┬────┬─┬────┬────┬─┬─┬────┬────┬──┬────┬──┬──┬────────┬──┬──┬────┬──┬────┬────┬──┬──┬────┬────┬──┬────┬──┬──┬────────┬──┬──┬────┬──┬────┐
│1│2│Fizz│4│Buzz│Fizz│7│8│Fizz│Buzz│11│Fizz│13│14│FizzBuzz│16│17│Fizz│19│Buzz│Fizz│22│23│Fizz│Buzz│26│Fizz│28│29│FizzBuzz│31│32│Fizz│34│Buzz│
└─┴─┴────┴─┴────┴────┴─┴─┴────┴────┴──┴────┴──┴──┴────────┴──┴──┴────┴──┴────┴────┴──┴──┴────┴────┴──┴────┴──┴──┴────────┴──┴──┴────┴──┴────┘

This isn’t, perhaps, how you should implement FizzBuzz in an industrial context, and it does do things that impact performance, but it is a pretty good demonstration of applying the array approach to a traditionally loopy problem.

Justify it#

Let’s do another example: take a character matrix and justify it without looping over the lines. This means distributing the trailing spaces into the existing word separations.

For example,

In publishing and graphic design,
Lorem ipsum is a placeholder text
commonly used to demonstrate the visual form
of a document or a typeface
without relying on meaningful content.

becomes

In    publishing    and    graphic   design,
Lorem    ipsum   is   a   placeholder   text
commonly used to demonstrate the visual form
of     a     document    or    a    typeface
without   relying   on  meaningful  content.

This isn’t a particularly difficult problem for a single line, but if we enforce treating the contiguous ravelled data in one go, it becomes a bit more tricky. So, let’s say we have t as the above 5-by-44 matrix. It follows that our result must also be a 5-by-44 matrix.

There are two obvious approaches. One is to move some spaces from the end of the lines to the middle by reordering elements. The other is to determine for each space how many copies if it we need (0 to remove it, 1 to keep it, and more to extend it). Let’s go with the latter method.

t(5 44'In publishing and graphic design,           Lorem ipsum is a placeholder text           commonly used to demonstrate the visual formof a document or a typeface                 without relying on meaningful content.      ')
t
In publishing and graphic design,           
Lorem ipsum is a placeholder text           
commonly used to demonstrate the visual form
of a document or a typeface                 
without relying on meaningful content.      

The first step is identifying spaces. Luckily, scalar extension allows use to do spaces←' '=t:

spaces' '=t
0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1
0 0 0 0 0 1 0 0 0 0 0 1 0 0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1
0 0 0 0 0 0 0 0 1 0 0 0 0 1 0 0 1 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 1 0 0 0 0
0 0 1 0 1 0 0 0 0 0 0 0 0 1 0 0 1 0 1 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 1 1 1 1 1

How might we use that to create a mask (Boolean matrix) for the characters we want to keep?

keep~⌽∧\spaces
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0

Next we need to get the number of trailing spaces on each line,

cols⊃⌽⍴keep
trailcols-+/keep
11 11 0 17 6

Since we need to distribute extra width over inner spaces, we need to know how many inner spaces each line has, so we can divide the trailing width by that.

innertrail-+/spaces
4 5 6 5 4

We now need to distribute the extra spaces over the inner spaces, noting that they may not be evenly distributable. We can just take the floor throughout, and the strategically add 1 here and there, preferably as evenly distributed as possible. We could start at the beginning and add one to each interspace until we’re “fully adjusted”. If you look at the example above, that’s what we did:

In⎕⎕⎕⎕publishing⎕⎕⎕⎕and⎕⎕⎕⎕graphic⎕⎕⎕design,

The first three have 4 and the last one has 3. How might we determine the number of spaces that need one extra space? Well, it’s the remainder of dividing total needed spaces by how many spaces we have. For example, if we need to have 14 spaces and only have 5 spots then it’d be 4. We can express this as:

modinner|trail
3 1 0 2 2

The base extension per line is

divtrail÷inner
2 2 0 3 1

Now we can create a mask for spaces that need an extra space:

extraspaces×mod0 1+\spaces×keep
0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

Now we’re ready to put the parts together to get a replication factor for each character.

replicationkeep+extra+div(×0 1)spaces×keep
1 1 4 1 1 1 1 1 1 1 1 1 1 4 1 1 1 4 1 1 1 1 1 1 1 3 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0
1 1 1 1 1 4 1 1 1 1 1 3 1 1 3 1 3 1 1 1 1 1 1 1 1 1 1 1 3 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 5 1 5 1 1 1 1 1 1 1 1 4 1 1 4 1 4 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
1 1 1 1 1 1 1 3 1 1 1 1 1 1 1 3 1 1 2 1 1 1 1 1 1 1 1 1 1 2 1 1 1 1 1 1 1 1 0 0 0 0 0 0

and, finally, we can apply the transformation:

( t)(,replication)/,t
In    publishing    and    graphic   design,
Lorem    ipsum   is   a   placeholder   text
commonly used to demonstrate the visual form
of     a     document    or    a    typeface
without   relying   on  meaningful  content.

Combine it all into a dfn, and we get:

]dinput
Justify  {
    spaces  ' '=
    keep  ~⌽∧\spaces
    trail  +/~keep
    inner  |trail - +/spaces
    mod  inner|trail
    div  trail÷inner
    extra  spaces×mod(0 1)+\ spaces×keep
    replication  keep+extra+div(×0 1)spaces×keep
    ( )(,replication)/,
}
Justify t
In    publishing    and    graphic   design,
Lorem    ipsum   is   a   placeholder   text
commonly used to demonstrate the visual form
of     a     document    or    a    typeface
without   relying   on  meaningful  content.