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()
{}