In this course, we'll study the essential elements
of the collections hierarchy in Pharo.
We'll see that Pharo has many types of collections.
They make a programmer's life easier
as they all share the same API.
We'll also see the difference between literal and dynamic collections.
The collections' API is very diverse: there are many types of collections.
They all share a common API which is well organized
and make a programmer's life easier.
What's specific about Pharo
is that the first element is at index 1
instead of 0 in other languages.
Collections can contain any types of objects in Pharo.
It's not always the case with other languages.
Let's see some of the most remarkable and widely used collections:
OrderedCollection is a dynamic collection
which grows whenever you add elements in it.
Array is a collection of fixed size.
You can access the elements according to an index, Set,
which contains elements without duplicates.
You can't insert the same element twice.
Dictionaries are hash tables: to one key is associated one value.
This is an excerpt of the collection hierarchy.
It's only an excerpt: in Pharo, it's bigger.
There are many classes. They all have collections
with a shared API for all the collections.
In this course, I'll tell you about those in bold type.
There's a shared API with seven elements:
specific methods for the creation of collections sent to the classes;
specific methods to access their properties
(the size of the collections
or even the elements they contain);
testing methods (is the collection empty?);
elements adding and removing methods; collection elements enumeration
(I want to browse the elements or know whether an element is there);
and conversion methods from one type to another.
Let's start with an example: I want to create a collection in Pharo.
I'll just select the class I'm interested in
and send the message new to instantiate this class.
First option: I use new.
Second option: I can't use new directly.
I want to specify the size of the collection.
I could send new: 4. I create a size-4 or size-2 array.
It also work with OrderedCollection. I could create one of size 1000.
There are other existing methods to create pre-initialized collections:
withAll: for instance.
I'll pass a literal collection. A literal collection starts with #(.
I'll create a new instance of OrderedCollection
which will contain all the elements passed when it was created.
I could do the same with Set.
In a set, you can't have any duplicates.
The number 7, which you have twice in the literal collection,
can't end up twice in Set.
There are other types of messages you can send
to the classes to initialize them.
This is another example: new: withAll:.
I want a size-5 collection.
Every element must be initialized with a specific object:
in this case, a string containing a.
Remember that in Pharo, every collection starts at index 1.
If I ask this 3-element collection
to return the element at index 2, it'll be this one.
This is 1; this is 2; this is 3.
It's the same with OrderedCollection:
If I convert this collection and ask it to return its index 2,
I'll get the same result: hates.
Collections can contain any types of objects.
I'll show you one example:
this literal collection contains the string calvin
and a collection containing the numbers 1 2 3.
I can create an array.
This array is composed of the elements 1 2
and of Set at the end.
I added the element 1 here, then the element 2, then Set.
You can browse the elements of a collection
by using the message do: for instance.
This is a collection
to which I'll send the message do: before passing it a block.
The block starts with "[" and ends with "]".
The block's parameter is called :each.
It is separated from the block's body by |.
With each loop, the value of :each is the collection's 1st element,
then the second, etc.
And on the transcript you have: Calvin hates Suzie.
Arrays are fixed-size collections.
You can query an array for its size: I send the message size.
You can directly access an array's element by sending at:.
I want the second element.
You can modify the element at index 2 in the collection
by sending at: 1 put: 'Calvin':
I insert the string Calvin in cell 1.
I can also ask the size.
What's interesting about this example
is that the same array has been built in two ways:
first a literal version, then a dynamic version.
Here, I instantiated the class Array by myself
and filled in every cell.
You can send size to a collection to learn about its size.
You can access a collection's element by using the method at:.
I already mentioned it.
Be careful:
when you access an element by providing an index,
you must ensure that the index
is within the collection's bounds: it should be smaller than its size.
If I ask this collection to return the element at index 55, it won't exist.
It'll return an error.
To modify the elements:
at index 2, I want to insert a new element in the collection.
The string loves will replace the string hates.
You can see it in the result.
Literal arrays:
this is an example of a literal array.
It starts with #(, as I told you.
You can put anything inside: a number, a string, etc.
Every literal array in Pharo
are instance of the class Array by default.
I can send the message class to a literal array.
It returns Array, which means it's instance of the class Array.
Dynamic and literal versions are equivalent in Pharo.
Literal arrays are simply shorter: you can write them faster.
Here you have a collection's literal version.
And that's the dynamic version where I instantiate the class Array.
They're equivalent as they return the same results.
The class OrderedCollection defines a specific collection
which can be extended.
Whenever you add elements, it grows.
I instantiate OrderedCollection by sending the message new.
I use the method add: to add new elements in this collection.
I could use addFirst: to add an element at the beginning of the collection.
By default, it's at the end.
You can see what the collection returns.
It is composed of three elements: Pharo, Reef, Pharo.
With add: 'Seaside', 'Seaside' is added at the end.
I have conversion methods between a collection type and another.
Here I'm using a literal collection, which is an array.
The message asOrderedCollection will turn this array
into an ordered collection.
Sets are a type of collection without duplicates.
They can be extended: they grow with every added element.
I can use a literal collection I turn into a Set.
I end up with a Set without any duplicates.
I could also choose the dynamic version over the literal one:
Set with: with: creates a Set and fills it with two elements,
that's two Sets each time.
Conversion methods are convenient to turn a collection into something else.
It's always the same: as + the name of the collection you want to get.
Dictionaries are a group of collections key/values:
I associate a value to a key.
They can be extended: they grow with every added element.
There's also an unusual API
when it comes to this collection:
typical at: message; at:ifabsent:
(what do I return if I want to access a specific key that doesn't exist?);
at:put: insert a new value into a specific key.
I can iterate using typical messages such as do:,
but also new messages
such as keysDo: which browses all the dictionary's keys
or keys and values.
For instance, I create an instance of the class Dictionary.
Picture the Dictionary as an array:
to the key January, I associate 31; to the key February, 28;
to the key March, 31.
It's equivalent to a dynamic collection.
You create a dynamic collection using {:
{ and }.
You use arrows to create associations.
This is a symbol.
To the symbol January I associate the number 31.
I have a collection of associations
I turn into a dictionary with asDictionary.
These two ways of creating a dictionary are equivalent.
If I query association for its key,
it will return the key, so the beginning.
It's the same.
If I query an association for its value, it'll only return the value.
This is a pair or an association.
Dictionaries:
if I want to access a specific value in a Dictionary,
I just need to use at: and to specify the key whose value I want.
If the key doesn't exist,
I will get an error in return.
To avoid that, I can use at: ifAbsent:.
I write at: plus a key that doesn't exist in the Dictionary.
If it is absent, it will return this value, 0.
The key doesn't exist in the Dictionary, so I get the value 0.
I can iterate over a Dictionary: if use do: over its elements,
I get the Dictionary's values only. You can't see the keys.
One might wonder why as it's quite strange.
It's very logical, actually.
If you look at the way do:, taking a block, is implemented in Dictionary,
what happens is ^self valuesDo:.
By default, if you apply do: to a Dictionary,
you only browse its values, not the keys.
If I want to browse both,
I must use a specific method called keysAndValuesDo:
which takes a block with two arguments (:k and :v) as parameter.
:k is a key and v: is the value.
The Dictionary is full.
In this course, we learned that
Pharo has plenty of different types of collections available.
The collections have a common vocabulary
when it comes to creating them,
accessing their elements, their size, etc.
It makes a programmer's life easier.
It's easy to convert a collection into another type.
We even learned something more:
when you have questions, it's easy to look into the system, into Pharo,
by reading the code of the classes to learn new classes of collections.