It's always been important to group related pieces of data. From the start, we had arrays. Then we had User-Defined Types. While they're good and useful, they both have a fairly strict structure. A fixed number of members. A fixed data type. Of course, you can REDIM an array, but you need to constantly monitor the content, so you don't inadvertantly exceed the legal bounds. One slipup and your program is "toast".
Is there a better way? Perhaps. With the latest versions of our PoweerBASIC compiler, we now have Collections. A very important addition to your arsenal of programming tools. While collections won't replace arrays or UDTs, they may offer a much better solution for certain types of data groups.
Collections are implemented as an object. You create an object, then use the built-in methods to add items, delete items, retrieve items, or perhaps just modify some of them as your program executes. You can store string items, numeric items, object items, and more. The collection object contains them all, and helps you retrieve them later. There are no artificial limits to force bounds errors. It's just a very flexible structure to hold your important data.
Compared to an array, you have a huge variety of options. Arrays work well when the individual elements correspond to a numeric value. When MONTH$(1) = "January", the relation is obvious and easy to use. But it's not so straightforward when you're trying to find the element which corresponds to a particular email address. You can't just reference Customer$(john@hotmail). You can't easily find the oldest element to implement a queue. And a real stack? That's not so obvious, either.
These are the areas where collections shine! Stay with me a minute more and see what we can find!
A collection object contains a set of data items. That lets you refer to them as a unit, or refer to the member items individually. The items can be almost any data type, and you control the way in which they can be retrieved.
You create a collection object the same way you create any other object. You simply use a predefined internal class and internal interface.
LOCAL Collect AS IPowerCollection LET Collect = CLASS "PowerCollection"
Once you have created a collection object, you can manipulate it using the member methods. Internally, each data item in the set is stored as a VARIANT variable, which may contain any valid data type (numeric, string, UDT, object, etc.). All PowerBASIC collection interfaces are DUAL. The methods may be referenced using either Direct or Dispatch form.
While the collection object expects to receive your data items as variant variables, you can take advantage of the auto-conversion options in PowerBASIC. If a Variant parameter is expected, and you pass a single variable instead, PowerBASIC will automatically convert it to Variant with no intervention needed on your part.
Very often, it's convenient to create a collection of user defined types (UDT). While a variant may not normally contain a UDT, PowerBASIC offers a special methodology to do so. At programmer direction, a TYPE may be assigned to a variant (as a byte string) by using:
[LET] VrntVar = TypeVar AS STRING
You can even do this auto-conversion as part of the method call. For example, you add a keyed item to a collection by using the ADD method. It expects two parameters, a key string to be used for retrieval, and the data item contained in a Variant. There's no need for a 2-step process to convert and then add a UDT. The compiler can auto-convert it for you by just appending the words AS STRING:
CollObject.ADD( Key$$, UDTVar AS STRING )
The data contained in the User-Defined Type variable (UDTVar) is stored in the variant argument as a dynamic string of bytes (vt_bstr). When the collection object retrieves that UDT data, it understands the content and handles it accurately. This special technique offers ease of coding and much improved speed. If you like, you can use the same sort of functionality in your own PowerBASIC code. However, you should keep in mind that other programming languages may not understand this technique, so it should be limited to PowerBASIC applications.
PowerBASIC offers four types of collections: Keyed, LinkList, Stack, and Queue. Everything you need to use them is built right into the compiler. Each of them is optimized for a particular purpose, so the supported methods are unique to each class. Power Collection
Probably the most powerful and most frequently used collection. Each data item is stored with an alpha-numeric key which can be used to retrieve the data item later. The data item is stored as a variant, while the key is stored as a wide (unicode) string. You can retrieve these data items directly by using their key, by using their position in the collection, or sequentially in ascending or descending sequence.
The important point here is that each data item is associated with an alphanumeric key. Perhaps you want to keep a list of every member of the forums on your web site. A convenient key might be the email address of the forum member, while the data could be a UDT with all of his personal information.
CollObject.ADD( "jim@hotmail", UDTVar1 AS STRING ) CollObject.ADD( "bob@hotmail", UDTVar2 AS STRING )
... vrnt1 = CollObject.ITEM("jim@hotmail") UDTVar3 = Variant$(BYTE, vrnt1)
Lines 1 and 2 show how you might add two data items to the Power Collection. Later, when you want to see Jim's personal information, line 3 retrieves it to the variant named vrnt1. Line 4 extracts the UDT, assigning it to UDTVar3.
Other methods include Clear, Contains, Count, Entry, First, Index, Item, Last, Next, Previous, Remove, Replace, and Sort.
A LinkList Collection is actually just a simplified form of the Power Collection. It's an ordered set of data items which are accessed by their position in the list rather than by a key. Each data item is passed and stored as a variant variable. You can retrieve the data items by their position number, or sequentially, in ascending or descending sequence.
A LinkList Collection is perfect for maintaining a group of data items which will grow or shrink over time. A good example might be a list of shipments of your products, or subscribers to your magazine.
CollObject.ADD(MyUDT) adds an item to the end of the LinkList, while CollObject.INSERT(Index&, MyUDT) inserts it at a specific position in the list. The FIRST or LAST method can be used to set the read index to the beginning or end, while the INDEX method sets it to a specific location. At that point, you can use NEXT or PREVIOUS to retrieve data items sequentially.
Other methods include Clear, Count, Item, Remove, and Replace.
A Stack Collection is an ordered set of data items, which are accessed on a LIFO (Last-In / First-Out) basis. This collection follows the same algorithm as the machine stack on your Intel CPU. Each data item is passed and stored as a variant variable, using the PUSH and POP methods. The following example shows how you might use a Stack Collection to calulate 2 + 4 * 8 in Reverse Polish Notation.
CollObject.PUSH(2) CollObject.PUSH(4) CollObject.PUSH(8) vrnt1 = CollObject.POP : vrnt2 = CollObject.POP CollObject.PUSH( Variant#(vrnt1) * Variant#(vrnt2) ) vrnt1 = CollObject.POP : vrnt2 = CollObject.POP CollObject.PUSH( Variant#(vrnt1) + Variant#(vrnt2) )
In lines 1, 2, 3, the three numeric values are pushed, so that stack contains <2 4 8>. In line 4, the top two items (the last ones pushed) are popped. In line 5, they are multiplied, and the result is pushed, so that the stack contains <2 32>. In line 6, the top two items are popped. In line 7, they are added and the result is pushed. The stack contains a final value of 34.
Other methods include Clear and Count.
A Queue Collection is an ordered set of data items, which are accessed on a FIFO (First-In / First-Out) basis. Each data item is passed and stored as a variant variable, using the ENQUEUE and DEQUEUE methods.
A good example of a queue is the Windows Messaging system for GUI programs. In this case, messages may accumulate faster than they can be executed. A queue stores these messages in the sequence they were receive by executing the CollObject.ENQUEUE method with the data about each message. When there is time to act upon these message, the CollObject.DEQUEUE method is called, and the oldest data is retrieved. This is repeated until the QUEUE Collection object is empty.
Other methods include Clear and Count.
As you can imagine, collections are often examined sequentially. Of course, you can use the NEXT and PREVIOUS methods, but there is a better way! FOR EACH Loops.
FOR EACH VariantVar IN CollectionObjectVar
The FOR EACH loop allows you to examine each member of a collection in sequence, to perform most any operation with that data. When NEXT is reached, the next member item is assigned to the VariantVar, and the loop is repeated. This repetition continues until there are no more member items.