More String Manipulations
Vector 7:1, page 27
Transcribed from the printed issue; not yet reviewed. Read it in the PDF of the issue, page 27
Introduction¶
Replies to my questionnaire seemed to suggest that readers of Education Vector have found its contents interesting and helpful. One of the nice things about being its editor, is that I benefit from readers’ attempts to broaden my experience of APL. This short note comes as a response to one of Bill McLean’s notes to me, where he draws attention to a program published in ‘APL IS EASY’ by Jerry R Turner. It produces a calendar for a specified month and year and is used to illustrate the typical compactness of APL programs. I thought other readers might be interested in this program, particularly as Bill had problems converting it to run in IAPL because of a sophisticated ‘format’ statement used.
Recently I have made great use of a program I wrote to annotate columns of a matrix. So I thought that I would continue my theme of string manipulations for a second issue. First then the calendar.
A Calendar Program¶
Here is a typical calendar for August 1990 as you might find in the front of a diary, or on the current calendar in your kitchen.
AUG 1990
SUN MON TUES WED THUR FRI SAT
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
The APL approach to producing such objects is generally to regard them not as consecutive lines of printing but as a matrix of characters - this one happens to be 9 by 42. What are the steps involved in producing such a calendar?
First we have to calculate two numbers - one telling us that the 1st of August 1990 occurs on a Wednesday, and the other that there are 31 days in the month required. So we require two functions, call them DAYINWEEK and DAYSINMONTH. The second one is the easier one to do since February and leap years is the only complication we have to cope with. Here is the function, with right argument being the year and left argument the month (1 to 12):
DAYSINWEEK is more complicated, and quite honestly I confess to not even trying to understand the code as presented by Jerry Turner:
∇ Q←M DAYOFWEEK Y;T
[1] M←M,1,Y
[2] Q←100|(M[1 3]← 1 0 +⌽ 0 12 ⊤¯3+ 0 12 ⊥M[3 1])[2]
[3] Q←7|M[2]+Q+(⌊¯0.2+2.6×M[1])+(⌊Q÷4)-⌈1.75×⌊M[3]÷100
∇
Note by the way, the value returned is the day of the week starting from Monday, so 8 DAYOFWEEK 1990 gives (what I shall call my OFFSET) 3, telling me that I must allow for 3 blanks at the beginning of the first week in August.
Having successfully pirated the nasty bits (thanks Jerry) now let’s concentrate on the formatting. The format operator ⍕ converts numbers into characters. It can be used with a two digit prefix that specifies the total size (in characters) of the field associated with each number and the number of decimal places. Hence for example:
and
Since we can have up to six different weeks represented in a month, and assuming that day titles have block length 6, we need to create a 6 by 42 array of characters consisting of blanks and then the numbers 1 to 31, plus more blanks. This can be done using the statement:
The rest is now relatively easy - we can append a string of day titles by using the expression
as long as TITLES is also of width 42. Then we have to append the appropriate month and year onto the top of this. The resulting program will be something like the following, where I have used last issue’s function BOX to provide a matrix of (abbreviated here) month names.
∇ CAL←MONTH CALENDAR YEAR;DAYS;MONTHS;NDAYS;OFFSET
[1] DAYS←' SUN MON TUES WED THUR FRI SAT'
[2] MONTHS←BOX'JAN/FEB/MAR/APR/MAY/JUN/JUL/AUG/SEP/OCT/NOV/DEC/'
[3] OFFSET←MONTH DAYOFWEEK YEAR
[4] NDAYS←MONTH DAYSINMONTH YEAR
[5] CAL← 6 42 ⍴252↑((6×OFFSET)⍴' '), 6 0 ⍕⍳NDAYS
[6] CAL←' ',[1]DAYS,[1]CAL
[7] CAL←(42↑(15⍴' '),MONTHS[MONTH;],' ',⍕YEAR),[1]CAL
∇
Labelling a Matrix¶
Producing tables with rows and columns nicely labelled is not easy. It was one of the first things I tried to do in a demonstration in my early days with APL and failed abysmally. The function HEADER is quite useful in this respect . It builds on some of the code in BOX to produce a matrix of specified names (underlined) of desired width with the extra facility of being able to left- or right-justify the titles. (As you probably noticed, the format operator right-justifies numbers whereas columns of text such as names will be left-justified, so it is useful to be able to do both.)
Here is the function.
∇ R←N HEADER ST;I;J;K;L;U;LL;LNEG;T;TT
[1] ⍝ Header with left or right just'n according to sign of N
[2] L←(I←ST=¯1↑ST)/K←⍳⍴ST ⍝ pos of separators
[3] J←¯1+L-0,¯1↓L ⍝size of strings
[4] ST[I/K]←' ' ⋄ U←ST ⋄ U[(~I)/K]←'-'
[5] LNEG←(N<0)/⍳⍴N ⋄ LL←L ⋄ LL[LNEG]←1+(0,L)[LNEG]
[6] L←(N<0)/L ⋄ LL←(N<0)/LL ⍝ L is pos to move to, LL to move
[7] T←(⍴ST)⍴1 ⋄ T[LL]←0
[8] TT←∨\~T ⋄ K[T/K]←K[T/K]-T/TT ⋄ K[LL]←L
[9] ⍝K now puts blanks at start or end according to r-j or l-j
[10] ST←ST[K] ⋄ U←U[K] ⋄ I←1+I[K]
[11] I[(I=2)/⍳⍴I]←(|N)-J
[12] R←(I/ST),[0.5]I/U
∇
Let’s see what it does …
15 20 10 HEADER 'BANANAS/APPLES/PEARS/'
BANANAS APPLES PEARS
------- ------ -----
15 ¯20 10 HEADER 'BANANAS/APPLES/PEARS/'
BANANAS APPLES PEARS
------- ------ -----
Now let’s use it. Suppose we have a 3 by 3 matrix of marks M, corresponding in rows to three pupils, and in columns to three subjects say MATHS, ENGLISH and PHYSICS.
If we wish to label the columns, all we need do is decide on the width ( which should be big enough to cope with the label size - say 10), format the columns of M, create the header, and glue together.
(¯10 ¯10 ¯10 HEADER 'MATHS/ENGLISH/PHYSICS/'),10 0⍕M
MATHS ENGLISH PHYSICS
----- ------- -------
14 76 46
54 22 5
68 68 94
(If you require different widths for the columns, then your format statement must have a pair of numbers for each column, e.g. 10 3 15 2 20 0⍕M.)
So far so good. What about pupils names. These can be created using BOX:
and to accommodate the names we can have a new title:
where we have allowed 20 spaces for the names. But NAMES is only of width 9. So to glue NAMES to the marks to make a matrix of width 50 we must expand NAMES, which we can do using 3 20↑NAMES.
Hence finally, we get the full table thus: