Array programming techniques
Contents
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
mask←⎕←0=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
nums←⍳35
mat←(⍱⌿⍪⊢)0=3 5∘.|nums
mat×@1⍨←nums
The next step is to replace all 1s in row 2 with ‘Fizz’, and the 1s in row 3 with ‘Buzz’:
mat←(⊂'Fizz')@⊢@2⊢mat
mat←(⊂'Buzz')@⊢@3⊢mat
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×@1⍨←nums
mat←(⊂'Fizz')@⊢@2⊢mat
mat←(⊂'Buzz')@⊢@3⊢mat
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
trail←⎕←cols-+/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.
inner←⎕←trail-⍨+/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:
mod←⎕←inner|trail
3 1 0 2 2
The base extension per line is
div←⎕←⌊trail÷inner
2 2 0 3 1
Now we can create a mask for spaces that need an extra space:
extra←⎕←spaces×mod≥⍤0 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.
replication←⎕←keep+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.