Arrays, Lists, and Strings

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

Array indexes

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

Array properties

Various properties of arrays can be queried with the following intrinsic methods:

arr.shape()
Returns the shape (list of dimensions) of an array
arr.rank()
Returns the dimensionality of an array
arr.length()
Returns the number of elements in the first dimension of an array
arr.width()
Returns the number of elements in the last dimension of an array
arr.sizeof()
Returns the total number of elements in an array
arr.stride()
Returns the "stride" of each index of an array
arr.arrbase()
Returns the base type of an array, or Array for a heterogeneous array
arr.readonly()
Returns whether an array or object is read-only (constant) or changeable
arr.transient()
Returns whether an array or object is dynamically managed (transient) or permanent

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

Enclosures

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|
+--------+ +--------+ +--------+ +--------+


Array creation

Arrays can be created in several ways. These include:

Constant Strings

Strings 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!";

Bracket syntax (packed arrays)

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]

Brace syntax (lists)

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.|
+--------+ +---------+

Array brace syntax (unpacked arrays)

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

Iterator syntax

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

Array new syntax

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

Array creation with "foreach"

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

Array creation intrinsics

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.

Array-valued expressions

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

Back to OADL Types

Continue to Dictionaries

Return to Introduction