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

Creating Subsets
select:
first:
last:

Sorting and Ranking Collections
sortUpBy:then:
sortDownBy:then:
rankUp:
rankDown:
rankDown:usingCollector:
rankUp:usingCollector:

Grouping Collections
groupedBy:
groupedByString:
groupedBy:in:
groupedBy:intersect:
groupedBy:union:
groupedBy:usingCutoffs:
groupedByCriteria:
groupPrintUsing:
mgroupedBy:

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:

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:

Inter-List Messages
isEquivalentTo:
union:
union:using:
intersect:
intersect:using:
exclude:
exclude:using:
difference:

Creation and Update Messages
copyListElements
append:
collectListElementsFrom:

Inquiry Messages
all:
any:
excludesElement:
includesElement:


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
 ] ;

Related Topics