The Question Mark - blog by Mark Volkmann

Collections

Smalltalk supports a large number of collection classes. Collection elements can be any kind of object, including other collections.

The Cuis Smalltalk base image contains 60 subclasses of the Collection class. The following list depicts the partial class hierarchy of those:

  • Collection
    • SequenceableCollection
      • ArrayedCollection
        • Array
        • ByteArray
        • ColorArray
        • FloatArray
        • IntegerArray
          • PointArray
      • Heap
      • Interval
      • LinkedList
      • OrderedCollection
        • SortedCollection
    • Bag
    • Set
      • Dictionary
        • OrderedDictionary
  • Array2D - a two-dimensional array
  • SharedQueue

Association

Some programming languages provide a Pair data type that represents a collection of a pair of values. Smalltalk provides two of these.

Instances of the Point class represents a pair of numbers with accessor methods x and y. Sometimes these represent 2D Cartesian coordinates. Sometimes these represent a width and height. But they can be used for any pair of numbers.

Instances of the Association class represents a key/value pair where the keys and values can be any kind of object. These are used by several other classes including Dictionary and Bag.

An Association instance can be created in the following ways:

a := Association key: someKey value: someValue.
a := someKey -> someValue.

The message -> is defined in the Object class which make it easy to create an Association with any object as the key.

The following table describes some of the instance methods defined in the Association class and its superclass LookupKey.

MethodDescription
keyanswers the receiver key
key:modifies the receiver key
key:value:modifies the receiver key and value
valueanswers the receiver value
value:modifies the receiver value

Collection

The following table describes some of the class methods defined in the Collection class. These must be called on a concrete subclass, not on Collection.

MethodDescription
with:answers an instance containing one value
with:with:answers an instance containing two values
with:with:with:answers an instance containing three values
with:with:with:with:answers an instance containing four values
with:with:with:with:with:answers an instance containing five values
withAll:answers an instance containing all the values in another collection

The following table describes some of the instance methods defined in the Collection class.

MethodDescription
*answers instance whose elements are receiver elements times argument
+answers instance whose elements are receiver elements plus argument
,answers instance whose elements are the concatenation of receiver and argument collections
-answers instance whose elements are receiver elements minus argument
/answers instance whose elements are receiver elements divided by argument
//answers instance whose elements are receiver elements integer divided by argument
=answers Boolean indicating if receiver and argument are equivalent
\\answers instance whose elements are receiver elements modulo argument
absanswers instance whose elements are absolute value of receiver elements
add:adds argument to collection and answers argument
addALl:adds all objects in argument collection to collection and answers argument
allSatisfy:answers Boolean indicating if ALL elements satisfy a block; like every in JavaScript
anySatisfy:answers Boolean indicating if ANY elements satisfy a block; like some in JavaScript
asArrayanswers Array instance whose elements are those in receiver
asBaganswers Bag instance whose elements are those in receiver
asCommaStringAndanswers comma-separated String where last elements are separated by “and”
asIdentitySetanswers IdentitySet instance whose elements are those in receiver with no duplicates
asOrderedCollectionanswers OrderedCollection instance whose elements are those in receiver
asSortedCollectionanswers SortedCollection instance whose elements are those in receiver
asSortedCollection:same as asSortedCollection, but takes block that defines sort order
asSetanswers Set instance whose elements are those in receiver with no duplicates
atRandomanswers a random element
averagesame as mean
ceilinganswers instances whose elements are the ceiling of receiver elements
collect:answers instance whose elements are results of passing receiver elements to a block; like map in JavaScript
collect:andFoldanswers instance whose elements are results of passing receiver elements to a block; like map in JavaScript
count:answers number of receiver elements that satisfy argument block
detect:answers first element in receiver that satisfies block argument; like find in JavaScript
detect:ifFound:ifNonelike detect: but executes ifFound: block if found and ifNone: block if not found
detect:ifNonelike detect: but executes ifNone: block if not found
difference:answers instance that includes elements in receiver that are not in argument
do:evaluates block argument for each element; like forEach in JavaScript
do:separatedBy:evaluates do: block argument for each element and separatedBy: block argument between each element
flooranswers instances whose elements are the floor of receiver elements
fold:answers value that results from folding receiver elements with a block; like reduce in JavaScript
fold:ifEmpty:like fold:, but specifies value to use if collection is empty
groupBy:answers Dictionary where keys are values returned by passing each element to block argument and values are OrderedCollection instances
ifEmpty:evaluates block argument if collection is empty
ifEmpty:ifNotEmpty:combines ifEmpty: and ifNotEmpty:
ifNotEmpty:evaluates block argument if collection is not empty
ifNotEmpty:ifEmpty:combines ifNotEmpty: and ifEmpty:
includes:answers Boolean indicating if argument is an element of receiver
includesAllOf:answers Boolean indicating if all elements in argument collection are elements of receiver
includesAnyOf:answers Boolean indicating if any elements in argument collection are elements of receiver
inject:into:similar to fold:, but can specify initial accumulator value; like reduce in JavaScript
intersection:answers instance that includes elements present in both receiver and argument collections
isEmptyanswers Boolean indicating if collection does not contain any elements
maxanswers largest number element
meananswers mean of number elements
minanswers smallest number element
noneSatisfy:answers Boolean indicating if NONE of the elements satisfy a block
notEmptyanswers Boolean indicating if collection is not empty
occurrencesOf:answers number of elements that are equal to argument
productanswers product of number elements
rangeanswers difference between max and min values
reduce:same as fold:
reject:answers instance containing receiver elements that do not satisfy a block
reject:thenCollect:combines reject: and collect:
reject:thenDo:combines reject: and do:
remove:removes first occurrence of argument and answers argument
select:answers instance containing receiver elements that satisfy a block; like filter in JavaScript
select:thenCollectcombines select: and collect:
select:thenDo:combines select: and do:
sizesanswers number of elements in receiver
sortedanswers instance containing all receiver elements in sorted order
sqrtanswers instance whose elements are square root of receiver elements
squaredanswers instance whose elements are squared values of receiver elements
sumanswers sum of receiver number elements
union:answers Set instance that includes elements present in receiver or argument collection

Collections support binary messages that operate on all the elements and return a new array containing the results. For example, #(1 2 3) * 2 returns #(2 4 6).

The allSatisfy: method can be used to verify whether all the characters in a String are digits before asking for the value as a number. For example:

someString allSatisfy: [:char | char isDigit] :: ifTrue: [
    number := someString asNumber.
    ...
]

The fold: method uses the first element as the initial value and folds in the remaining elements. The inject:into: method takes an initial value and folds in all the elements.

The following code demonstrates some of the methods described above:

#('red' 'green' 'blue') asCommaStringAnd "gives 'red, green and blue'

#(1 2 3 4) mean` "gives Fraction 5/2"

numbers := #(1 2 3 4).
numbers collect: [:n | n * 2]. "#(2 4 6 8)"
numbers select: [:n | n odd]. "#(1 3)"
numbers inject: 0 into: [:acc :n | acc + n]. "10"

I implemented the method asOxfordCommaAnd so the example above gives 'red, green, and blue;.

SequenceableCollection

There are no particularly interesting class methods in the SequenceableCollection class.

The following table describes some of the instance methods defined in the SequenceableCollection class that are not defined in the Collection superclass.

All indexes are 1-based.

MethodDescription
,answers new collection created by appending receiver and argument
+=modifies receiver number elements by adding argument to each
-=modifies receiver number elements by subtracting argument from each
allButFirstanswers copy including all but first element
allButFirst:answers copy including all but first argument elements
allButFirstDo:evaluates block argument with all but first element
allButLastanswers copy including all but last element
allButLast:answers copy including all but last argument elements
allButLastDo:evaluates block argument with all but last element
at:ifAbsent:answers element at at: index or if absent the value of ifAbsent: block
atLast:answers element at index that is argument from end (1 for last)
atLast:ifAbsent:like atLast: but specifies value to return if not enough elements
combinations:atATimeDo:evaluates atATimeDo once for every unique combination of combinations: elements
copyFrom:to:answers instance containing only receiver elements from index copyFrom: to index to:
do:displayingProgress:evaluates a block for each receiver element and displays a progress bar (see example below)
findFirst:like detect:, but answers element index of value instead of element value
findFirst:startingAtlike findFirst:, but starts search at a given index
findLast:like findFirst:, but answers element index of last element that satisifies block argument
firstanswers first element in receiver
first:answers copy of first argument elements in receiver
head:same as first:
includes:answers Boolean indicating if receiver contains argument value
indexOf:answers first index of argument value or 0
indexOf:startingAt:answers index of argument value starting at a given index or 0
keysAndValuesDo:evaluates block argument with all key/value pairs
lastanswers last element in receiver
last:answers copy of last argument elements in receiver
lastIndexOf:answers last index of argument value or 0
middleanswers the middle element in receiver
permutationsDo:evaluates block argument with an OrderedCollection once for each permutation of all receiver elements
polynomialEval:answers result of using receiver number elements as polynomial coefficients with argument for x
printStringWithNewlineanswers String with newline between each element value
replace:replace each element in receiver with result of passing it to argument block
reversesame as reversed
reversedanswers copy with elements in reverse order
shuffledanswers copy with elements in random order
tail:same as last:
with:collect:answers Array of results from evaluating collect: block with corresponding elements from receiver and with: collections
with:do:evaluates do: block with corresponding elements from receiver and with: collections
with:with:collect:like with:collect:, but operates on three collections
with:with:do:like with:do:, but operates on three collections
withIndexCollect:like collect:, but passes element and index values to argument block
withIndexDo:like do:, but passes element and index values to argument block
withNextDo:evaluate argument block with each receiver element and the next element, using nil for next of last element

In polynomialEval:, the first element is the constant, the second is the x coefficient, the third is the x^2 coefficient, and so on.

The following code demonstrates using the do:displayingProgress: method.

Cuis SequenceableCollection do:displayingProgress:

c := #(10 20 30 40 50).
delay := Delay forSeconds: 1.
c do: [:n |
    ('processing {1}' format: {n}) print.
    delay wait.
] displayingProgress: 'doing stuff'

ArrayedCollection

There are no particularly interesting methods in this class.

Array

Array instances are fixed-length, ordered collections. They are indexed starting at 1, not 0. Elements cannot be added or deleted, but they can be modified.

Most of the interesting Array methods are defined in the superclasses SequenceableCollection and Collection.

Compile-time literal arrays begin with #(, end with ), and contain space-separated expressions. The expressions can be literal values or expressions whose values can be determined at compile-time. For example, #(true 7 'Tami' (Color red)).

Compile-time literal arrays cannot contain references to variables or other expressions whose values cannot be determined until run-time. The expression #(a b c) is not interpreted as an array containing the values of the variables a, b, c. It is instead interpreted as an array containing the symbols #a, #b, and #c.

Run-time literal arrays (a.k.a. dynamic arrays) begin with {, end with }, and contain dot-separated expressions. For example, {name. breed}. This is an alternative to the more verbose syntax Array with: name with: breed. A common error is to separate expressions with commas instead of periods.

To create an array of a given size where the initial value of all the elements is nil, send the message #ofSize: to the Array class. For example, rgb := Array ofSize: 3.

To create an array of a given size where the initial value of all the elements is the same non-nil value, send the message #new:withAll: to the Array class. For example, rgb := Array new: 3 withAll: 255.

The following table describes some of the instance methods defined in the Array class that are not defined in its superclasses.

MethodDescription
evalStringsanswers Array whose elements are the results of evaluating receiver String elements as Smalltalk expressions

To create an array of numbers from a range, send the #asArray message to a Range. For example, (1 to: 5) asArray returns #(1 2 3 4 5).

In addition to the Array class that can hold values of any type, there are predefined, type-specific array classes including ByteArray, ColorArray, DoubleByteArray, DoubleWordArray, Int16PointArray, FloatArray, Float32Array, Float32PointArray, Float64Array, IntegerArray, PointArray, RunArray, RunNotArray, and WordArray.

Interval

Instances of the Interval class represent a finite arithmetic progression. This is a sequence of numbers where the difference between consecutive terms is constant. An example is the numbers 2, 4, 6, and 8.

The Interval class is a subclass of SequenceableCollection which is a subclass of Collection.

The following table describes some of the class methods defined in the Interval class.

MethodDescription
from:to:answers an instance where the increment between values is 1
from:to:by:answers an instance where the increment between values is by:
from:to:count:answers an instance where the number of values is count: and the increment can be a Fraction
integersFrom:to:count:answers an instance where the number of values is count: and the increment is the closest Integer, not a Fraction

The following table describes some of the instance methods defined in the Interval class that are not also defined in superclasses.

MethodDescription
at:answers the value at a given index
do:evaluates a block for each value from first to last
extentanswers the difference between the last and first values
firstanswers the first value
includes:answers Boolean indicating if argument is one of the values
incrementanswers the increment between values
isEmptyanswers Boolean indicating if the size is 0
lastanswers the last value
sizeanswers the number of values
reverseDo:evaluates a block for each value from last to first

LinkedList

LinkedList instances represent a singly-linked list of Link subclasses. Custom Link subclasses must be defined. For example, the StringLink class defined below has a single instance variable named “value”.

Link subclass: #StringLink
    instanceVariableNames: 'value'
    classVariableNames: ''
    poolDictionaries: ''
    category: 'Volkmann'!

The StringLink class has the following class method for creating instances.

value: aString
    | link |
    link := self new.
    link setValue: aString.
    ^ link

The StringLink class has the following instance methods.

setValue: aString
    value := aString

value
    ^value

value: aString
    value := aString

We can now create an instance of LinkList whose links are StringLink instances.

list := LinkedList new.
list addLast: (StringLink value: 'banana').
list addLast: (StringLink value: 'cherry').
list addFirst: (StringLink value: 'apple').

"Output all the link values which are apple, banana, and cherry."
list do: [:link | link value print].

list size print. "3"

('first is {1}' format: {list first value}) print. "apple"
('last is {1}' format: {list last value}) print. "cherry"

list removeLast. "removes cherry"

"Output all the link values which are apple and banana."
list do: [:link | link value print].

Link instances have the methods nextLink and nextLink: to get and set the link to which they refer. In addition to the LinkedList methods described below, these can be used to insert new Link instances into a LinkedList.

The following table describes some of the instance methods defined in the LinkedList class that are not also defined in superclasses.

MethodDescription
add:same as addLast:
add:before:adds add: Link before before: Link
addFirst:adds argument Link to beginning
addLast:adds argument Link to end
at:answers Link at argument index; error if absent
at:ifAbsent:answers Link at at: index or value of ifAbsent: if absent
do:evaluates argument block for each Link
firstanswers first Link
isEmptyanswers Boolean indicating if size is 0
lastanswers last Link
remove:ifAbsent:removes remove: Link and answers it; answers ifAbsent: value if absent
removeFirstremoves and answers first Link; treats like a queue
removeLastremoves and answers last Link; treats like a stack

OrderedCollection

The OrderedCollection class is a subclass of SequenceableCollection. Instances are variable-length, ordered collections that can contain duplicates.

To create an OrderedCollection from an array, send the #newFrom: message. For example, fruits := OrderedCollection newFrom: #('apple' 'banana' 'cherry')

To create an OrderedCollection of a given size where the initial value of all the elements is nil, send the message #ofSize: to the OrderedCollection class. For example, players := OrderedCollection ofSize: 4.

To get the number of elements, send the #size message. For example, fruits size returns 3.

To get an element at a specific position send messages like #first, #last, and #at: which takes a 1-based index. For example, fruits first returns 'apple', fruits last returns 'cherry', and fruits at: 2 returns 'banana'.

To add an element to the end, send the #add message. For example, fruits add: 'date'.

To add an element at a specific index, send the #add:beforeIndex: or #add:afterIndex message. For example, fruits add: 'orange' beforeIndex: 3.

To remove an element at a given 1-based index send the #removeAt message. For example, fruits removeAt: 3 removes 'cherry'.

To get the index of the first occurrence of a given value, send the #indexOf: message. For example, fruits indexOf: 'banana' returns 2.

The following table describes some of the instance methods defined in the OrderedCollection class that are not also defined in superclasses.

MethodDescription
add:adds argument to end
add:after:adds add: object after after: object
add:afterIndex:adds add: object after afterIndex: index
add:before:adds add: object before before: object
add:beforeIndex:adds add: object before beforeIndex: index
addAllFirst:adds all objects in argument OrderedColection at beginning
addAllLast:adds all objects in argument OrderedColection at end
at:answers element at argument index
at:ifAbsentPut:answers element at at: index; if not present, adds and answers ifAbsentPut: block value
at:put:replaces existing element at at: index with put: object
collect:thenSelect:combines collect: and select:
copyanswers a shallow copy
find:answers index of first occurrence of argument value
removeAllremoves all elements
removeAllSuchThat:removes all elements that satisfy argument block
removeAt:removes element at argument index
removeFirstremoves first element and answers it
removeFirst:removes first argument elements and answers Array of them
removeLastremoves first element and answers it
removeLast:removes last argument elements and answers Array of them
sortsorts the elements in place
sort:sorts the elements in place using argument block to compare them

The following code creates an instance of OrderedCollection containing Symbol objects that are names of fruits. It then sorts them on their length.

oc := OrderedCollection newFrom: #(#banana #watermelon #cherry #apple #plum).
oc sort: [:a :b | a size < b size].

SortedCollection

The SortedCollection class is a subclass of OrderedCollection. Instances keep their elements in sorted order. The element objects must implement the <= instance method.

The following code demonstrates defining a Dog class whose instances can used as elements in a SortedCollection.

Object subclass: #Dog
    instanceVariableNames: 'breed name'
    classVariableNames: ''
    poolDictionaries: ''
    category: 'Volkmann'

"class methods follow"

name: nameString breed: breedString
    ^self new setName: nameString breed: breedString

"instance methods follow"

breed
    ^ breed

name
    ^ name

<= aDog
    ^ self name <= aDog name

setName: nameString breed: breedString
    name := nameString.
    breed := breedString

The following code demonstrates creating a SortedCollection of Dog objects and printing their names.

comet := Dog name: 'Comet' breed: 'Whippet'.
oscar := Dog name: 'Oscar' breed: 'German Shorthaired Pointer'.
dogs := SortedCollection newFrom: {oscar. comet}.
dogs do: [:dog | dog name print]

The output in the Transcript will be “Comet” followed by “Oscar”.

Bag

Bag instances are variable-length, unordered collections that can contain duplicates. Elements are stored in a dictionary where the keys are the objects and the values are their number of occurrences.

The following table describes some of the instance methods defined in the OrderedCollection class that are not also defined in the superclass Collection.

MethodDescription
sortedCountsanswers a SortedCollection of Association objects where keys are counts and values are elements
sortedElementsanswers a SortedCollection of Association objects where keys are elements and values are counts
withOccurrencesDo:evaluates argument block for each unique element, passing it an element and its count

The following code creates a Bag instance containing String fruit names, including many duplicates. It then sends several messages to the Bag.

b := Bag newFrom: #('apple' 'banana' 'cherry' 'apple' 'cherry' 'apple').
b sortedCounts print.
b sortedElements print.
b withOccurrencesDo: [:obj :count |
   ('{1} occurs {2} times.' format: {obj. count}) print
].

The code above produces the following output in the Transcript:

a SortedCollection(3 -> 'apple' 2 -> 'cherry' 1 -> 'banana')
a SortedCollection('apple' -> 3 'banana' -> 1 'cherry' -> 2)
cherry occurs 2 times.
apple occurs 3 times.
banana occurs 1 times.

Set

Set instances are unordered collections of unique values. The elements must supports the = and hash messages and cannot be nil.

To create an empty Set:

set := Set new

To create a Set that is populated from an Array:

set := Set newFrom: #('apple' 'banana' 'cherry' 'apple')

The following code demonstrates adding an element, removing an element, and testing for the existence of an element:

set add: `orange`.
set remove: 'banana'.
set includes: 'apple`. "true"
set includes: 'banana`. "false"

Numbers in a Set are compared based on their values, not considering the classes of which they are instances. For example, the SmallInteger value 2, the Float value 2.0, and the Fraction value 6/3 are all considered equal, so only one of those values can be present in a Set.

Dictionary

Dictionary instances are collections of key/value pairs. The keys are often symbols, but they can be any kind of object that supports the = and hash messages. The values can be any kind of object.

To create a Dictionary:

dict := Dictionary new.

"Can populate from a run-time Array of Association objects."
dict := Dictionary newFrom: { k1 -> v1. k2 -> v2. ... }.

dict := { k1 -> v1. k2 -> v2. ... } asDictionary.

It is not possible to create a Dictionary instance from a compile-time Array of Association objects because Association objects cannot be created at compile-time. This means the following cannot be used:

#( k1->v1 k2->v2 ... )

To add a key/value pair:

dict at: #key put: value.
dict add: key -> value.

To get the value for a key:

value := dict at: #key
value := dict at: #key ifAbsent: defaultValue
value := dict at: #key ifAbsentPut: defaultValue

If a default value is not provided and the key is not found, an Error window will open that says “key not found”.

To get all the keys, values, or associations:

ks := dict keys.
vs = dict values.
as = dict associations.

To iterate over the values:

dict do: [:value | value print].

To iterate over the keys and values:

dict associationsDo: [:assoc |
    Transcript
        show: assoc key;
        show: ' ';
        show: assoc value;
        cr
].

The following table describes some of the instance methods defined in the Dictionary class that are not also defined in the superclasses Set or Collection.

MethodDescription
addAll:adds key/value pairs described by a Collection of Association objects
associationAt:answers Association for argument key; error if not found
associationAt:ifAbsent:answers Association for associationAt: key or ifAbsent: value if not found
associationsanswers Array of Association objects for all key/value pairs
associationsDo:evaluates argument block for each Association
at:answers value for argument key; error if not found
at:ifAbsent:answers value for at: key or ifAbsent: value if not found
at:ifAbsentPut:answers value for at: key; if not found, adds and answers ifAbsentPut: block value
at:ifPresent:if at: key is present, answers value of passing value to ifAbsent block; otherwise answers nil
at:ifPresent:ifAbsent:combines at:ifPresent and at:ifAbsent
at:put:adds key at: with value put:
bindingOf:answers Assocication for argument key
bindingsDo:same as associationsDo
hasBindingThatBeginsWith:answers Boolean indicating if any String key begins with argument; error if non-String keys
includesKey:answers Boolean indicating if argument key is present
keysanswers Array of all keys
keysAndValuesDo:evaluates argument block for each key/value pair passing key and value arguments to block
keysAndValuesRemove:removes all key/value pairs that satisfy argument block, passing key and value arguments to block
keysDo:evaluates argument block for each key
removeKey:removes key/value pair for argument key
removeKey:ifAbsent:removes key/value pair for removeKey: key; answers removed value if present; otherwise answers ifAbsent: value
select:answers new Dictionary containing all key/value pairs whose value satisfies argument block
valuesanswers Array of all values
valuesDo:evaluates argument block for each value

One way to delete an entry from a Dictionary is to inspect it, right-click a key in the left column, and select “Remove”. There are also context menu items to add and rename keys.

The at:ifAbsentPut method can be used to implement memoized methods that avoid computing their result if the same arguments have been passed before. The following code demonstrates this by adding the Object instance method memoize: which takes a block. This caches previously computed values in an IdentityDictionary that is saved in Smalltalk which is also an IdentityDictionary.

memoize: aBlock
    "Cache the result of evaluating a method with specific arguments.
    This intended for use in class methods that only use their arguments.
    To clear the cache for all methods in a given class, send
    Object clearMemo: 'SomeClassName'"
    | cache cacheKey sender valueKey |

    sender := thisContext sender.

    "Smalltalk is a SystemDictionary which is an IdentityDictionary.
    That is why cacheKey must be a Symbol."
    cacheKey := ('memo-', sender name) asSymbol.

    cache := Smalltalk at: cacheKey ifAbsentPut: [ IdentityDictionary new ].
    valueKey := thisContext name, sender arguments asString :: asSymbol.

    ^ cache at: valueKey ifAbsentPut: [ aBlock value ].memoize: aBlock

Using the memoize: method is demonstrated in the class method indentDepth: (added to any class) which takes an integer and returns a string with four times that number of spaces.

indentDepth: aNumber
    ^ self memoize: [
        String new: aNumber * 4 withAll: Character space.
    ]

Here is another class method that uses memoize: to avoid repeated computations:

fibonacci: anInteger
    ^ self memoize: [
        anInteger = 0 ifTrue: [ 0 ]
        ifFalse: [ anInteger = 1 ifTrue: [ 1 ]
        ifFalse: [ (self fibonacci: (anInteger - 1)) + (self fibonacci: (anInteger - 2)) ] ]
    ]

OrderedDictionary

The OrderedDictionary class is a subclass of Dictionary. Instances remember the order in which entries were added. Iteration methods like do:, keysDo: and associationsDo: and valuesDo:. process the entries in that order.

The following code demonstrates creating an OrderedDictionary whose values are objects from the Dog class defined the SortedCollection section above, and printing their names.

comet := Dog name: 'Comet' breed: 'Whippet'.
oscar := Dog name: 'Oscar' breed: 'German Shorthaired Pointer'.
dict := OrderedDictionary new.
dict at: comet name put: comet.
dict at: oscar name put: oscar.
dogs do: [:dog | dog name print]

The output in the Transcript will be “Comet” followed by “Oscar”.

Indexable Classes

Classes whose instances respond to messages like #at:put: are “indexable”. This means they hold a collection of objects that are accessed by index. Such classes are defined with by sending the message #variableSubclass:instanceVariableNames:classVariableNames:poolDictionaries:category: to another class. That is handled by an instance method in the class Class. An example of a class that is defined this way is Array.

Also see the Class instance methods whose names begin with variableByteSubclass:, variableDoubleByteSubclass:, variableDoubleWordSubclass:, and variableWordSubclass:.

See the instance method instSpec in the Behavior class which defines values for the format instance variable. The values 2 and 3 indicate that the object in indexable.

Identity Collections

The collection classes IdentityBag, IdentityDictionary, and IdentitySet are similar to their non-identity counterparts, but differ in that elements are compared using == instead of =. This means they check whether objects are the same memory rather than checking for equal values. The advantage is that this check can be done more quickly.

They use hash values returned by the ProtoObject instance method identityHash. TODO: Does this use the memory address of the object?

Weak Collections

The collection classes WeakArray, WeakKeyDictionary, WeakIdentityKeyDictionary, WeakValueDictionary, WeakSet, and WeakIdentitySet are similar to their non-week counterparts, but they differ in that objects to which they refer are not prevented from being garbage collected if they are only referred to by weak collections.

If an attempt is made to use an element in one of these collections that has been garbage collected, its value will be nil.

For example, create a class named WeakSetDemo with the instance variable set and the following instance methods:

initialize
    | comet oscar |
    comet := Dog name: 'Comet' breed: 'Whippet'.
    oscar := Dog name: 'Oscar' breed: 'GSP'.
    set := WeakSet newFrom: {comet. oscar}.
    set do: [:dog | dog name print].

first
    ^ set asArray first

Enter the following lines in a Workspace, select them, and “Print it”:

demo := WeakSetDemo new. "invokes initialize"
demo first

The result will be nil because the comet and oscar objects will be garbage collected after the initialize method executes.

Conversions

Many collection classes have instance methods whose names begin with “as” that create a different type of collection containing its elements.

Highlights include:

  • asArray: answers Array representation of any kind of Collection
  • asBag: answers Bag representation of any kind of Collection
  • asDictionary: answers Dictionary representation of any kind of Collection whose elements are Association instances
  • asOrderedCollection: answers an OrderedCollection representation of any kind of Collection
  • asSet: answers a Set representation of any Collection, removing duplicates
  • asSortedCollection: answers SortedCollection representation of any Collection

In addition, the asJsonString method answers a JSON String representation of any kind of collection.

Common Abbreviations

For method arguments and variables whose values are expected to be a kind of collection, it’s useful to include the least restrictive kind of collection that is suitable in the name of the argument or variable. For example, it’s rarely the case that an Array is required and often the case that any kind of SequenceableCollection will suffice.

The names of many of the collection classes are long. I recommend using the following abbreviations in variable names.

Class NameAbbreviation
ArrayArray
BagBag
CollectionColl
DictionaryDict
IdentityDictionaryIdDict
OrderedCollectionOrdered
SequenceableCollectionSeq
SetSet
SortedCollectionSorted