4.3 Fun with collections

A Collection is a grouping of objects. Arrays and Lists are collections. We already know a String is a collection; precisely a collection of characters. Many kinds of Collection have similar behaviors.

An Array is a fixed-size collection, and unlike a string, it can contain any kind of literal enclosed in #( ):

"array of numbers"
#(1 3 5 7 11 1.1)
"array of mixed literals"
#(1 'friend' $& 'al')

An Array is constructed directly using well-formed literal elements. We will get to the meaning of this last statement when we discuss details of the Smalltalk language.

For now, just note that using non-literal expressions to construct an array will not work as expected:

#(1 2/3)
⇒ #(1 2 #/ 3)

Indeed, the $/ is interpreted as a literal symbol and we get basic components of “2 / 3” but this text is not interpreted as a fraction. To get a fraction inserted in the array, you use a run-time array or dynamic array, whose elements are expressions separated by dots and surrounded with { }:

{1 . 2/3 . 7.5}
⇒ #(1 2/3 7.5)

With an array filled with numbers you can request information and arithmetic operations:

#(1 2 3 4) size ⇒ 4
#(1 2 3 4) + 10 ⇒ #(11 12 13 14)
#(1 2 3 4) / 10 ⇒ #(1/10 1/5 3/10 2/5)

Mathematical operations work as well:

#(1 2 3 4) squared ⇒ #(1 4 9 16)
#(0 30 45 60) degreeCos
⇒ #(1.0 0.8660254037844386
0.7071067811865475 0.49999999999999994)

Basic statistical methods can be used directly on arrays of numbers:

#(7.5 3.5 8.9) mean ⇒ 6.633333333333333 
#(7.5 3.5 8.9) range ⇒ 5.4
#(7.5 3.5 8.9) min ⇒ 3.5
#(7.5 3.5 8.9) max ⇒ 8.9

To get an array of natural numbers from 1 to 100, we use the keyword message #to:

(1 to: 100) asArray
⇒ #(1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
92 93 94 95 96 97 98 99 100)

In this line of code, the message #to: is sent to 1 with the argument 100. It returns an interval object. The message #asArray sent to the interval returns an array.

 CuisLogo Create an array of integer numbers ranging from -80 to 50.

Exercise 4.2: Negative integer numbers

The size of an array is fixed, it can not grow. An OrderedCollection is a dynamic, ordered collection. It grows when adding element with the #add: message:

| fibo | 
fibo := OrderedCollection newFrom: #(1 1 2 3).
fibo add: 5;
   add: 8;
   add: 13;
   add: 21.
fibo
⇒ an OrderedCollection(1 1 2 3 5 8 13 21)

Example 4.1: Dynamic size collection

Index access to the elements of a collection is done with a variety of messages. The index naturally ranges from 1 to the collection size:

fibo at: 1 ⇒ 1
fibo at: 6 ⇒ 5
fibo last ⇒ 21
fibo indexOf: 2 ⇒ 3
fibo at: fibo size ⇒ 21

Playing with enumerators

A collection comes with a set of helpful methods named enumerators. Enumerators operate on each element of a collection.

Set operations between two collections are computed with the #union:, #intersection: and #difference: messages.

#(1 2 3 4 5) intersection: #(3 4 5 6 7)
⇒ #(3 4 5)
#(1 2 3 4 5) union: #(3 4 5 6 7)
⇒ a Set(5 4 3 2 7 1 6) 
#(1 2 3 4 5) difference: #(3 4 5 6 7)
⇒ #(1 2)

Example 4.2: Set operations

 CuisLogo Construct the array of the numbers 1,...,24,76,...,100.

Exercise 4.3: Hole in a set

Set operations work with any kind of object. Comparing objects deserves its own section.

#(1 2 3 'e' 5) intersection: #(3.0 4 6 7 'e')
⇒ #(3 'e')

To select the prime numbers from 1 to 100, we use the #select: enumerator. This message is sent to a collection, then it will select each element of the collection returning true to a test condition:

(1 to: 100) select: [ :n | n isPrime ]
⇒  #(2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71
73 79 83 89 97)

Example 4.3: Select prime numbers between 1 and 100

This example introduces the message #select: and a block of code, a primordial constituent element of the Cuis-Smalltalk model. A block of code, delimited by square brackets, is a piece of code for later execution(s). Let’s explain how this script is evaluated:

A block of code can be saved in a variable, passed as a parameter, and can be used multiple times.

| add2 |
add2 := [:n| n + 2].
{ add2 value: 2. add2 value: 7 }.
⇒  #(4 9)

Enumerators implement tremendously powerful ways to process collections without needing an index. By this, we mean that they are simple to get right. We like simple!

To get an idea of how useful enumerators are, take a browse at the Collection class in the method category enumerating.

 CuisLogo Select the odd numbers between -20 and 45.

Exercise 4.4: Odd integers

You want to know the number of prime numbers under 100. Just send the message #size to the answered collection at Example 4.3. The parenthesis are mandatory to ensure #size is sent last to the resulting collection:

( (1 to: 100) select: [:n | n isPrime] ) size
⇒ 25

Example 4.4: Quantity of prime numbers between 1 and 100

For more clarity, we use a variable named primeNumbers to store the prime numbers list we built:

| primeNumbers |
primeNumbers := (1 to: 100) select: [:n | n isPrime].
primeNumbers size

 CuisLogo Modify Example 4.4 to calculate the number of prime numbers between 101 and 200.

Exercise 4.5: Number of prime numbers between 101 and 200

 CuisLogo Build the list of the multiples of 7 below 100.

Exercise 4.6: Multiples of 7

 CuisLogo Build a collection of the odd integers in [1 ; 100] which are not prime.

Exercise 4.7: Odd and non prime integers

A sister enumerator to #select: is #collect:. It returns a new collection of the same size, with each element transformed by a block of code. When searching for perfect cubic roots, it is useful to know about some cubes:

(1 to: 10) collect: [:n | n cubed]
⇒ #(1 8 27 64 125 216 343 512 729 1000)

Example 4.5: Collect cubes

The collected elements can be of a different type. Below, a string is enumerated and integers are collected:

'Bonjour' collect: [:c | c asciiValue ]
⇒  #(66 111 110 106 111 117 114)

We can shift the ASCII value, convert it back to a character, then collect it in a new string. It is a simple cipher:

'Bonjour' collect: [:c | (c asciiValue + 1) asCharacter ]
⇒ 'Cpokpvs'

Example 4.6: Simple cipher

 CuisLogo Write the script to decode cipher ’Zpv!bsf!b!cptt’, it was encoded with Example 4.6.

Exercise 4.8: Cipher decode

Caesar’s cipher is based on shifting letters to the right in the alphabet order. The method is named after Julius Caesar, who used it in his private correspondence with a shift of 3.

 CuisLogo Write a script to collect the alphabet’s uppercase letters representing the Caesar’s cipher. The expected answers is #($D $E $F $G $H $I $J $K $L $M $N $O $P $Q $R $S $T $U $V $W $X $Y $Z $A $B $C).

Exercise 4.9: Alphabet Caesar’s cipher

Once you get the alphabet cipher right, you can encode your first message:

 CuisLogo Encode the phrase ’SMALLTALKEXPRESSION’.

Exercise 4.10: Encode with Caesar’s cipher

And decode message:

 CuisLogo Decode this famous quotation attributed to Julius Caesar ’DOHDMDFWDHVW’.

Exercise 4.11: Decode with Caesar’s cipher

Fun with loops

Collection can be iterated with traditional loops: there is a whole family of repeat, while and for loops.

A simple for loop between two integer values is written with the keyword message #to:do:, the last argument is a block of code executed for each index:

| sequence |
sequence := OrderedCollection new.
1 to: 10 do: [:k | sequence add: 1 / k].
sequence
⇒ an OrderedCollection(1 1/2 1/3 1/4 1/5 1/6 1/7 1/8 1/9 1/10)

Example 4.7: A for loop

A collect writes more concisely, though:

(1 to: 10) collect: [:k | 1 / k]

To step with a different value than 1, a third numeric argument is inserted:

1 to: 10 by: 0.5 do: [:k | sequence add: 1 / k]

A repeated loop without an index or any collection is written with the #timesRepeat: message send to an integer:

| fibo |
fibo := OrderedCollection newFrom: #(1 1).
10 timesRepeat: [
   fibo add: (fibo last + fibo atLast: 2)].
fibo
⇒ an OrderedCollection(1 1 2 3 5 8 13 21 34 55 89 144)

Example 4.8: A repeat loop

The quotient of consecutive Fibonacci terms converges toward the golden value:

fibo pairsDo: [:i :j |
   Transcript show: (j / i ) asFloat ; cr]
⇒ 1.0
⇒ 1.5
⇒ 1.6
⇒ 1.6153846153846154
⇒ 1.6176470588235294
⇒ 1.6179775280898876

Footnotes

(18)

An identifier is just a word that starts in a lowercase letter and consists of upper and lower case letters and decimal digits. All variable names are identifiers