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.
Create an array of integer numbers ranging from -80 to 50.
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)
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
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)
Construct the array of the numbers 1,...,24,76,...,100.
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)
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:
(1 to: 100)
is evaluated as an interval
[:n | n isPrime]
is instantiated (created)
#select:
is sent to the interval with the
block of code as the argument
select:
method, for each integer of the
interval, the block of code is invoked with its parameter
n
set to the integer value. A block parameter
starts with a colon, “:”, and is an ordinary identifier 18. Then, each time n
isPrime
evaluates to true, the n
value is added to a new
collection answered when the select:
method finished
testing each element of the collection.
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
.
Select the odd numbers between -20 and 45.
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
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
Modify Example 4.4 to calculate the number of prime numbers between 101 and 200.
Build the list of the multiples of 7 below 100.
Build a collection of the odd integers in [1 ; 100] which are not prime.
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)
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'
Write the script to decode cipher ’Zpv!bsf!b!cptt’, it was encoded with Example 4.6.
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.
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).
Once you get the alphabet cipher right, you can encode your first message:
Encode the phrase ’SMALLTALKEXPRESSION’.
And decode message:
Decode this famous quotation attributed to Julius Caesar ’DOHDMDFWDHVW’.
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)
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)
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
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