Programmers Workshop Series Part 2

Passing arrays to procedures is simplicity itself if you use our cunning techniques.

Volume 2

Number 1

March 1984

Pulling strings and handling arrays

By ALAN WHITTLE

MANIPULATING arrays in BBC Basic is hampered by the fact that their names cannot officially be passed to procedures as parameters. In the case of numerical arrays we can get around this quite nicely by smuggling the names in as strings, and the system turns out to have surprising bonuses.

First let's look at the method itself. As an example, consider a straightforward procedure for adding two one dimensional arrays together element by element:

DEF PROCaddl(n%)
LOCAL i%
FOR i%=0 TO n% result(i%) = a(i%)+b(i%)
NEXT i%
ENDPROC

The call PROCadd1(10) then has the effect of adding the first 11 elements of a and b together and stores the sum in the array result.

The usefulness of this is very limited, since we would have to rewrite part of it if the names of the arrays were other than a and b, or we would have to copy the required arrays to a and b in advance, squandering both time and storage space.

The following technique improves on this a great deal.

DEF PROCadd2( n%, left$, right$)
LOCAL i%
FOR i%=0 TO n%
result (i%)=EVAL(left$+"(i%)") + EVAL(riqht$ + "(i%)")
NEXT i%
ENDPROC

This is a considerable improvement, because we can now feed the names of any two arrays we like to the procedure as parameters. The call:

PROCadd2(10,"a","b")

for example, has the same effect as our earlier call of PROCadd1. We still have to copy result to wherever we need it, but at least we've cut down on the copying.

There are further advantages of this system hidden just below the surface. If we want to add the absolute values, say, of the two arrays instead of the actual values, we can use the call:

PROCadd2(10, "ABSa", "ABSb")

This works because ABS - like all other ready-made functions except RND - doesn't need the parentheses which we often insert for tidiness.

Furthermore, the same procedure will serve to add two user-defined functions of i%, say FNfred(i%) and FNbill(i%), instead of arrays, provided we have suitably defined the functions named. For this we need the call:

PROCadd2(10, "FNfred", "FNbill")

It would be nice to go yet further with this and add functions of array values. But unlike most of the ready-made functions, we can't drop the parentheses in expressions such as FNfred(x).

So our procedure as it stands is not capable of adding, say, FNfred(a(i%)) and FNbill(b(i%)) for a sequence of i%. since it requires (i%) to be the last part of the string to be evaluated, and that

final parenthesis gets in the way.

One way of gaining this extra flexibility is to simplify the procedure and pack more into the strings passed as parameters. The procedure becomes:

DEF PROCadd(n%,a$,b$)
LOCAL i%
FOR i%=0 TO n%
result(i%) = EVALa$ + EVALb$
NEXT i%
ENDPROC

and the call:

PROCadd(10, "FNfred(a(i%))", "FNbill(b(i%))")

has the required effect.

This has the disadvantage that now we always have to incorporate the (i%) in the parameter strings, even when we're adding arrays without the frills we've been at pains to cater for, and this is a bit irksome when keying in.

We can get the best of both worlds by defining PROCAdd - note the capital 'A'- as follows:

DEF PROCAdd(n%, a$, b$) PROCadd(n%, a$+"(i%)", b$+"(i%)") ENDPROC

Thus a call of PROCAdd without the (i%) parts in the strings is the same as one of PROCadd with them, and we use PROCadd only when (i%) is not at the end of the string.

Even now we have not yet reached full flexibility. Our procedure will add all sorts of different arrays or functions of arrays, but it will still only add.

We might want to perform any of the usual arithmetic operations, logical operations or whatever, and we can do this by specifying the type of operation in one parameter string op$.

Our procedures are now modified to their final forms:

DEF PROCop(n%, a$, b$, op$)
LOCAL i%, a, b
FOR i%"0 TO n%
a=EVALa$ : b=EVALb$
result(i%) = EVALop$
NEXT i%
ENDPROC
DEF PROCOp(n%, a$, b$, op$)
PROCop(n%, a$+"(i%)", b$+"(i%)", op$)
ENDPROC

The string op$ must specify the term-by-term operation in terms of a and b, where a and b stand respectively for the elements of the array specified by a$ and b$. We use PROCop or PROCOp analogously to PROCadd and FROCAdd above.

Thus PROCop is needed only when we are forced to include (i%) in the parameter strings - that is, when it doesn't appear at the end of them.

These final procedures are remarkably flexible. A few examples will illustrate their power, and then we will look briefly at some more procedures and functions that take advantage of the techniques outlined so far.

Example I: To subtract an array called second from another called first, each with n elements, use the call:

PROCOp(n-l, 'first", "second", "a-b")

Example II: To multiply the sines of the elements of array A by the cosines of those of array B, each having 20 elements, use:

PROCOp(19, "SINA", "COSB', 'a*b")

Example III: To divide FNpoly of the elements of array Fred by FNpoly of those of array Charlie, where FNpoly is defined somewhere in the program, we need PROCop instead of PROCOp:

PROCop(n-l, "FNpoly(Fred(i%))", "FNpoly(Charlie(i%)", "a/b")

Of course we must be careful that none of the bs turn out to be zero!

Example IV: We can make that final string more complicated, using functions or whatever we like:

PROCOp(n-l, "COS", "SIN", "SQR(a*a+b*b)")

can be used to help verify a result well known to those who have studied trigonometry.

Now, as promised, we'll look at a couple of different functions and a procedure.

First, one to evaluate the maximum element of a two-dimensional array given, as before, in two forms for maximum flexibility.

DEF FNmaximum(a$, m%, n%)
LOCAL i%, j%, max%, temp%
FOR i%=0 TO m%:FOR j%=0 TO n%
temp%=EVALa$
IF temp%>max% THEN max%=temp%
NEXT j%: NEXT i%
= max%
DEF FNMaximum(a$, a%, n%) = FNmaximum(a$ + "(i%,j%)", m%, n%)

If we also need to find the minimum element, we don't need to repeat the whole procedure with the obvious minor amendments.

Instead, we call on another variant of the techniques discussed earlier:

DEF FNminimum(a$, m%, n%) = -FNmaximum("-'+a$, m%, n%)
DEF FNMinimum(a$, m%, n% )= -FNMaximum("-"+a$, m%, n%)

I'll leave you to figure out how this works.

Again, calls such as:

x"FNMaximum("ABSa", 10, 4)

may be useful to find the element in the 11x5 array a with largest absolute size. Finally, an example to show how Boolean conditions can also be passed as parameters:

DEF PROCchoose(a$, b$, n%, condition$) LOCAL i%, a, b FOR i% = 0 TO n% a=EVALa$ : b=EVALb$ IF EVALcondition$ THEN result(i%)=a ELSE result(i%)=b NEXT i% ENDPROC

DEF PROCChoose(a$, b$, n%, condition$) PROCchoose(a$+ "(i%)", b$+"(i%)", n%, condtion$) ENDPROC

This could be used, for example, to find the array that chooses, for each i% (0 to 15), the larger of the two elements left(i%), right(i%), by means of the call:

PROCChoose("left", "right", 15, "a>b")