TOC PREV NEXT

4  Composite Data Types

4.1  Arrays

Arrays are specified with curly brackets, e.g., "{1, 2, 3}" is an array of int, while
"{"x", "y", "z"}" is an array of string. The types are denoted "{int}" and "{string}" respectively. An array is an ordered list of tokens of any type, with the only constraint being that the elements all have the same type. If an array is given with mixed types, the expression evaluator will attempt to losslessly convert the elements to a common type. Thus, for example,
{1, 2.3}

has value
{1.0, 2.3}

Its type is {{double}}. The common type might be scalar, which is a union type (a type that can contain multiple distinct types). For example,
{1, 2.3, true}

has value
{1, 2.3, true}

The value is unchanged, although the type of the array is now scalar.
The elements of the array can be given by expressions, as in the example "{2*pi, 3*pi}." Arrays can be nested; for example, "{{1, 2}, {3, 4, 5}}" is an array of arrays of integers. The elements of an array can be accessed as follows:
>> {1.0, 2.3}(1)
2.3

which yields 2.3. Note that indexing begins at 0. Of course, if name is the name of a variable in scope whose value is an array, then its elements may be accessed similarly, as shown in this example:
>> x = {1.0, 2.3}
{1.0, 2.3}
>> x(0)
1.0

Arithmetic operations on arrays are carried out element-by-element, as shown by the following examples:
>> {1, 2}*{2, 2}
{2, 4}
>> {1, 2}+{2, 2}
{3, 4}
>> {1, 2}-{2, 2}
{-1, 0}
>> {1, 2}\^2
{1, 4}
>> {1, 2}\%{2, 2}
{1, 0}

Addition, subtraction, multiplication, division, and modulo of arrays by scalars is also supported, as in the following examples:
>> {1.0, 2.0} / 2.0
{0.5, 1.0}
>> 1.0 / {2.0, 4.0}
{0.5, 0.25}
>> 3 *{2, 3}
{6, 9}
>> 12 / {3, 4}
{4, 3}

Arrays of length 1 are equivalent to scalars, as illustrated below:
>> {1.0, 2.0} / {2.0}
{0.5, 1.0}
>> {1.0} / {2.0, 4.0}
{0.5, 0.25}
>> {3} * {2, 3}
{6, 9}
>> {12} / {3, 4}
{4, 3}

A significant subtlety arises when using nested arrays. Note the following example
>> {{1.0, 2.0}, {3.0, 1.0}} / {0.5, 2.0}
{{2.0, 4.0}, {1.5, 0.5}}

In this example, the left argument of the divide is an array with two elements, and the right argument is also an array with two elements. The divide is thus elementwise. However, each division is the division of an array by a scalar. An array can be checked for equality with another array as follows:
>> {1, 2}=={2, 2}
false
>> {1, 2}!={2, 2}
true

For other comparisons of arrays, use the compare() function (see Table 10). As with scalars, testing for equality using the == or != operators tests the values, independent of type. For example,
>> {1, 2}=={1.0, 2.0}
true

You can extract a subarray by invoking the subarray() method as follows:
>> {1, 2, 3, 4}.subarray(2, 2)
{3, 4}

The first argument is the starting index of the subarray, and the second argument is the length.
You can also extract non-contiguous elements from an array using the extract() method. This method has two forms. The first form takes a boolean array of the same length as the original array which indicates which elements to extract, as in the following example:
>> {``red'',``green'',``blue''}.extract({true,false,true})
{``red'', ``blue''}

The second form takes an array of integers giving the indices to extract, as in the following example:
>> {``red'',``green'',``blue''}.extract({2,0,1,1})
{``blue'', ``red'', ``green'', ``green''}

You can create an empty array with a specific element type using the emptyArray() function. For example, to create an empty array of integers, use:
>> emptyArray(int)
{}

You can combine arrays into a single array using the concatenate() function. For example,
>> concatenate({1, 2}, {3})
{1, 2, 3}

4.2  Matrices

In Ptolemy II, arrays are ordered sets of tokens. Ptolemy II also supports matrices, which are more specialized than arrays. They contain only certain primitive types, currently boolean, complex, double, fixedpoint, int, and long. Currently float, short and unsignedByte matrices are not supported. Matrices cannot contain arbitrary tokens, so they cannot, for example, contain matrices. They are intended for data intensive computations. Matrices are specified with square brackets, using commas to separate row elements and semicolons to separate rows. E.g., "[1, 2, 3; 4, 5, 5+1]" gives a two by three integer matrix (2 rows and 3 columns). Note that an array or matrix element can be given by an expression. A row vector can be given as "[1, 2, 3]" and a column vector as "[1; 2; 3]". Some MATLAB-style array constructors are supported. For example, "[1:2:9]" gives an array of odd numbers from 1 to 9, and is equivalent to "[1, 3, 5, 7, 9]." Similarly, "[1:2:9; 2:2:10]" is equivalent to "[1, 3, 5, 7, 9; 2, 4, 6, 8, 10]." In the syntax "[p:q:r]", p is the first element, q is the step between elements, and r is an upper bound on the last element. That is, the matrix will not contain an element larger than r. If a matrix with mixed types is specified, then the elements will be converted to a common type, if possible. Thus, for example, "[1.0, 1]" is equivalent to "[1.0, 1.0]," but "[1.0, 1L]" is illegal (because there is no common type to which both elements can be converted losslessly).
Reference to elements of matrices have the form "matrix(n, m)" or "name(n, m)" where name is the name of a matrix variable in scope, n is the row index, and m is the column index. Index numbers start with zero, as in Java, not 1, as in MATLAB. For example,
>> [1, 2; 3, 4](0,0)
1
>> a = [1, 2; 3, 4]
[1, 2; 3, 4]
>> a(1,1)
4

Matrix multiplication works as expected. For example, as seen in the expression evaluator (see figure 1),
>> [1, 2; 3, 4]*[2, 2; 2, 2]
[6, 6; 14, 14]

Of course, if the dimensions of the matrix don't match, then you will get an error message. To do element wise multiplication, use the multipyElements() function (see Table 10). Matrix addition and subtraction are element wise, as expected, but the division operator is not supported. Element wise division can be accomplished with the divideElements() function, and multiplication by a matrix inverse can be accomplished using the inverse() function (see Table 10). A matrix can be raised to an int, short or unsignedByte power, which is equivalent to multiplying it by itself some number of times. For instance,
>> [3, 0; 0, 3]\^3
[27, 0; 0, 27]

A matrix can also be multiplied or divided by a scalar, as follows:
>> [3, 0; 0, 3]*3
[9, 0; 0, 9]

A matrix can be added to a scalar. It can also be subtracted from a scalar, or have a scalar subtracted from it. For instance,
>> 1-[3, 0; 0, 3]
[-2, 1; 1, -2]

A matrix can be checked for equality with another matrix as follows:
>> [3, 0; 0, 3]!=[3, 0; 0, 6]
true
>> [3, 0; 0, 3]==[3, 0; 0, 3]
true

For other comparisons of matrices, use the compare() function (see Table 10). As with scalars, testing for equality using the == or != operators tests the values, independent of type. For example,
>> [1, 2]==[1.0, 2.0]
true

To get type-specific equality tests, use the equals() method, as in the following examples:
>> [1, 2].equals([1.0, 2.0])
false
>> [1.0, 2.0].equals([1.0, 2.0])
true

4.3  Records

A record token is a composite type containing named fields, where each field has a value. The value of each field can have a distinct type. Records are delimited by curly braces, with each field given a name. For example, "{a=1, b=''foo''}" is a record with two fields, named "a" and "b", with values 1 (an integer) and "foo" (a string), respectively. The value of a field can be an arbitrary expression, and records can be nested (a field of a record token may be a record token).
Ordered records behave similarly to normal records except that they preserve the original ordering of the labels rather than alphabetizing them. Ordered records are delimited using square brackets rather than curly braces. For example, [b="foo", a=1] is an ordered record token in which 'b' will remain the first label.
Fields may be accessed using the period operator. For example,
{a=1,b=2}.a

yields 1. You can optionally write this as if it were a method call:
{a=1,b=2}.a()

The arithmetic operators +, -, *, /, and % can be applied to records. If the records do not have identical fields, then the operator is applied only to the fields that match, and the result contains only the fields that match. Thus, for example,
{foodCost=40, hotelCost=100} + {foodCost=20, taxiCost=20}

yields the result
{foodCost=60}

You can think of an operation as a set intersection, where the operation specifies how to merge the values of the intersecting fields. You can also form an intersection without applying an operation. In this case, using the intersect() function, you form a record that has only the common fields of two specified records, with the values taken from the first record. For example,
>> intersect({a=1, c=2}, {a=3, b=4})
{a=1}

Records can be joined (think of a set union) without any operation being applied by using the merge() function. This function takes two arguments, both of which are record tokens. If the two record tokens have common fields, then the field value from the first record is used. For example,
merge({a=1, b=2}, {a=3, c=3})

yields the result {a=1, b=2, c=3}.
Records can be compared, as in the following examples:
>> {a=1, b=2}!={a=1, b=2}
false
>> {a=1, b=2}!={a=1, c=2}
true

Note that two records are equal only if they have the same field labels and the values match. As with scalars, the values match irrespective of type. For example:
>> {a=1, b=2}=={a=1.0, b=2.0+0.0i}
true

The order of the fields is irrelevant for normal (unordered) records. Hence
>> {a=1, b=2}=={b=2, a=1}
true

Moreover, normal record fields are reported in alphabetical order, irrespective of the order in which they are defined. For example,
>> {b=2, a=1}
{a=1, b=2}

Equality comparisons for ordered records respect the original order of the fields. For example,
>> [a=1, b=2]==[b=2, a=1]
false

Additionally, ordered record fields are always reported in the order in which they are defined. For example,
>> [b=2, a=1]
[b=2, a=1]

To get type-specific equality tests, use the equals() method, as in the following examples:
>> {a=1, b=2}.equals({a=1.0, b=2.0+0.0i})
false
>> {a=1, b=2}.equals({b=2, a=1})
true

Finally, You can create an empty record using the emptyRecord() function:
>> emptyRecord()
{}

TOC PREV NEXT