4.4 Collections detailed

The Collections- class categories are the most prolific, there are 7 of them gathering 46 classes.

The category Collections-Abstract groups classes which are said to be abstract. An abstract class cannot be instantiated, its behavior is declared but not completely implemented. It is the responsibility of its subclasses to implement the missing part of the behavior.

An abstract class is useful to establish a set of polymorphic methods which each of its concrete subclasses are expected to specialize. This captures and communicates our intent.

Observe how the important do: method is declared but not implemented:

Collection>>do: aBlock 
"Evaluate aBlock with each of the receiver's elements as the argument."
self subclassResponsibility

Then observe how two different Collection subclasses implement it:

OrderedCollection>>do: aBlock 
firstIndex to: lastIndex do: [ :index |
   aBlock value: (array at: index) ]

and:

Dictionary>>do: aBlock
super do: [:assoc | aBlock value: assoc value]

Two important groups of collections must be distinguished: collection with a fixed size and collection with a variable size.

Collection of fixed size. Such collections are gathered in the category Collections-Arrayed. The most notable one is Array, its size – the number of elements it can hold – is set when creating the instance. Once instantiated, you can neither add nor delete elements to an array.

There are different ways to create Array instance:

array1 := #(2 'Apple' $@ 4) "create at compile time"
array1b := {2 . 'Apple' . 2@1 . 1/3 } "created a execution time"
array2 := Array with: 2 with: 'Apple' with: 2@3 with: 1/3.
array3 := Array ofSize: 4 "an empty array with a 4 element capacity"

Example 4.9: Collection with a fixed size

Array array1 and array1b are bit different. The former one is created and filled with its contents at compile time of the code, the consequence is it can only be filled with literal elements as integer, float, string. The later one is created at execution time of the code, it can be filled with elements instantiated at the execution time as Fraction or Point instances.

You can access elements with an important variety of messages:

array1 first ⇒ 2
array1 second ⇒ 'Apple'
array1 third ⇒ $@
array1 fourth ⇒ 4
array1 last ⇒ 4
array1 at: 2 ⇒ 'Apple'
array2 at: 3 ⇒ 2@3
array2 swap: 2 with: 4 ⇒ #(2 1/3 2@3 'Apple') 
array1 at: 2 put: 'Orange'; yourself ⇒ #(2 'Orange' $@ 4)
array1 indexOf: 'Orange ⇒ 2

Example 4.10: Collection access to elements

Use the System Browser to discover alternative way to access elements of a collection.

 CuisLogo What is the appropriate message to access the first 2 elements of the array1 collection?

Exercise 4.12: Access part of a collection

You can’t add or remove an element, though:

array1 add: 'Orange'
⇒ Error: 'This message is not appropriate for this object'
array1 remove: 'Apple'
⇒  Error: 'This message is not appropriate for this object'

Nevertheless, it is possible to fill at once an array:

 CuisLogo Fill every element in array1 with ’kiwi’ all at once?

Exercise 4.13: Fill an array

Collection of variable size. Such collection are gathered in several class categories: Collections-Unordered, Collections-Sequenceable, etc. They represent the most common collections.

OrderedCollection is a notable one. Its elements are ordered: elements are added one after the other in sequence18. Its size is variable depending on added or removed elements.

coll1 := {2 . 'Apple' . 2@1 . 1/3 } asOrderedCollection
coll2 := OrderedCollection with: 2 with: 'Apple' with: 2@1 with: 1/3
coll3 := OrderedCollection ofSize: 4

Example 4.11: Collection with a variable size

The access to elements is identical to an Array instance, but dynamic collections allow you to add and remove elements:

coll1 add: 'Orange'; yourself
⇒ an OrderedCollection(2 'Apple' 2@1 1/3 'Orange')
coll1 remove: 2@1; yourself
⇒ an OrderedCollection(2 'Apple' 1/3)

Example 4.12: Adding, removing element from a dynamic array

 CuisLogo How to add ’Orange’ after ’Apple’ in coll1?

Exercise 4.14: Add an element after

Set. Set is an unordered collection without duplicated elements. The order of the element is not guaranteed, though. Observe how pi is the first element of the set:

set := Set new.
set add: 1; add: Float pi; yourself
⇒ a Set(3.141592653589793 1)

Example 4.13: Set collection

Non duplicate are guaranteed at least, even with number of different types. Observe how 1, 3/3 and 1.0 are considered equal and not duplicated in the set:

set := Set new.
set add: 1; add: Float pi; add: 3/3; add: 1/3; add: 1.0; yourself
⇒ a Set(1/3 3.141592653589793 1)

Example 4.14: Set, without duplicates

A very handy way to create a Set instance, or any other collection, is to create a dynamic array and convert it with the #asSet message:

{1 . Float pi . 3/3 . 1/3 . 1.0} asSet
⇒ a Set(3.141592653589793 1/3 1)

Example 4.15: Convert dynamic array

Observe the alternate conversion messages:

{1 . Float pi . 3/3 . 1/3 . 1.0} asOrderedCollection
⇒ an OrderedCollection(1 3.141592653589793 1 1/3 1.0) 

{1 . Float pi . 3/3 . 1/3 . 1.0} asSortedCollection
⇒ a SortedCollection(1/3 1 1 1.0 3.141592653589793)

To uniquely collect the divisors list of 30 and 45 (not the common divisors):

Set  new 
   addAll: #(1 2 3 5 6 10 15 30) ; 
   addAll: #(1 3 5 9 15 45) ; 
   yourself. 
⇒ a Set(5 10 15 1 6 30 45 2 3 9)

 CuisLogo How will you collect the letters in the sentences ’buenos días’ and ’bonjour’?

Exercise 4.15: Letters

Dictionary. A dictionary is a list of associations between a key and an object. Of course a key is an object, but it must respond to equality tests. Most of the time, symbols are used as keys.

To compile a list of colors:

| colors |
colors := Dictionary new.
colors
   add: #red -> Color red;
   add: #blue -> Color blue;
   add: #green -> Color green

Example 4.16: Dictionary of colors

There are shorter descriptions:

colors := Dictionary newFrom:
   {#red -> Color red . #blue -> Color blue . #green -> Color green}.
colors := {#red -> Color red . #blue -> Color blue .
   #green -> Color green} asDictionary

You access color by symbols:

colors at: #blue
⇒ Color blue
colors at: #blue put: Color blue darker
colors at: #yellow ifAbsentPut: Color yellow
⇒ association `#yellow -> Colors yellow` added to the dictionary

There are different way to access a dictionary contents:

colors keys.
⇒ #(#red #green #blue) 
colors keyAtValue: Color green
⇒ #green

Beware. The classic enumerators iterate the values of the dictionary:

colors do: [:value | Transcript show:  value; space ]
⇒ (Color r: 1.000 g: 1.000 b: 0.078) (Color r: 0.898 g: 0.000 b: 0.000)...

Sometimes, you really need to iterated the whole key-value association:

colors associationsDo: [:assoc | 
   Transcript show: assoc key; space; assoc value; cr ]

There are other variants to explore by yourself.

 CuisLogo With an appropriate enumerator, how will you edit the contents of the colors dictionary to replace its values with a nicely capitalized string representing the name of the color?

Exercise 4.16: Color by name

There are many more collections to explore. You now know enough to explore and to search by yourself with the System Browser, and to experiment with the Workspace.


Footnotes

(18)

Of course you can insert an element between two elements. However LinkList instance are more efficient for this use case.