List:
A list
stores objects of all the same type in a sequential order. The elements can be
any type specified in the Types base enum. Elements may be added to the
beginning or end of the list. A summary of useful methods is shown in the table
below:
addEnd(_item)
|
Adds
_item to the end of the list
|
addStart(_item)
|
Adds
_item to the beginning of the list
|
appendList(_list)
|
Adds
elements _list to the end of current list
|
elements
|
Returns
the number of items in the list
|
getEnumerator
|
Returns
an enumerator for traversing the list
|
The
example code below shows creating two different lists, combining, and
traversing.
public void listExample() {
List listOne;
List listTwo;
List combinedList;
ListEnumerator listEnumerator;
SalesTable salesTable;
// Initialize using Types enum
listOne = new List(Types::String);
// Initialize using extendedTypeId2Type which does not require
// knowing the base type
listTwo = new List(extendedTypeId2Type(extendedTypeNum(SalesId)));
// Add a and b to list such that order is b,a
listOne.addEnd("a");
listOne.addStart("b");
// Add an actual sales id and dummy sales id
select firstOnly SalesId from salesTable;
listTwo.addEnd(SalesTable.SalesId);
listTwo.addEnd("Dummy_Sales_Id_12345");
// Print first list
listEnumerator = listOne.getEnumerator();
info("List one");
while (listEnumerator.moveNext()) {
info(listEnumerator.current());
}
// Create a new list by combining lists
combinedList = List::merge(listTwo, listOne);
// Print combined list
info("Combined");
listEnumerator = combinedList.getEnumerator();
while (listEnumerator.moveNext()) {
info(listEnumerator.current());
}
}
Below is
the output produced by the above code:
Set
A set is
an unordered collection that contains elements of all the same type and is
similar to a mathematical set in that each element must be unique. Again, the
types are all the possible types of the Types base enum. Repeated inserts of a
value that is already in the set will have no effect. There will be no record
of how many such inserts of a duplicate value occurred. For example, if a set
of Types::Integer contains only the integer 2, repeated calls to the add method
with 2 as a parameter will have no effect. No matter how many such calls are
made, the set will still only contain the integer 2. One possible use of a set
is to store RecIds to ignore any duplicate records and insure a process only
acts once per record.
Unlike a
list, an element can be removed from a set directly without using an iterator.
There are also powerful methods that mimic mathematical set operations that
allow creation of new sets based on two existing methods. A summary of useful
methods is shown in the table below:
add(_item)
|
Adds
_item to the set
|
delete(_item)
|
Removes
the passed in item to the set
|
elements
|
Returns
the number of items in the set
|
getEnumerator
|
Returns
an enumerator for traversing the set
|
in(_item)
|
Returns
true if _item is present in the set
|
difference(_set1,
_set2)
|
Returns
items that are in _set1 but are not in _set2
|
intersection(_set1,
_set2)
|
Returns
a new set that contains only elements present in both _set1 and _set2
|
union(_set1,
_set2)
|
Returns
a new set that contains all the elements of _set1 and _set2.
|
The
diagram below illustrates the results of set based operations which are
equivalent to the mathematical operations.
The code
below shows an example of creating, filling, and traversing sets:
public void setExample() {
Set setOne;
Set setTwo;
// Inner method for printing sets
void printSet(Set _set, str _prefix) {
int i;
SetEnumerator setEnumerator;
str output = _prefix;
setEnumerator = _set.getEnumerator();
// Add each element to output string
while (setEnumerator.moveNext()) {
output += strfmt(" %1", setEnumerator.current());
}
info(output);
}
// Declare sets
setOne = new Set(Types::String);
setTwo = new Set(Types::String);
// Fill sets
setOne.add("A");
setOne.add("B");
setTwo.add("B");
setTwo.add("C");
// Size of set
info(strfmt("Size of set one: %1", setOne.elements()));
// Print sets and the results of set operations
printSet(setOne, "Set 1");
printSet(setTwo, "Set 2");
printSet(Set::difference(setOne, setTwo), "Difference ");
printSet(Set::intersection(setOne, setTwo), "Intersection ");
printSet(Set::union(setOne, setTwo), "Union");
}
The
output of the above code is:
Map
A map
stores key-value pairs. For each key, there is a corresponding value. Accessing
the corresponding value of a key is a fast operation. The keys are unique and
are all of the same type. The values do not have to be unique and also are all
the same type. As with the other collection classes, the possible types are
those specified by the Types base enum. Note that the key types and value types
do not need to be the same and typically are not.
Some
examples of maps are keys that are RecIds or strings that correspond to a
numeric value. Another example is mapping RecIds from one table to a Record of
another table, thus quickly and easily creating a fast performing link between
two tables without having to use a temp table. The diagram below shows a map
with Int64 keys mapping to reals.
Useful
map methods are summarized in the table below:
delete(_key)
|
Deletes
_key (and thereby its corresponding value) from the map
|
elements
|
Returns
the number of key-value pairs in the map.
|
exists(_key)
|
Returns
true if _key is present in the map.
|
insert(_key,
_value)
|
Inserts
the key value pair with a key of _key and a corresponding value of _value. In
other words, lookups for _key will return _value.If _key already existed in
the map, the corresponding value of that key is overwritten such that the new
mapped value is _value.
|
lookup(_key)
|
Returns
the corresponding value of _key.
|
One important note is that calling the lookup method for a key
that does not exist in the map will cause a runtime error. Therefore, the exists method
should always be called first to verify that the key exists.
The MapEnumerator class traverses a map. The key and value are
retrieved from the enumerator using the currentKey and currentValue methods.
The code
below shows examples of several of the map methods including using an
enumerator:
public void mapExample() {
Map theMap;
MapEnumerator mapEnumerator;
real value;
// Declare map
theMap = new Map(Types::String, Types::Real);
// Insert values into the map
theMap.insert("LOT-01", 42.5);
theMap.insert("LOT-02", 58.3);
// Insert new value for LOT-01. Previous value overwritten.
theMap.insert("LOT-01", 99.9);
// Traverse the map
mapEnumerator = theMap.getEnumerator();
while (mapEnumerator.moveNext()) {
info(strfmt("key %1 value %2",
mapEnumerator.currentKey(),
mapEnumerator.currentValue()));
}
// Check if key exists
info(strfmt("Does LOT-01 exist? %1", theMap.exists("LOT-01")));
info("Checking if LOT-03 exists");
if (theMap.exists("LOT-03")) {
// Will not execute this code, so there is no error
value = theMap.lookup("LOT-03");
}
// Will cause run time error: Lookup a key that is not in the map without
// first calling exists
value = theMap.lookup("LOT-03");
value = 1;
}
The code
above produces the following output:
Struct
A struct groups
information into a single entity. Any of the types of the Types enum can be
stored which includes containers, dates, Int64’s, integers, objects, real
numbers, records, strings, times, and UTCDateTimes. The diagram of the struct
named “custInfo” shows an example struct that collects a string, real number,
record, and object into a single entity.
The
struct class is one of the simplest collection classes. Using a struct is
similar to using a container that has a small number of values at fixed
positions (such as the TradeTotals class). One major difference is that a
struct is a class while a container is a built-in data type. A struct uses
string indexes whereas a container uses numeric indexes. One of the problems
with both is that the indexes or keys need to be managed entirely by the
developer. For example, in the struct illustrated above, the value “43.58”
needs to be accessed by providing the string “invoiced” exactly. Using the
string “invoice” will cause a run time error because no value exists for the
key “invoice”. For both structs and containers, one way to manage this issue is
to access the keys/indexes using #define or methods.
The code
below uses a constant for the invoiced but not the other keys. The code shows
how to create and fill the struct illustrated above as well as how to iterate
through a struct.
public void structExample() {#
define.invoiced('invoiced')
Struct theStruct;
SalesTable salesTable;
int i;
// Create struct
theStruct = new Struct(
Types::String, "name",
Types::Real, #invoiced,
Types::Record, "order");
// Fill struct
theStruct.value("name", "John Smith");
theStruct.value(#invoiced, 43.58);
select firstOnly salesTable;
theStruct.value("order", salesTable);
// Add a new entry into the struct
theStruct.add("orderType", SalesTableType::construct(theStruct.value("order")));
// Print struct information
info(strFmt("Definition string: %1", theStruct.definitionString()));
for (i = 1; i <= theStruct.fields(); i++) {
info(strFmt("Field name: %1 Type: %2",
theStruct.fieldType(i),
theStruct.fieldName(i)));
}
// Using exists for a key
info(strFmt("Exists for key 'xyz'? %1", theStruct.exists("xyz")));
}
Below is
the output produced by the above code:
The
sections below outline aspects of collection classes that may apply to more
than one collection class.
Iterators vs. Enumerators
Enumerators
should always be used to traverse a list, set, or map unless elements need to
be deleted. Iterators can fail if they are used on a different tier than the
tier where they were created. For example, code will fail if an iterator was
created on the client side, but then runs on the server side or vice versa.
Also, loop maintenance is easier when using an enumerator since there is no
need to advance the iterator as a separate step.
while(iterator.more()) while (enumerator.moveNext())
{ {
iterator.next(); }
}
If the
client/server problem can be safely avoided, then iterators can be used for
deletion. However, there are alternative solutions (potentially expensive) to
deleting elements from a collection without using an iterator. For a set, one
possibility is putting elements to delete in a second set during traversal and
calling the static intersection method. For a map, one could put keys to be
deleted in a set, then traverse the set, deleting each key individually.
Pack and Create
All the
collection classes mentioned above can be packed into a container using the
pack method. A new instance of these classes can then be created using the
appropriate static create method.
public void packCreateExample() {
List aList = new List(Types::Real);
List aNewList;
container c;
// Fill list
aList.addEnd(1.61803);
aList.addEnd(3.14159);
// Pack the list into a container
c = aList.pack();
// Create a new list from the packed list
aNewList = List::create(c);
}
Uniqueness of Tables and Classes
Special
care should be taken if tables or classes need to be unique within the
collection classes. This includes elements in a set or keys of a map. If you
use a table as a unique key to a map, it seems that RecId is not the field used
to determine uniqueness. Instead, non-system fields are checked. One way to
bypass this is to add a GUID field to the table (thanks to Justin Wong for
pointing this out). For classes it seems that even if two objects have the same
values for variables, the objects will be determined to be unique by the
collection classes.
Sorting Behavior of Sets and Maps
No matter
what order elements are added to a set or keys are added to a map, when using
an enumerator to traverse the set or map, the elements are in a sorted order
(for string and numeric types). However, the MSDN documentation for the set
class states that elements are stored in a set “in a way that facilitates
efficient lookup of the elements.” It might not be safe, therefore, to rely on
this sorting behavior as it might possibly change in the future.
The code
below shows the sorting behavior in a set of reals:
public void setSortingExample() {
Set set = new Set(Types::Real);
SetEnumerator setEnumerator;
set.add(2.3);
set.add(3.8);
set.add(1.2);
setEnumerator = set.getEnumerator();
while (setEnumerator.moveNext()) {
info(setEnumerator.current());
}
}
The
result of this code is shown below: