Collections

Collection is an abstract class, but it defines several common methods which the concrete subclasses will need to implement in their own appropriate way. Some of the most important of these common methods are:

purpose / methods
for accessing elements / at:, at:put:*
for finding how many elements a collection has / size
for adding to (a variable-sized collection only) / add:
for removing from (again a variable-sized collection) / remove:, remove:ifAbsent:
for testing / includes:, isEmpty
for enumeration (i.e. going through all elements in turn) / do: , detect:ifNone:

* (actually at: and at:put: are defined at an even higher level, in Object itself)

detect:ifNone: can act like a more complicated includes:, a sort of “does the collection include an element satisfying this condition?”

Creation

Creation: to create the fixed-size collections, array, string or symbol, you would either do it literally (by saying what the initial contents are) or by using new: followed by a number. For the other collections, you can use new. E.g.

mySet := Set new.

myDictionary := Dictionary new.

mySortedList := SortedCollection new.

myArray := Array new:5.

myOtherArray := #(13.7 12.3 16.8 14.1 7.3).

myString := 'Yorkshire Region'.

myOtherString := String new:16.

mySymbol := #'jump'.

myOtherSymbol := #left. "Starts with letter, no spaces"

Please note: throughout where I have said informally something like "an OrderedCollection" I do mean "an instance of class OrderedCollection".

Iterating through collections

do: goes through all the values of a collection. The general form is:

aCollection do: [:currentValue| "do something with currentValue"]

N.B. currentValue is a name you choose: you place it at the beginning of the block, preceded by a colon, and then use it again inside the block. It can be anything you like.

Problem 1

If stringCollection is an OrderedCollection of strings, numSet is a Set of numbers and

frogArray is an Array of 5 frogs, then what comment would be appropriate for each of the following (i.e. what do you think each does)?

(display show: just puts things into the display pane of the LearningBook.)

(i) stringCollection do:[:this| display show:this; cr]

(ii) numSet do:[:number| display show: (number printString); cr ]

(iii) | total |

total := 0.

numSet do:[:current| total := total + current].

^total

(iv) frogArray do:[:currentFroggy| currentFroggy jump; right; jump; left; brown]

(v) stringCollection do:[:thisString| thisString at:1 put:$?]

(at:put: will be discussed later: here consider it to be replacement of a character at a specified position in the string)


The variant keysAndValuesDo: can be very useful. For SequenceableCollections, in which each data value has a position (an index), the two block arguments run through the indexes and the data values, and for Dictionary objects they run through the keys and the values.

Problem 2

What do you think the following does?

frogArray keysAndValuesDo: [:thisNumber :thisFrog|

(thisFrog colour = Green)

ifTrue:[display show:’Frog No ‘,

thisNumber printString

]

]

So far, we have dealt with how to get at existing objects in collections. If we want to put new or replacement objects into a collection, we need some other techniques.

Inserting and replacing in collections

add: and at:put: are the main methods of placing objects in a collection of variable size and of fixed size respectively.

Insertion in variable-sized collections

For variable-sized collections, there is generally no need to initialize – just start with no elements and add them as they come along. For example:

s:= Set new.

s add:34; add:76; add:22.

NBeware If you write (as I frequently do because I forget) things like

mySet := Set new add:'Jim'; add:'Sue'; add:'Mark'

then your program won't work!!!! WHY NOT?

Reading into a set can be done like this:

"read in a set of numbers, terminated by Cancel being pressed"

| input finished |

finished:= false.

[finished] whileFalse:

[input:= (Dialog request: 'Please type next number'

initialAnswer:''

onCancel:[finished := true]).

(finished) ifFalse: [numSet add: input asNumber]

]


Insertion in fixed-sized collections

For fixed-size collections, you have a fixed number of elements which will be references to nil on creation of the collection. You may want to initialize them all to be some other value.

Suppose you want to initialize every element of an array of 8 elements, which is called results, to something, e.g. zero, or an empty set. You can either run through the array, or you may find you can make use of a handy method called atAllPut:. Let's start by trying to put zero in, as that's easier.

Problem 3

Are the following all OK, do you think?

(i) results atAllPut: 0

(ii) results do: [:each| each := 0]

(iii) 1 to: (results size) do: [:index| results at:index put:0]

Problem 4

What about putting something like a (different) new empty set at each position of the array? Have a look at (i) to (iii) below, and see if you can spot which will do what you want.

(i) results atAllPut: Set new

(ii) results do: [:each| each := Set new]

(iii) 1 to:(results size) do: [:indexvalue|

results at:indexvalue put:Set new

]

A variation where you ask the user for the initial values, this time for an array of strings:

1 to:(results size) do:[:position| results at:position put:

(Dialog request:

('Please type name no. ',position printString)

initialAnswer:''

onCancel:['unknown name'])

]

Further problems

Problem 5

What are the final values of a b and c , after the following expression series has been evaluated?

| a b c d |

a := #(4.75 'rats' #(32 44) 'pigs' 150 #green).

a at:4 put:((a at:2) reverse).

b := a at:2.

b at:3 put:$m.

c:= a at:3.

c at:1 put:27.

(a at:2) at:1 put:$h.

Problem 6

Amphibian Telecommunications is planning to try out a system of discounts to its telephone service users, which allows them to define up to 10 “pals” to whom they will be able to make cheaper calls. The system, which is currently named “Pals Under Test”, is to be implemented in Smalltalk by making use of a class called Pal, with just two instance variables called nickName and phoneNo, both strings. Pal has the usual getter and setter methods for its instance variables.

An array of size 10 will be used for each customer, and each element will either refer to an instance of Pal, or will be nil.

(i) What Smalltalk expression(s) would create an array of size 10 referenced by variable cust, and place at element 1 a Pal with nickname ‘Gill’ and phone number ‘01137778888’?

(ii) There is a need for a string containing the nicknames from all the instances of Pal in the cust array. They must be concatenated into one long string, each nickname followed by a “\” character. How would you do this?

(iii) How would you create a set, referenced by a variable called areacodes, and containing all the areacodes used in the phone numbers of the array cust? For simplicity, take the area code of a telephone number to be the first 4 characters. (Note: if you send to a string the message copyFrom:1 to: 4 then the message answer is the first 4 characters. You can assume that phone numbers are more than 4 characters long.)

Problem 7

What fragments of code would test a set, referenced by variable mySet,

(i) to find out whether it had at least 5 elements?

(ii) to find out whether it had no elements at all?

Problem 8

A class called FrogSet (a subclass of Set) has instances each of which is a set of frog objects. An instance method called trafficLight is to return true if there is at least one green frog and at least one red frog in the receiver (i.e. the set of frogs), and false otherwise. Write Smalltalk code for the method. How would you test your method?


Solutions

Problem 1

(i) "show the strings on separate lines in the display pane"

(ii) "show (the string representations of) the numbers on separate lines in the display pane"

(iii) "return the result of adding together all the numbers in the set"

(iv) "make all the frogs do a little dance and turn brown"

(v) "replace the first character of each string with a ?"

Problem 2

"display the position numbers within the array of all the green frogs"

Problem 3

(ii) won't work, because do: is just delivering the values, not allowing you to know where they are in order to change them. You get a message attempt to store into argument. The other methods will work.

Problem 4

The problem is that you may have all 8 positions of your array referring to the same Set! You find that if you evaluate, say,

(results at:1) add:'cat'

then inspect results, there appear to be 8 instances of Set each containing the string 'cat'! But of course they are really all the same one.

(i) all elements refer to the same Set

(ii) doesn't work at all - see Problem 3

(iii) OK

Problem 5

a -> / 4.75 / 'hams' / #(27 44) / 'star' / 150 / #green

b -> 'hams'

c -> / 27 / 44


Problem 6

(i) cust := Array new:10.

cust at:1 put:((Pal new) nickName:'Gill'; phoneNo:'01137778888')

or use a temporary variable to refer to the new Pal instance first

(ii) allNames:=''.

cust do:[:thisPal|

(thisPal isNil) ifFalse:

[allNames:=allNames,thisPal nickName,'\']]

(iii) |code|

areacode:= Set new.

cust do:

[ :aPal | (aPal isNil) ifFalse:

[code:= (aPal phoneNo) copyFrom:1 to:4.

areacode add:code]

]

Problem 7

(i) (mySet size >= 5) ifTrue:[ ]

(i) (mySet isEmpty) ifTrue:[ ]

Problem 8

One method is shown: using detect: would give a good solution.

trafficLight

"Return true if at least one green and at least one red frog in the receiver. Otherwise return false"

| hasGreen hasRed |

hasGreen:=false.

hasRed:=false.

self do:[:eachFrog| (eachFrog colour = Green) ifTrue: [hasGreen:=true].

(eachFrog colour = Red) ifTrue: [hasRed:=true]

].

^hasGreen & hasRed

– 1 –