Collections, Containers,
and Path Expressions
A container is simply an object that can hold a variable number of other objects. A container can be as simple as an array of numbers, or it can be a huge database of other containers. The class Container provides structure and behavior for objects that contain other objects.
Collections are a subset of containers that implement common aggregate data structures such as linked lists, arrays, and hash tables. The Collection class, the root class of the collection hierarchy of classes, inherits from Container.
Finally, path expressions are a set of ScriptX language constructs designed to operate on all the subclasses of Container, including collections. Path expressions are used to search and select objects in containers using specific criteria.
This chapter has two main sections:
Collections, as described above, are classes that implement common aggregate data structures such as arrays and linked lists. The Collection class and its subclasses define behavior for collections and provide methods for adding and removing elements in collections, as well for querying and changing those elements.
Note: This section is not an exhaustive description of all the collection classes in ScriptX. Instead, this section is intended to be an introduction to the most useful classes and the most common ways of using those classes. The ScriptX Architecture and Components Guide contains the definitive list of all the subclasses of Collection.
Figure 1. A Few of The Collection Classes
The following sections contain a short summary of many of the available ScriptX collection classes. For further details on all these classes, see the ScriptX Architecture and Components Guide or the ScriptX Core Classes Reference.
The Array array is the simplest form of the Collection classes, containing simply an indexed array of elements. LinkedList is similar to Array, except it is implemented as a linked list of elements. Arrays are generally allocated with some number of "slots," (typically ten), and grow by an incremental number of elements when they run out of slots. Linked lists are variable in size; elements are added and removed dynamically as needed. Arrays are typically faster for querying and changing elements, but linked lists are faster for insertion of elements.
Both Array and LinkedList are implicitly keyed sequences (they inherit from the abstract classes Sequence and ImplicitlyKeyedCollection), which means that their elements are accessed through their position in the list or array and not by some explicit key. Sequences begin with the ordinal position 1.
The class KeyedLinkedList is a list of explicit key-value pairs. Whereas sequences can be said to have implicit keys (their keys are their ordinal positions), instances of KeyedLinkedList have explicit keys. The values associated with those keys are usually accessed via the keys themselves.
The SortedArray and SortedKeyedArray classes provide implementations of an array and an array of key-value pairs, respectively, that are always sorted. If you add new elements to a sorted collection, they are inserted based on where they should appear in the sorting order.
Ranges are used to represent a range of numbers between two values. There are three range classes that are part of the collection hierarchy:
Single, Pair, Triple, and Quad are bounded sequences, that is, they can only contain a specific number of elements (one, two, three, and four, respectively). These classes take up less memory than a more general sequence with the same number of elements (such as an array).
ScriptX collections include two other classes which may be of interest:
Chapter 2, "ScriptX Building Blocks" described the literals in ScriptX for specifying instances of the Array and KeyedLinkedList classes. They are reproduced here for a summary:
Ranges, such as those used in the for loop examples, are a special literal that creates an instance of the appropriate subclass of Range.
In both forms, startValue is the number to begin the range, and endValue is the number to end the range.
1 to 10
1 to 56
To iterate down from one number to another, put the larger number first in the range:
10 to 1
100 to 50
The optional by increment part enables you to specify the increment or change for the range. If by is not included, the increment is 1 if startValue is lower than endValue, or -1 if startValue is the larger of the two.
1 to 10 by 2
1 to 100 by 7
0 to 1 by 0.1
10 to 1 by -3
Although both the startValue, endValue, and increment are often numbers, they can also be an expression that results in a number. Keep in mind that most complex expressions must be specified inside parentheses for those expressions to be evaluated before the range expression. The following expressions can be used without parentheses, where appropriate:
The range literal generally evaluates to an instance of the NumberRange class. There is also a literal for ContinuousNumberRange, a range of all the numbers (floating-point and integer) between the end-points of the range. Although you can test the membership of a given number in a ContinuousNumberRange, you cannot count or list its elements.
To create an instance of ContinuousNumberRange, use the following literal syntax:
The continuous reserved word indicates that the range is to be continuous. You also have the option of specifying the exclusivity of the numbers that begin and end the continuous range. There are two reserved words for exclusivity:
exclusive
inclusive
Either the startNum or the endNum, or both, can be either specified as exclusive or inclusive, where exclusive means that the range includes all numbers except that number itself, and inclusive means the range includes the number specified. The default is inclusive.
1 to 3 continuous
1 exclusive to 3 inclusive continuous
1 to 3 exclusive continuous
Note that in the last example, the exclusive reserved word only applies to the end number of the range. You must specify exclusivity separately to both ends of the range.
Just as with other objects you can use either the new method or the object expression to create a new instance of a subclass of Collection. You can specify the initial contents of a collection using the contents reserved word:
object myList (Array)
contents "penny", "nickel", "dime", "quarter"
end
print myList
#("penny", "nickel", "dime", "quarter")
Probably the easiest way to create a simple new instance of a collection with an initial set of values is to use either the array or keyed list literal (as described on page 122), and then coerce the resulting Array or KeyedLinkedList object into an instance of the appropriate class. See "Coercing Between Collection Classes," next, for examples.
Colections, like other objects, can be coerced into other collection classes by using the as reserved word with the new collection class.
In this syntax, oldcollection is an expression yielding the original collection, and newcollection is the class of the collection to which it is coerced (or an expression resulting in a class).
col := #(4,2,1,3)
getClassName col
Array
col as SortedArray
#(1,2,3,4) as SortedArray
col2 := col as KeyedLinkedList
#(4:4,2:2,1:1,3:3)
col2 as LinkedList
#(4,2,1,3) as LinkedList
The return values in the above examples are shown as if they had been printed using a function such as prin. The printable representations of most of the collection classes always refer to the class of the object as if that object had been coerced, even if it was originally created as an instance of that class. This is so that the printable representation of a collection class is the same as the syntax for creating that collection in a script. "Printing Collections" on page 129 for more information.
Coercing between subclasses of Collection that are not of similar appearance may have unusual side effects. Table 1 describes the effect of many of these coercions.
Table 1. Coercions Between Collection Classes
| Type of Original Collection | Type of New Collection | Effect of Coercion |
|---|---|---|
| Sequences such as: Array LinkedList |
Explicitly keyed collections such as: KeyedLinkedList |
The elements in the original collection become both the key and the value in the pair (that is, the keys and the values in each are the same in the new collection). #(1,2) as KeyedLinkedList |
| Explicitly keyed collections such as: KeyedLinkedList |
Sequences such as: Array LinkedList |
Only the values in the original key-value pairs are retained. The order of the elements in the new collection may not be the same as in the original collection. #(1:"one",2:"two") as LinkedList |
| Unsorted collections such as: Array KeyedLinkedList |
Sorted collections such as: SortedKeyedArray SortedArray |
The elements in the original collection are sorted. #(4,3,1,2) as SortedArray |
| Unbounded collections such as: Array KeyedLinkedList |
Bounded collections such as: Single Pair |
If the unbounded collection contains more elements than the bounded collection can hold, only the initial elements are included in the new collection. All others are discarded. If there are too few elements in the unbounded collection, the remaining elements are set to undefined. #(1,2,3,4) as Pair |
To access an element in a collection, use a method such as those described in the next section, or use the collection indexing construct (which is actually syntactic shorthand for the getOne method):
Using this syntax, collection is the instance of the collection class (or an expression resulting in a collection), and index is either the position of the element in a sequence or the key of the element in a keyed collection.
melons := #("honeydew","cantaloupe","water","carnegie")
melons[1]
"honeydew"
melons[3]
"water"
r :=#(@one:"money",@two:"show",@three:"get ready",@four:"go!")
r[@two]
"show"
To change elements in a collection, use the same syntax as above, followed by an assignment. This form of element access is syntactic shorthand for the setOne method:
For example:
melons := #("honeydew","cantaloupe","water","carnegie")
melons[3] := "cassava"
print melons
#("honeydew","cantaloupe","cassava","carnegie")
melons[5] := "water"
print melons
#("honeydew","cantaloupe","cassava","carnegie",
"water")
r := #(@one:"money",@two:"show",@three:"get ready",@four:"go!")
r[@four] := "pause"
"show"
This section contains a partial list of methods available for collection object. See the ScriptX Core Classes Reference for further details and a more complete listing.
Many of the methods described in the sections below may return empty if the element or key you specify is not contained in the collection. The empty object is a special global constant used by the collection classes to mean "item not found" and should not be confused with undefined.
Putting the empty object into a collection as an element itself is not recommended, as it becomes difficult to tell whether a method is returning the fact that the specified element was not found, or that it was found, but it was the empty object. Use undefined instead of empty as a placeholder for elements with no value.
The getOne method gets the value given by the first instance of key in the collection self; getAll gets all the values associated with the key key. The getNth method, available only on collections that refer to an element's position as its key, gets the value at the ordinal position ordinal, and is the specialized form of getOne for collections with implicit keys. Keep in mind that ScriptX sequences begin at position 1.
The getOne method is equivalent to the expression self[key].
getOrdOne self value
The getOrdOne method is available only on collections that refer to an element's position as its key (that is, subclasses of the abstract collection LinearCollection). It returns the position at which the first instance of value is located in the collection self.
getKeyOne self value
getKeyAll self value
The getKeyOne and getKeyAll methods act like getOrdOne on explicitly keyed collections such as KeyedLinkedList; given the value value they return the first instance in the collection self of a key with that value (getKeyOne) or all instances of keys with that value (getKeyAll).
add self key value
The add method inserts the key-value pair specified by key and value into the collection self. For sequences such as arrays or lists, key is the position in the list.
addMany self another
The addMany method appends all the values (or key-value pairs) in the collection another to the collection specified by self.
append self value
prepend self value
The append method inserts the value value at the end of the collection self; prepend inserts it at the beginning.
deleteOne self value
deleteAll self value
deleteNth self ordinal
The deleteOne method deletes the first instance of the value value in the collection self; deleteAll deletes all instances of value from self. With collections that have explicit key-value pairs, both the keys and the values are deleted. The deleteNth method, available only on collections that refer to an element's position as its key, deletes the instance of the element or at the ordinal position ordinal.
deleteKeyOne self key
deleteKeyAll self key
The deleteKeyOne method deletes the first instance of the key-value pair specified by key in the collection self. The deleteKeyAll method deletes all instances of that key and its values.
setOne collection key value
setAll collection key value
The setOnemethod changes the binding of the first instance of key to the value value in the collection self. If key does not exist, the key-value pair specified by key and value is added to the collection. The setAll method is equivalent to setOne, except that for all instances of key the associated values are changed to value.
On sequences such as arrays, key is the position of the element in the collection self, and cannot be greater than the current length of the collection plus one (that is, it can only be added to the end of the sequence if it does not exist in the collection).
The setOne method is equivalent to the expression self[key] := value.
collection.size
The size instance variable contains the number of elements in the collection. It is a virtual instance variable that is automatically calculated by its getter method; you cannot change it yourself.
isEmpty self
The isEmpty method tests whether the collection self is empty. It returns either true or false.
isMember self value
The isMember method tests whether the element specified by value is in the given collection, where value is the value of an explicitly keyed list, or an element in a sequence such as an array.
The isMember method is equivalent to the expression self contains value.
[remainder of chapter deleted]