Vision Class: IndexedList
Overview
The IndexedList class is an indirect subclass of the Collection class which is also a superclass of the classes List and TimeSeries. Instances of the class IndexedList consist of a collection of objects that are accessed either by a user-defined index or as a set. An IndexedList is updated by adding index-object pairs.
The IndexedList class has been optimized to organize and query large sets of data. A large number of the messages defined for this class have been written in Vision and can therefore be modified and expanded as needed. As always, you can define any number of new messages for the class.
The IndexedList class is a direct subclass of IndexedCollection:
Object | Function | EnumeratedFunction | Collection | IndexedCollection | |-- IndexedList | |-- TimeSeries
IndexedList Basics
An IndexedList is used when you need to reference specific elements in a Collection by something other than position. You add elements to an IndexedList by supplying an object to be used as the index and an object to be used as the value. The index can be any object except a String. The value can be any object. The index and value can be the same object.
You use the new message to create a new IndexedList object:
!ilist <- IndexedList new ; ilist count printNL ;Initially an IndexedList contains 0 elements. The at:put: message is used to update this list:
ilist at: -100 put: 0 ; ilist at: -50 put: 10 ; ilist at: 10 put: 20 ;
The variable ilist now refers to an IndexedList containing 3 elements. The at: message is used to access a specific element in this list. The supplied parameter is the object representing the index associated with the object you wish to access. If there is no object associated with that index, the NA value is returned. For example, the expression:
ilist at: 10 .returns the value 20 and the expression:
ilist at: 3 .returns the value NA.
The delete: message is used to delete an index from the list. For example, the expression:
ilist delete: -50 .deletes this element from the list, leaving a list of 2 elements. You can display these elements using:
ilist do: [ printNL ] ;
The index value need not be numeric. For example, you could create an IndexedList to override some currency exchange rates using:
#-- Create new IndexedList to hold overrides !currencyOverrides <- IndexedList new ; #-- Define overrides for specific Currencies currencyOverrides at: Named Currency USD put: 3.1 ; currencyOverrides at: Named Currency CAD put: 5.2 ; #-- Display value for USD currencyOverrides at: Named Currency USD . printNL ; #-- Display override for currency if it exists, #-- exchange rate otherwise Currency instanceList do: [ !rate <- ^global currencyOverrides at: ^self ; code print: 10 ; rate else: usExchange . print ; rate isntNA ifTrue: [ " (override) " print ] ; newLine print ; ] ;Any object can be used as an index. The object used with the at: message to access a value must be identical to the object used as the index with the at:put: message. Instances of the String class and objects that contain extensions are not usually good index values and are a common source of programming error (see below).
Since the IndexedList class is a subclass of Collection, messages such as do:, send:, select:, groupedBy:, sortUp:, decileDown:, and extendBy: are available to IndexedList objects as well.
The toList message can be used to convert an IndexedList into a standard List object. For example:
currencyOverrides at: 1 .returns NA since 1 has not been defined as an index in this IndexedList. The expression:
currencyOverrides toList at: 1 .returns the value 3.1 which is the first value positionally after the IndexedList has been converted to a List.
Collection Message Summary
Since the IndexedList class is a subclass of Collection, all messages defined for the Collection class are available to IndexedList objects as well. A subset of these messages are documented in Vision Class: Collection and are summarized below.
- Collection Basics
- count
- do:
- basicDo:
- send:
- basicSend:
- extendBy:
- basicExtend:
- collect:
- numberElements
- linkElements
- do:
- Sorting and Ranking Collections
- sortUpBy:then:
- sortDownBy:then:
- rankUp:
- rankDown:
- rankDown:usingCollector:
- rankUp:usingCollector:
- sortDownBy:then:
- Grouping Collections
- groupedBy:
- groupedByString:
- groupedBy:in:
- groupedBy:intersect:
- groupedBy:union:
- groupedBy:usingCutoffs:
- groupedByCriteria:
- groupPrintUsing:
- mgroupedBy:
- groupedByString:
- Collection Computation Messages
- average
- average:
- average:withWeights:
- compound
- compound:
- correlate:with:
- gMean
- gMean:
- harmonicMean
- harmonicMean:
- harmonicMean:withWeights:
- max
- max:
- median
- median:
- min
- min:
- mode
- mode:
- product
- product:
- rankCorrelate:with:
- regress:
- stdDev
- stdDev:
- total
- total:
- average:
- Intra-List Messages
- decileUp:
- decileDown:
- quintileUp:
- quintileDown:
- percentileUp:
- percentileDown:
- tileUp:tiles:
- tileDown:tiles:
- decileUp:using:
- decileDown:using:
- quintileUp:using:
- quintileDown:using:
- percentileUp:using:
- percentileDown:using:
- tileUp:using:tiles:
- tileDown:using:tiles:
- runningTotal:
- runningAverage:
- normalize:
- weightedDecile:
- weightedQuintile:
- decileDown:
- Inter-List Messages
- isEquivalentTo:
- union:
- union:using:
- intersect:
- intersect:using:
- exclude:
- exclude:using:
- difference:
- union:
- Inquiry Messages
- all:
- any:
- excludesElement:
- includesElement:
- any:
Additional IndexedList Messages
You can redefine Collection messages and define new messages directly for the IndexedList class. You can define any number of new methods and constants. You cannot define and update properties directly for IndexedList objects; however, you can create subclasses of IndexedList and define and update properties for their instances.
The following messages are used to access and update IndexedList objects:
Message | Definition | Sample |
at: | Return value in recipient associated with the index specified by parameter | ilist at: Named Currency USD . |
at:put: | Update recipient using parameter1 as index and parameter2 as object associated with index | ilist at: Named Currency USD put: 1.2 ; |
delete: | Delete value in recipient associated with the index specified by parameter | ilist delete: Named Currency USD . |
deleteColumn: | Deletes this index from all instances in the recipient's cluster (see below) | ilist deleteColumn: Named Currency USD . |
toList | Converts recipient to standard List object | ilist toList |
valueCell: | Returns the structure containing the value associated with the parameter in the recipient | (used by at: message) |
newValueCell: | Returns structure that can be updated using the parameter as an index | (used by at:put: message) |
Creating IndexedList Subclasses
You can create subclasses of the IndexedList class using the standard procedures. Although you cannot create new properties for the IndexedList class directly, you can create new properties at any subclasses you create.
Warning!! Because the IndexedList class is a built-in class that is created primitively as part of the Vision database bootstrap, its subclasses do not inherit a place to store fixed property values defined at parent classes. As a result, you cannot set values for the properties defined at Object in objects that are subclasses of IndexedList. If you want to use these properties, you must redefine them at your subclass.
Accessing the Index
Once you store an object into an IndexedList, there is no built-in way to retrieve its index value or to obtain a list of the indices currently in use. In many cases, the object will be the same as the index or will contain information that allows you to determine the index. If this is not the case and you need to be access the index, you can provide it as an extension to the object being stored. For example:
!ilist <- IndexedList new ; ilist at: Named Currency USD put: (3.1 extendBy: [ !index <- ^global Named Currency US ]) ; ilist at: Named Currency CAD put: (5.2 extendBy: [ !index <- ^global Named Currency CAD ]) ;To display the index and value for each element in ilist use:
ilist do: [ index print ; printNL ] ;You could write a method to perform this function:
#-- Define Method IndexedList defineMethod: [ | at: index putAndIndex: object | !xobject <- object extendBy: [ !index <- ^my index ] ; ^self at: index put: xobject ; ^self ] ; #-- And Run it ilist at: Named Currency DEM putAndIndex: 7.3 ; ilist do: [ index print ; printNL ] ;
Common Errors with IndexedList
The object used as the index in the at: message must be identical to the object used to update the IndexedList in the first place. If the object used as the index in the asOf:put: message is an extension, you must use the identical object with the asOf: message. It is rarely useful to use an extended object as the index. It is therefore good practice to use the asSelf message to strip any extensions from the object used as the index in the at:put: and at: messages.
Since two strings with the same content are not necessarily the same object, it is not safe to use String objects as an index. If you want to use a String object as an index, you need to ensure that the Strings used are preserved and available to applications needing to access objects from the IndexedList. Techniques are available to do this. In general, if you want to use strings as your index, you may want to consider using a Dictionary object instead.
IndexedList Clustering and Property Assignment
Unlike most user-defined classes, you do not usually need to look at all the instances of the built-in classes as a single list. The expression:
IndexedList instanceList count printNL ;displays the value 1. This does not mean that there is only one IndexedList object in the database. The instanceList message actually returns the List of instances in the same physical structure or "cluster" as the recipient. By default, new instances of the IndexedList class are physically stored in separate, independent structures.
To create new instances in the same physical structure as the recipient, you need to use the clusterNew message instead of the new message to create the new IndexedList object. These messages are identical for most classes, since the default behavior is to cluster new instances. For example:
#-- create a new indexed list in its own cluster !newList <- IndexedList new ; #-- a cluster contains the single list by default newList instanceList count printNL ; #-- create new lists in the same cluster !newList1 <- newList clusterNew ; !newList2 <- newList clusterNew ; !newList3 <- newList clusterNew ; #-- instanceList includes all lists in cluster newList instanceList count printNL ; #-- update the different lists in cluster newList1 at: 1 put: 10 ; newList1 at: 2 put: 20 ; newList2 at: Named Currency USD put: Named Currency USD ; #-- display counts for each list in cluster newList instanceList numberElements do: [ position print ; #- position in cluster whatAmI print ; #- each element is an indexed list count printNL ; #- elements in indexed list ] ;More information about general clustering rules is available.
When you are creating IndexedLists "on-the-fly", you do not need to worry about clustering issues. Clustering is important when you are updating properties with IndexedList values.
A property can be updated to contain an IndexedList using the standard assignment messages. For example:
#-- Define a new class and some properties Object createSubclass: "NewClass" ; NewClass defineFixedProperty: "p1" ; #-- Update the properties with list values NewClass :p1 <- IndexedList new ; NewClass p1 at: 1 put: 10 ; NewClass p1 at: 2 put: 20 ;For efficiency reasons, it is preferable to store an IndexedList from the same cluster when you update the same property for different instances. By convention, a "prototype indexed list" is defined for the class. Properties are assigned to new instances in this prototype's cluster. For example:
#-- Define a prototype indexed list for p1 property NewClass define: 'p1PrototypeList' toBe: IndexedList new ; #-- Create new instance and update its value NewClass createInstance do: [ :p1 <- p1PrototypeList clusterNew ; p1 at: 1 put: 10 ; p1 at: 2 put: 20 ; ] ;This initialization is normally added to the initializeLocalAttributes method for the class, since this is automatically run when you create a new instance. For example:
NewClass defineMethod: [ | initializeLocalAttributes | ^super initializeLocalAttributes ; :p1 <- p1PrototypeList clusterNew ; ^self ] ;