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")