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] *:= 3a 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] *:= iHere 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] *:= iOmitting 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] := 0ary 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 * oten 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 | ! _2bHere _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.