• Aucun résultat trouvé

Enumerating Collections

Dans le document The Art and Science of Smalltalk (Page 89-93)

All subclasses of Collection understand a set of messages called enumerators. These are all to do with performing the same operation on every element of the collection. However, different enumerators have different specific effects.

The enumerators are one of the most powerful and convenient facilities of collections. Knowing how to use them will make writing some pieces of code simpler than you ever imagined possible. In fact, it is these methods which mean that constructions such as 'do-loops' are so rare in Smalltalk code.

We'll summarise the six different types of enumerator here and then explain them in more detail below:

do: — does the same operation on every element of the collection.

collect: —like do: but returns a collection of the results.

select: — tests every element and returns those which pass.

reject: — tests every element and returns those which fail.

detect: — returns the first element which passes the test.

i n j e c t : into: — feeds the result of one operation into the next.

do:

The most basic of all the enumerators is do:. It simply repeats the same operation for every element in a collection. For example, the expression:

MyCollection do: [:piece | piece reset].

sends the message reset to every element in MyCollection . For the purpose of this example we neither know nor care what reset does.

The code inside the square brackets is a block of Smalltalk which gets executed once for each element in MyCollection. The word :piece before the vertical bar |, simply says that we'll be using the name piece to refer to each element in turn in the code after the | . Every time the block is executed the next element of the collection is substituted for piece in the expression piece reset.

You can see that the do: message allows us to iterate through MyCollecfcion very simply, without knowing how big it is, without having to set up our own loop, and without having to increment and test a counter as we might in other languages. However, if you really want to set up a control structure similar to a do-loop in other languages, you

could send the same do: message to an instance of the class Interval. Try this:

(10 to: 35 by: 5) do:

[:i | Transcript show: i printString; cr].

You can use do: with any kind of collection, but bear in mind that with collections which don't have any defined order (Bag, Set, Dictionary), the elements will be processed in an unpredictable order. Usually this doesn't matter, but be careful you don't do something which relies on processing the elements in a particular order, or in an order which is repeatable.

It is very important not to modify the collection you are enumerating whilst you are iterating over it. This means you must not add, or remove elements from the collection (it is fine to modify the elements themselves). It is all too easy to write code like:

Fruits do: [:fruit | fruit isOrange ifTrue:

[Fruits remove: fruit 3 ] .

which attempts, quite reasonably, to remove all fruits which are oranges from Fruits. This will not work as expected, since it is changing the size of the collection Fruits as it goes along, giving unpredictable results. If you want to perform this kind of operation, you should iterate over Fruits building up a new collection containing all the fruits which are not oranges and then replace the old collection with the new one.

collect:

Another enumerator is collect: which is like do: except that it builds up a new collection which contains all the results of performing the same operation on each element in the collection. For example the expression:

Names := People collect: [:person | person name].

would create a new collection (Names) containing the results of sending the message name to each element in the collection People.

The new collection which is created will usually have the same class as the collection which was enumerated. So if People was an OrderedCollection, Names would be an OrderedCollection as well.

The Collection Classes

select:

Next, we have select: which creates a new collection of just those elements of the enumerated collection which made the block true. This actually does what we wanted above. The expression:

Oranges := Fruits select: [:fruit | fruit isOrange].

would collect into Oranges all the fruits which answered true to the message isOrange. The opposite is reject: which collects all the elements which make the block false.

detect:

Easily confused with the other iterators is detect:, which stops the enumeration as soon as the first element which makes the block return true has been found, and returns that element. If detect: doesn't find such an element it generates an error. Use detect:ifNone: to avoid this. For example the code:

Winner := Employees

detect: [:worker | worker age > 60]

i f N o n e : [Employees last].

would pick out the first employee whose age was greater than sixty. If nobody was older than sixty, the last employee in the collection would be returned.

inject:into:

The last enumerator, inject:into: is rather more complicated than the others. It allows the result of executing the block using the previous element to be 'injected' into the block for the next element. The classic example sums all the values in a collection of numbers:

Total := Numbers inject: 0 into:

[:subTotal :number| subTotal + number].

It is not trivial to understand how this works, and in practice inject: into: is not nearly so frequently used as the other enumerators so don't worry if it doesn't make much sense. Don't worry either if you can never remember the name of the enumerator you want, or you pick the wrong one the first time. Even experienced Smalltalk programmers have to browse the Collection class (enumerating protocol) to remind themselves which one they need!

The collection classes form a large and complex hierarchy under the class Collection. However, out of all the collection classes in the system you will probably find yourself using only about five or six on a regular basis. You can use collections to group together diverse sets of objects, and to implement some basic control structures (enumerating and looping).

Some of what we've discussed will become very familiar to you, and whilst you may forget the details of the rest, as long as you remember the types of feature that exist you'll be able to use the facilities of the various code browsers to track down the details when you need them.

The collection classes are some of the most useful in the system, and we have laboured some of the details for that reason. However, the basic features of inserting, retrieving, removing, testing for and enumerating elements are common to all collections and are well worth exploring and remembering.

Finally, the whole collection hierarchy forms a good example of how to abstract behaviour into superclasses, and share code among classes using inheritance. We'll be discussing how to use inheritance in Part II, and coming back to look at the collection classes and how they're implemented would be a good idea then. In the meantime, we'll continue our more detailed look into the class library by considering the dependency mechanism.

Dans le document The Art and Science of Smalltalk (Page 89-93)