| Getting Started | Documentation | Glish | Learn More | Programming | Contact Us |
| Version 1.9 Build 1556 |
|
Glish also has multi-dimensional arrays. These arrays can be manipulated much like single dimension vectors. There are operations which permit ``slices'' and ``picks'' to be taken of arrays. Most of the functions and operators work on arrays and one dimensional vectors.
Typically, an array is created using the array function. This function takes an arbitrary number of arguments. The first argument is the initial value for the new array. The remaining arguments are the lengths of the dimensions of the array. So, to create a cube,
cube := array(0,4,4,4)
creates a three dimensional array that is initialized to all zeros. Each of
the three dimensions of the cube has a length of four. The array can also be
initialized with a vector:
plane := array(1:4,4,3)
This creates a matrix that has two dimensions. Each of the three columns are
initialized to [1, 2, 3, 4]. If the initialization vector has fewer
elements than the final array, the vector is replicated to fill the array.
If the initialization vector has more elements than the final array, only
the initial portion of the vector will be used to fill the array; the rest
will be ignored. In the following
one := array([3,5],3,3)
two := array(1:25,3,4)
one is filled with repeating [3, 5] in column-major order. The
first (and third) columns equal [3, 5, 3] while the second
column equals
[5, 3, 5]. In the case of two, the elements of the array are
filled (in column-major order) with integers starting with 1 and
ending with 12; the first column of two contains [1, 2, 3].
Arrays are indexed using the the [] operator. This operator is used with one subscript for each dimension of the array. This operator can be used to access a single element, a contiguous section, or elements sprinkled throughout the array.
Access to individual elements of arrays is accomplished much like indexing one dimensional vectors. The only difference is instead of a single subscript, multiple subscripts are used, one for each dimension. For example,
x := array([3,5],3,3)
for (i in 1:3 ) x[i,i] *:= 3
a three by three matrix is assigned to x. The for
loop (§ 5.5.2, page
) then multiplies each
of the elements along the diagonal of x by 3. If x had three
dimensions instead of two, then three subscripts would be required. As with
vector indexing, the array subscripting operation are used to get as well
as put values in the array.
Multiple elements of arrays can be accessed as well. Often, however, the result will be another array instead of a vector. For example,
cube := array([3,5],3,3,3)
for (i in 1:3 ) x[1:3,1:3,i] *:= i
Here cube is a three dimensional array, and the for loop
multiplies each plane in the cube by its position. The first plane is
unchanged, i.e. multiplied by 1, but the second plane is multiplied
by 2 and the last plane is multiplied by 3. x[1:3,1:3,i]
selects a whole plane from the cube, in particular the ith plane.
The 1:3 in the first two subscript positions selects
all of the elements
in those dimensions, but the third subscript, i, limits selection
in that direction. Selecting large sections of the array in this manner
is often referred to as ``slicing'' the array.
Often, all of the elements along a dimension are not selected. In this case, the elements of interest must be listed explicitly. When all of the elements along a certain dimension are desired, as the case above, omitting a particular subscript implies all of the elements along that dimension. The example above can be rewritten:
cube := array([3,5],3,3,3)
for (i in 1:3 ) x[,,i] *:= i
Omitting subscripts implies the whole dimension. Whole array operations
are permitted much as they are for vector access (see § 3.6.3).
The ability to use subscripts to access either single elements or slices of an array covers most of the operations typically performed on arrays. Sometimes, however, this is not sufficient. Suppose, for example, you want to access the top left corner and bottom right corner of a matrix. With the available subscript operations presented above, the best that you can do is the following:
mat := array(1:16,4,4)
tlc_brc := mat[[1,4],[1,4]]
This unfortunately sets tlc_brc equal to a two by two array that
contains each of the four corners. To handle array accesses where desired
array elements are distributed throughout the array, a single subscript
that itself is an array can be used to access the elements of the array.
The problems with the example above can be fixed as follows:
tlc_brc := mat[array([1,4],2,2)]
In this example, an array is used as the single index into the array.
The rows of the subscript array indicate the elements of mat to
be selected. So the number of columns of the subscript array will
always equal the dimensionality of the array being accessed. In
the example above, the first point, [1, 1], is the first row of
the subscript array, and the second point, [4, 4], is the second
(and final) row. The subscript array can have an arbitrary number of rows.
This type of subscripting implements what is commonly known as a
``pick'' operation. Array subscript indexing can be use to both
get and put elements of the array.
Arrays that are used as an array index should not be of type boolean. (See § 3.7.4 for a discussion of boolean array indexes.)
As with ``picks'', sometimes specifying slices with multiple subscripts doesn't work. The situation may occur when writing a function that manipulates an array of an arbitrary dimensionality. In this case, use of multiple subscripts breaks down. Indexing with a subscript array solves this problem for accessing individual elements of the array, and indexing with a record solves it for accessing slices of the array.
A record can be used to specify the elements of the slice. The length
(see § 10.3, page
) of the record must equal the dimensionality of
the array. The field values of the record are used irrespective of the
field label, and each of the field values acts as one of the subscripts
in the slice operation. For example,
cube := array(1:27,3,3,3)
one := cube[,3,]
two := cube[[a=1:3,b=3,c=[]]]
cube is assigned an array with three dimensions. Here one is
assigned the ``right'' slice along the z axis. The assignment to
two demonstrates how
you use a record to index the same slice of the array. Each of
the fields of the record correspond to the respective dimension of
the array. The position of the field, rather than the field name, is
used to determine the dimension it specifies. In the assignment
to two, the first field contains 1:3 so the entire first
dimension participates in the slice, the second field to is set to 3 thus
limiting the second dimension, and since the final field is an empty array,
the entire third dimension participates in the slice. In this example,
one and two
have the same value after the assignment. Since the field names are ignored,
numeric subscripts can also be used to set up the index:
index := [=]
index[1] := []
index[2] := 3
index[3] := 1:3
three := cube[index]
Here index is set up with a record equivalent (for the purposes
of indexing arrays) to the one that was used to set two above. Thus after this
example, one, two and three all have the same value.
As was indicated, this is mainly used when the shape of the array is not known ahead of time. Functions that deal with arrays of a non-predetermined shape can use the system defined attribute shape (which is defined for all arrays) to determine the shape of the array at runtime. For arrays, this shape attribute always contains a vector that indicates the shape of the array. In the following,
func s( ary ) { return ary::shape }
the function s returns the shape of arrays. If s is invoked with
cube from the previous example as an argument, the result is [3, 3, 3].
As was mentioned earlier, most functions and operators accept arrays or vectors as parameters. All of the arithmetic, comparison, and logical operators operate element by element on arrays as well as vectors. This is not matrix arithmetic but rather element by element array arithmetic.
It is easy to write functions which operate on both vectors and arrays. This is because arrays are implemented as a vector plus a shape attribute. So an array can easily be constructed from a vector, for example
vec := [3, 4, 9, 12, 8, 2, 10, 21, 5]
vec::shape := [3, 3]
v := vec[3, 3]
Here a vector, vec, is constructed, and then by adding
a shape attribute is turned into an array, and from that point
on it can be manipulated as an array. v equals 5. Deleting
the shape attribute turns vec back into a vector,
vec:: := [=]
Now, an attempt to use array addressing, e.g. vec[3, 3], results
in an error.
The underlying vector can always be accessed for any array, and the shape attribute need not be cleared. For arrays, whenever a single non-boolean vector value is used as a subscript, the underlying vector is accessed. In the following,
vec := 1:9
vec::shape := [3, 3]
a := vec[3, 3]
b := vec[len(vec)]
both array accesses are valid, and both a and b have the
same value, 9. length applied to an array returns the
length of the underlying vector.
If the underlying vector and the shape of an array do not match, errors can result. If the length of the vector is greater than the length implicit in the shape, there is no problem; only a portion of the underlying vector will be used. If, however, the vector length is less than the length implied by the shape, runtime errors will be reported when accesses are attempted past the end of the vector. If the array function is used to create arrays, these errors can not happen.
As discussed above, many of the operators and functions operate element by element on arrays and vectors. This is how boolean indices are used with arrays. In the following,
ary := array([1, -3, -4, 7, 2, -1, 10, -6, 3],3,3)
ary[ary < 0] := 0
ary is assigned a two dimensional array. The next statement
alters all of the elements of the array that have negative
values by setting them to zero. The comparison operators applied to
arrays operate element by element, and the result is another array.
If two arrays are involved, their lengths must be equal, and if a
scalar and an array are involved, as above, the scalar is compared
with each element of the array. A boolean array is generated as
a result. This boolean array is then used to select elements from
the array in the same way that boolean masks are used with vectors
(§ 3.6.2, page
). A
single boolean vector or array subscript acts as a mask, and as a result,
the length of the boolean subscript must equal the length
of the array being indexed.
Arithmetic operations applied to arrays are identical to the corresponding vector operations. If an arithmetic operator is applied to two arrays, their lengths must be equal, and the result is the array generated by the element by element application of the operator. In the following,
ten := array(1:9,3,3) + array(9:1,3,3)
ident := array(0,4,4)
ident[array(1:4,4,2)] := 1
o := array(seq(1,4,.2),4,4)
r := ident * o
ten is a two dimensional array, and each of its elements equals
10. ident, after the two assignments, is the identity matrix
with 1s along the diagonal and 0s elsewhere. o has
1:4 along its diagonal and the intermediate numbers elsewhere.
When ident is multiplied by o the result, r, is a
matrix with 1:4 along its diagonal, but zeros elsewhere. This
demonstrates the nature of array arithmetic within Glish. Arithmetic operators
perform element by element operations; they do not obey the rules of
matrix arithmetic.
Logical operators also perform element by element operations. For example,
_2b := array(F,3,3)
the_question := _2b | ! _2b
Here _2b is a two dimensional boolean array, the_question
is a two dimensional array with each element set to T. This is a result
of the element by element nature of Glish logical operations.