Doing Useful Things with Strings
Vector 6:4, page 24
Transcribed from the printed issue; not yet reviewed. Read it in the PDF of the issue, page 24
Introduction¶
Just in case you think I have a one-track mind, my article this edition concentrates on the manipulation of strings. APL deals with arrays either of numbers or of characters. Many of the APL primitives apply equally well to arrays of characters as arrays of numbers, excluding the arithmetic ones of course. But is it useful to consider manipulating arrays? In the last edition of VECTOR my fellow statistician Tony O’Hagan demonstrated very cleverly one such application - to generate program code for the computation of a wide range of similar problems that could be described by a formula (string).
My contribution is not so esoteric, but nevertheless useful in helping newcomers to APL to understand some of the things that can be done with strings. One such situation is that of importing data from a file external to APL.
Manipulating an ASCII File¶
Even if my concern is still with data analysis, frequently the data of interest may come from a computer file external to APL. All APL interpreters give the user the ability to read in an ASCII file, though they tend to have different ways of doing it! Let’s suppose that we have managed to get such a (small) file into APL as a text matrix. What must we do to convert it into numbers?
As you can see, ASF is clearly not what it looks - a 4 by 3 array of numbers but a 4 by 16 character matrix. Also note that it includes the usual ‘middle of the line’ minus sign to indicate those numbers that are negative. So we have to convert such signs to the APL high-minus sign, and then ‘execute’ i.e. convert to numbers using the APL execute symbol ⍎. Since this will only work on a vector, the matrix must be converted into a vector using ‘,’. In doing so we make sure that there is at least one space on the end of each line, so that the end of one line does not merge into the beginning of the subsequent line.
Let’s do this in easy stages. First convert those signs.
This catenates one column of spaces onto the right of ASF, and then converts to a vector.
The vector A='-' is a boolean vector indicating where the '-' signs are in A, which is used to reduce the integers 1 2 3 4 …. to give the positions in A where they occur.
This is all that is required to convert the string A into the vector of numbers portrayed by A. This can then be dimensioned to give a numeric matrix.
Thus we may usefully write this as a utility function called ASCTONUM:
Boxing a Set of String Vectors¶
It is very often useful to have a set of names in APL as a character matrix. If the names are not all of the same length, then we need to be able to pad out the names to the longest length using blanks - an operation usually referred to as making a BOX.
The key to the solution of this problem is knowing how to replicate values of a vector. This is demonstrated by the following examples.
In each case, a vector of non-negative integers of the same length as the source vector is used to say how many elements of each are to be used. So how do we use this facility for boxing?
Let’s suppose that we are faced with the string of names ALAN,ALBERT and ADA. We will separate the names using the character ‘/’ …
Note that we have used the separation string as the last character - any other character that is not in the names could be used. First we find out just where the separators occur, and replace them by blanks:
Now we find out how long the names are (vector J), the maximum length (M) and hence the number of spaces required to pad each out to the longest (K):
If we now make a vector, most of whose elements equal 1, except those elements corresponding to spaces (given by I), which we make equal to the elements of K, then this vector may be used to replicate X:
Hence we have …
We pull all these elements into a function called BOX as follows:
∇ RES←BOX X;I;J;K;M;REP
[1] ⍝ Find out where the separators are
[2] I←(X=¯1↑X)/⍳⍴X←,X
[3] ⍝ Replace them with spaces
[4] X[I]←' '
[5] ⍝ Calculate the length of each name J
[6] ⍝ and the maximum length M
[7] ⍝ and the number of spaces required to pad out
[8] J←¯1+I-0,¯1↓I
[9] K←(M←⌈/J)-J
[10] ⍝ Construct a vector for using with 'replicate'
[11] REP←(⍴X)⍴1
[12] REP[I]←K
[13] ⍝ Replicate and convert to a matrix
[14] RES←((⍴I),M)⍴REP/X
∇
Right-Justification¶
Whether you like your text right-justified or not is a matter of taste. Conventional word-processors normally incorporate a ‘reformat’ procedure which takes the jagged text and neatly puts it into lines of a given length. How might one tackle this in APL? The problem is a similar one to the previous example involving placing extra spaces adjacent to existing spaces so that on re-shaping, the resulting matrix has no gaps at the end of the line, and words are not split. As newspaper readers will know, this can lead to serious problems for small columns (such as splitting ‘legend’ to become ‘leg-end’). My attempt at right-justification will not attempt to be sophisticated enough to cope with small column widths in general, though we use a small amount of text as an illustration.
is our text vector, and suppose the line length required is 30. As before, we find out where the blanks occur (J), and the position of the last blank before the line length (K1). In addition, we find the last non-blank before K1: K2.
This last line requires some explanation. The vector K1↑X consists of the first K1 elements of X, and hence (K1↑X)≠' ' returns a boolean vector corresponding to all the non-blank elements. The character ⌽ reverses the order of the elements of its vector, and V⍳1 finds the first occurrence of 1 in vector V, hence K2 is as required.
So we can now calculate the number of extra spaces required - namely NS←LL-K2. We then have to decide how to apportion the extra spaces NS amongst the number of blanks NJ. A whole range of different strategies are possible - I decided to construct a vector of length NJ which consists of integers as near equal as possible, with the higher values at the beginning. The values used are therefore equal to M and M+1 where M←⌊NS÷NJ, and the number of values of M+1 is given by N←NS-M×NJ. To right-justify the first line, all we need to do now is to construct a vector REP to replicate the elements of the vector X the required number of times.
So what about the rest of the vector? If we allow our function to be recursive, then at this stage we can just apply the function to the rest. To do this, we must check initially the vector is greater than the length of line LL - if it isn’t then there’s nothing to do except make sure that the text is a matrix so that lines may be appended to the top.
The ideas expressed here are incorporated into the function RJ with left argument equal to LL and right argument the text vector X to be right-justified:
∇ R←LL RJ X;I;J;K1;K2;REP;F;NJ;M;NS;N
[1] ⍝ Check for length being greater than one line
[2] →(LL<⍴X)/ct
[3] R←(1,LL)⍴LL↑X ⋄ →0
[4] ct:I←X=' '
[5] ⍝ K1 is the position of the last blank in the first line
[6] ⍝ K2 is the position of the last non-blank char before K1
[7] J←I/⍳⍴I ⋄ K1←J[+/J≤LL]
[8] K2←1+K1-(⌽(K1↑X)≠' ')⍳1
[9] ⍝ Reduce I to len K2 and find posns J and no NJ of blks
[10] I←K2↑I ⋄ NJ←⍴J←I/⍳⍴I
[11] ⍝ Calc the number of blanks F to be added at each J
[12] NS←LL-K2 ⋄ M←⌊NS÷NJ ⋄ N←NS-M×NJ
[13] F←(N,NJ-N)/ 1 0 +M
[14] ⍝ Make vector for replicating blanks in 1st line to LL
[15] REP←(⍴I)⍴1 ⋄ REP[J]←REP[J]+F
[16] ⍝ Repl current line and appd to same treatment of rest
[17] R←(REP/K2↑X),[1]LL RJ K1↓X
∇
25 RJ X ... as an example
EDUCATION VECTOR
ARTICLES ARE DESIGNED
FOR BEGINNERS IN APL
Of course, there’s much more to a proper facility to right-justify text. Carriage returns in the text indicate that the contents of that line should not be right-justified, and in addition, unnecessary spaces in the text should be removed before right-justification is applied. This suggests ‘an easy exercise for the reader’ (as one of my Maths professors used to say). How can unnecessary blanks be removed from a text vector? If you feel like experimenting, please write to Education Vector with your results!