There are many kinds of arrays in OADL:
Array |
List |
PackBool |
String |
PackChar |
PackByte |
PackUbyte |
PackShort |
PackUshort |
PackHalf |
WideString |
PackWideChar |
PackInt |
PackUint |
PackFloat |
PackLong |
PackUlong |
PackDouble |
|
|
Regardless of kind, all arrays are ordered lists of values, indexed by one or more numeric values. Arrays are a first-class types in OADL, in that they are dynamically allocated and assigned, and can participate in arithmetic operations as easily as scalars can. An array in OADL has three primary attributes: its shape, the type of its elements, and the elements themselves. The dimensionality of an array is known as its rank.
As mentioned, arrays are indexed by one or more numeric values. The indexes start at 0, and therefore the maximum index for a given dimension is the size of that dimension, minus one. Note that some of the array types actually only have one dimension; they are indistinguishable from the corresponding multi-dimensional type if only one dimension is specified:
Single-dimensional type |
Multi-dimensional type |
---|---|
List |
Array |
String |
PackChar |
WideString |
PackWideChar |
As mentioned before, array elements may be of any type. In the
most general case, each element may be of a different type. This
"generic" array will give a typeof()
result of Array
or List
,
depending on whether the array is multi- or single-dimensional. The
new Array(
dims)
and
new List(
length)
methods create generic arrays and lists.
However, it is more efficient in both space as well as execution
speed if all the elements are of the same type and are packed
together. OADL will create these packed arrays when the [
]
array creation syntax is used. It is a
TypeCheck
error to try to assign a
non-convertable value to an element of a packed array:
a = [1,2,3] a[1] = 2.5 // 2.5 will be converted to Int a[1] = "two" // "two" cannot be converted to Int Illegal type
The pack()
and unpack()
methods can be used to make packed and unpacked copies of arrays,
respectively. The array base type will be computed via the table
shown under the Bracket syntax (packed arrays)
section, below. For example:
a = [1,2,3].unpack() a[1] = "two" a 1 two 3 a = {1,2,3} typeof(a) List a = a.pack() typeof(a) PackInt
Elements of arrays may be referenced and changed using the
indexing operator
arr[
indexes]
Arrays in OADL always use zero-based indexing:
a = new Array(3) for (var i = 0; i < 3; i++) { a[i] = i + 1; } a 1 2 3
The RangeCheck
exception is thrown when an
attempt is made to access a non-existent element:
a = {1,2,3} a[3] = 4 Value out of range
Multi-dimensional arrays may be indexed by a list of numbers that can be as short as one number or as long as the number of dimensions of the array:
a = [3,3,3].iterate() a[0] 0 1 2 3 4 5 6 7 8 a[0,1] 3 4 5 a[0,1,2] 4 a[0,1,2,3] Inconsistent array shape
If a partial list of indexes is given, a copy of the subarray is created. This can lead to subtle differences in program execution between fully specified indexes and partially- specified indexes:
a[0,1,2] = 50 a[0] 0 1 2 3 4 50 6 7 8 // Note that a[0,1,2] will not be modified by this assignment a[0][1,2] = 500 a[0] 0 1 2 3 4 50 6 7 8
Any of the indexes may specified as a contiguous range of indexes
by using the :
syntax. If the first index in a
range is not given, 0 is assumed. If the last index in a range is not
given, the last element in that dimension is assumed:
str = "Hello world!" str[6:10] world str[:4] Hello str[6:] world! a = [3,4,3].iterate() a[1,1:2,1] = 42 // Assign just a sub-portion of the array a[1] 12 13 14 15 42 17 18 42 20 21 22 23
Arrays may also be indexed by other arrays; in that case, the resulting shape is the concatenation of the shapes of all the indexes (with a scalar index not increasing the rank of the resulting array):
a = [3,3,3].iterate() a 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 a[[0,1],1,[1,2]] 4 5 13 14 a[2,[1,2],[1,2]] = 100 a[2] 18 19 20 21 100 100 24 100 100 // The consistency rules for array-valued-index assignment are the same // as those for arithmetic expressions a[2,[1,2],[1,2]] = [[22,23],[24,25]] a[2] 18 19 20 21 22 23 24 24 25 a[2,[1,2],[1,2]] = [100,200] Inconsistent array shape
Any array-valued indexing result is actually a new copy of the array subrange. Changing an element of the subrange will NOT change the corresponding element of the original array.
Although syntactically similar, there are subtle differences
between using the
arr[
start:
end]
syntax and using an iterator as an index
arr[[
start:
end]]
.
Internally, OADL can optimize the first form to create a
copy-on-write reference to the array, reducing heap memory usage.
Additionally, unbounded iterators cannot be used as array indexes:
a = "Hello, world!" a[:] Hello, world! a[[:]] Unbounded iterator
A special indexing operator is the "flattened" indexing
operator #[
index]
. It
takes a single index (which may be an array) and interprets it as the
offset, in elements, from the beginning of the array. This can be
useful when writing algorithms that generally operate on arrays of
any rank.
a = "abcdefghi".reshape(3,3) a abc def ghi a#[2] c a#[4] = 'E' a abc dEf ghi
Various properties of arrays can be queried with the following intrinsic methods:
.shape()
.rank()
.length()
.width()
.sizeof()
.stride()
.arrbase()
Array
for a heterogeneous array.readonly()
.transient()
Examples:
arr = [2,3,4].iterate() arr.shape() 2 3 4 arr.rank() 3 arr.length() 2 arr.width() 4 arr.sizeof() 24 arr.stride() 12 4 1 arr.arrbase() Int arr.readonly() false arr.transient() true
It is is not possible to change the shape of an array; however, it
is possible to allocate a resized copy of an array by using the
reshape()
method.
str = "Hello world!" hello = str.reshape(5) hello Hello typeof(str) String str = str.reshape(2,6) str Hello world! typeof(str) PackedChar
An array or scalar may be enclosed. This creates a new
Enclosure
object which may be used as a scalar in
arithmetic operations. Arrays may be enclosed along one of their
axes; the result is a List
or
Array
consisting of slices of the array along
that axis:
a = [2,3,4].iterate() a.enclose() +-----------+ | 0 1 2 3| | 4 5 6 7| | 8 9 10 11| | | |12 13 14 15| |16 17 18 19| |20 21 22 23| +-----------+ a.enclose(1) // Enclose along axis #1 +-------+ +-------+ +--------+ +--------+ |+-----+| |+-----+| |+------+| |+------+| ||0 4 8|| ||1 5 9|| ||2 6 10|| ||3 7 11|| |+-----+| |+-----+| |+------+| |+------+| +-------+ +-------+ +--------+ +--------+ +----------+ +----------+ +----------+ +----------+ |+--------+| |+--------+| |+--------+| |+--------+| ||12 16 20|| ||13 17 21|| ||14 18 22|| ||15 19 23|| |+--------+| |+--------+| |+--------+| |+--------+| +----------+ +----------+ +----------+ +----------+ // The resulting Array has the shape of the remaining axes of the // original array a.enclose(1).shape 2 4 [1,2,3,4]+[10,20].enclose() +-------+ +-------+ +-------+ +-------+ |+-----+| |+-----+| |+-----+| |+-----+| ||11 21|| ||12 22|| ||13 23|| ||14 24|| |+-----+| |+-----+| |+-----+| |+-----+| +-------+ +-------+ +-------+ +-------+ // An enclosure may be (partially) reversed via the disclose() method a.enclose(1).disclose() +-----+ +-----+ +------+ +------+ |0 4 8| |1 5 9| |2 6 10| |3 7 11| +-----+ +-----+ +------+ +------+ +--------+ +--------+ +--------+ +--------+ |12 16 20| |13 17 21| |14 18 22| |15 19 23| +--------+ +--------+ +--------+ +--------+
Arrays can be created in several ways. These include:
new
arrType syntaxforeach
expressionStrings are single-dimensional arrays of either Char or WideChar, and constant strings can be created with the double-quote syntax:
str = "Hello!"; lStr = L"Long String!";
Arrays can be created and their contents initialized via the bracket syntax, which allows multi-dimensional constant arrays to be created:
arr = [[1,'2', 3.0],[4,'5',6.0]] arr 1. 50. 3. 4. 53. 6. typeof(arr) PackFloat arr.shape() 2 3
Note that packed arrays will be created if possible (as was shown
in the previous example). The items between the brackets may be of
different types, but must be compatible with each other for packing;
this means one cannot mix String
constants with
scalar numeric values. The following table illustrates which types of
constants may be present in a given packed array type. The cells
highlighted in green indicate the types which force a promotion to
the packed type on the left:
Bool | Char | Byte | Ubyte | Short | Ushort | Half | WideChar | Int | Uint | Float | Long | Ulong | Double | Other | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
PackBool | Y | ||||||||||||||
String2 | Y | ||||||||||||||
PackChar3 | Y | ||||||||||||||
PackByte | Y | Y | |||||||||||||
PackUbyte | Y | Y1 | Y | ||||||||||||
PackShort | Y | Y | Y | Y | |||||||||||
PackUshort | Y | Y1 | Y | Y1 | Y | ||||||||||
PackHalf | Y | Y | Y | Y | Y | Y | |||||||||
WideString2 | Y | Y1 | Y | Y1 | Y | Y | |||||||||
PackWideChar3 | Y | Y1 | Y | Y1 | Y | Y | |||||||||
PackInt | Y | Y | Y | Y | Y | Y | Y | ||||||||
PackUint | Y | Y1 | Y | Y1 | Y | Y | Y1 | Y | |||||||
PackFloat | Y | Y | Y | Y | Y | Y | Y | Y | Y | Y | |||||
PackLong | Y | Y | Y | Y | Y | Y | Y | Y | Y | ||||||
PackUlong | Y | Y1 | Y | Y1 | Y | Y | Y1 | Y | Y1 | Y | |||||
PackDouble | Y | Y | Y | Y | Y | Y | Y | Y | Y | Y | Y | Y | Y | ||
List2 | Y | Y | Y | Y | Y | Y | Y | Y | Y | Y | Y | Y | Y | Y | Y |
Array3 | Y | Y | Y | Y | Y | Y | Y | Y | Y | Y | Y | Y | Y | Y | Y |
1 Non-negative values only
2 Only one dimension
3
More than one dimension
Strings may be included in bracket-syntax packed arrays, but they must all have the same length. In bracket arrays, a string is syntactic sugar for a bracket with the list of individual characters in it:
a = [["abc","def"],["ghi","jkl"]] a abc def ghi jkl a.parent PackChar[2,2,3] // ['j','k','l'] has the same effect as "jkl" in a bracket-syntax array b = [["abc","def"],["ghi",['j','k','l']]] b.parent PackChar[2,2,3] a == b true // The characters in the string will be type-promoted according to // the table if mixed types are present in the bracket c = ["abc",[1,2,3]] c 97 98 99 1 2 3 c.parent PackInt[2,3]
A heterogeneous List
can be created using the
brace syntax. Note that the new list will always have only one
dimension; nested lists are supported, but they will be enclosed
sub-lists:
list = {1,"two",3.0} list 1 two 3. list = {{1,"two",3.0},{4,"five",6.0}} list +--------+ +---------+ |1 two 3.| |4 five 6.| +--------+ +---------+
A heterogeneous Array
can be created using the
array brace syntax. It is very similar to the bracket syntax, but
uses the "#{
" token to group the axes:
array = #{#{1,2},#{3,4}} array 1 2 3 4 array[0,0] = "hi" // Allowed becase array is not packed array hi 2 3 4
The iterator syntax can be used to create single-dimensional packed arrays:
iterator : '[' opt-expr ':' opt-expr ']' | '[' opt-expr ':' opt-expr ':' opt-expr ']' ;
The expressions in an iterator can be of any numeric or character type; the packed array will be of the appropriate type after potential type promotion. The first expression is the start element of the resulting array. If it is omitted, the first element will be a zero of the appropriate type.
The second expression specifies the end element of the
resulting array. If it is present, then the array will be limited to
the range of the start and end expressions, inclusive.
Otherwise, the iterator is unbounded. Note that unbounded iterators
can lead to UnboundedCheck
errors if they are
used in ways which would create unbounded memory allocations.
In the two-argument form, the iterator increment will be either 1 or -1 depending on whether the first expression is greater than or less than the second expression. In the three-argument form, the increment is the third expression, and the sign of the increment must be consistent with the order of the start and end elements.
Here are some examples of iterators:
['A':'Z'] ABCDEFGHIJKLMNOPQRSTUVWXYZ ['Z':'A'] ZYXWVUTSRQPONMLKJIHGFEDCBA [0:10:2] 0 2 4 6 8 10 [0.:1.:0.05] 0. .05 .1 .15 .2 .25 .3 .35 .4 .45 .5 .55 .6 .65 .7 .75 .8 .85 .9 .95 1. a = [:] // Unbounded iterator; it cannot be printed a Unbounded iterator a[10] // ...but it can be indexed 10
The new
arrType()
syntax can create an array of any type and shape. The elements of the
array are initialized to the appropriately typed zero:
arr = new Array(10,10) // Create a 10x10 heterogeneous array str = new String(32) // Create a string that can hold 32 chars lst = new List(10) // Create a 10-element heterogeneous list cube = new PackFloat(2,2,2) // Create a 3-dimensional 2x2x2 cube of floats
The foreach
statement creates an
appropriately-sized Array
or
List
by evaluating an expression according to an
array indexing template. The syntax of the foreach statement is:
foreach : 'foreach' '(' arr-expr ')' '{' expr '}'
The arr-expr must be an array index expression. The indexes
of arr-expr must be simple names. Each name will be implicitly
declared as a local variable within the scope of the
foreach
body. The arr-expr dictates the
shape of the resulting array. If a regular
N-dimensional index is used, the shape of the resulting
array is the first N elements of the shape of the
source array. If a flattened index is used, then the shape of the
resulting array is the same as the shape of the source array.
The expr may refer to the base array of arr-expr by
using the ?*
pseudo-constant. This is especially
useful if the base array is an expression itself.
The foreach
statement always creates a
heterogeneous List
or Array
.
Note that foreach
is an expression -
contrast it with forall
, which is a
statement. The definition of forall
can be
found in the Procedures chapter.
Example:
a = [2,3].iterate() // A single index creates a 1-D result b = foreach (a[i]) { "Row " ## (i => String) } b +-----+ +-----+ |Row 0| |Row 1| +-----+ +-----+ // Two indexes creates a 2-D result b = foreach (a[i,j]) { {i,j} } b +---+ +---+ +---+ |0 0| |0 1| |0 2| +---+ +---+ +---+ +---+ +---+ +---+ |1 0| |1 1| |1 2| +---+ +---+ +---+ // A flat index creates a result of the same shape as the source array b = foreach (a#[i]) { Char('a'+i) } b a b c d e f // Use the ?* constant to refer to the base array b = foreach ((a*10)[i,j]) { ?*[i,j] } b 0 10 20 30 40 50 // Attempting to access more elements than present in the array // throws a ShapeCheck error b = foreach(a[i,j,k]) { 1 } Inconsistent array shape
There are a variety of intrinsic methods that create arrays. For
example, two scalars may be concatentated, and the
iterate()
method may be used to create a packed
homogeneous array filled with a sequence of Int
values - a PackInt
array - starting at zero:
str = 'a' ## 'b' str ab arr = [2,3].iterate() arr 0 1 2 3 4 5
See the chapter on Intrinsic Procedures and Methods for more information on the various OADL array intrinsic methods.
Since arrays are first-class objects in OADL, they may be used in arithmetic expressions just like scalar (single-valued, non-array) values. A new array is created with its contents set to the element-by-element evaluation of the arithmetic expression.
Only values with consistent shapes can be used together in arithmetic statements. The rules regarding consistency are simple:
Note that an Enclosure
(see above ) is
considered to be a scalar. Here are some examples which illustrate
these rules:
a = [1,2,3] b = [4,5,6] a + b 5 7 9 a + 1 2 3 4 b = {4,{5,6},7} a + b 5 +---+ 10 |7 8| +---+ b = [7,8].enclose() a + b +---+ +----+ +-----+ |8 9| |9 10| |10 11| +---+ +----+ +-----+
If an attempt is made to use inconsistent shapes together in an
expression, the ShapeCheck
exception is thrown:
a = [1,2,3] b = [[4,5,6],[7,8,9]] a + b Inconsistent array shape
In expressions involving both arrays and objects with overloaded operators, the operator overloading rules are followed first, followed by the array expression rules. This allows arrays to be passed to the overloaded operator methods. For example:
class str { var sVal; public proc create(a) { sVal = a; } public proc get() { return sVal; } operator + (b) { // self is LHS of + switch (typeof(b)) { case String : return new str(sVal ## b); case Object : if (b.parent() == str) return new str(sVal ## b.get()); } throw oadl::TypeCheck; } operator \+ (a) { // self is RHS of \+ switch (typeof(a)) { case String : return new str(a ## sVal); case Object : if (a.parent() == str) return new str(a.get() ## sVal); } throw oadl::TypeCheck; } } a = new str("hello, ") {}; b = a + "world"; b.get() hello, world