December 21, 2024
Iteration Exercise - The Summer Show
I have constructed the following scenario to illustrate certain properties of Smalltalk collection classes. I also wish to demonstrate the use of whileTrue: and timesRepeat: iterating selectors.
Scenario
Summer usually heralds a drop in sales for London's galleries. Their cast of buyers leaves London for more exotic destinations. Similarly, Quad Gallery's owner, Cordelia Pleydell-Bouverie, regularly disappears for a "well earned" six week holiday in Tuscany. However this year she has opted for a break with tradition. In order to tempt the great throng beating its path to the Royal Academy's Summer Show, Cordelia decides to hold her own summer exhibition.
"Small is Beautiful" will consist of small paintings by some of Quad's artists. Cordelia has specified a maximum width of 60 cms for each painting. Furthermore in an attempt to generate sales, she has decided to reduce the price of these paintings by 20 percent. She has asked for a stock list of every painting, by every artist, that matches this description. She also requires that the record of prices, for these paintings, be updated to reflect this reduction.
The gallery holds work in stock by each represented artist. A corresponding record of stock is kept for each artist. Stock consists of paintings, drawings, prints and sculpture. Some artists produce work in all four media, some may simply produce only paintings. Stock records must reflect this diversity. We are going to look at just one artist, Steve Smith. All works of art are considered to be unique. Stock will be added to, or removed, as works of art are sold or returned to the artist.
Solution
A set is deemed suitable for holding Steve's work currently kept in stock. By using a set, there is no danger of duplication. Steve produces paintings, prints and drawings.
sSmithStock := Set new
We have already seen some of Steve's paintings and drawings
Note that individual prints, from the same edition, are regarded as unique. Here are two of his prints
print3 := Print new.
print3 title: 'Rita';
yearCreated: '2000';
height: 24;
width: 20;
media: 'etching';
price: 500.00;
editionSize: 10;
framedPrice: 650.00
print4 := Print new.
print4 title: 'Rita';
yearCreated: '2000';
height: 24;
width: 20;
media: 'etching';
price: 500.00;
editionSize: 10;
framedPrice: 650.00
print3 = print4 answers with false confirming uniqueness of prints from the same edition. Remember that Print inherits = from Object and is therefore a test for object identity, not state.
>= anObject
"Answer whether the receiver and the argument represent
the same
object. If = is redefined in any subclass, consider also
redefining
the message hash."
^self == anObject
Next, by using add: messages, his work is added to the set.
sSmithStock add: painting1;
add:
drawing1;
add:
print3
"etc, etc"
What is needed now is another collection, but one that contains only those works that match the requirements. This can be achieved by the following
sSmithSmallPs := Set new.
sSmithStock do: [:element | ((element class = Painting)
and: [element width <= 60])
ifTrue:
[sSmithSmallPs add: element]]
The explanation for this is as follows:
First we need to create another set, sSmithSmallPs,
to take all the paintings of the required size.
Secondly we use a do: message to loop through
the stock. Each element is accessed in turn and tested for its class with
element class = Painting, before testing that
the width is less than or equal to 60.
The Boolean expression will return either true
or false. Note that by using and:,
as opposed to &, if the element is not a
painting then the argument to and: will not be evaluated.
This is a far more efficient means of testing than the use of &,
because in the latter case the block will be evaluated even if the receiver
is false. One of these two Boolean
objects will now act as the receiver to an ifTrue:
message. This takes as its argument a block, in this case
[sSmithSmallPs add: element]
If true is the receiver, the block
is executed and the element will be added to the new collection.
At this stage all Steve's paintings of 60cms or less in width are held in the set sSmithSmallPs. What is needed is some code that will access all the prices of these paintings and reduce them by 20 percent.
As with many problems there is usually more than one way of arriving at a solution. Here is the simplest
sSmithSmallPs do: [: element | element price: (element price * 0.80)]
Here are two other, lengthy, solutions. I have included them simply to demonstrate a variety of algorithms. The first uses a whileTrue: and the second a timesRepeat: message. In both cases though there is a further problem. A set will not allow us to reference an indexed element by using an at: message. Sets are unindexed and the message at: will cause an error. Help is at hand, because Collection has a number of selectors that allow us to convert collection objects. We need to create an indexed collection in order to use at: and there are several messages that can achieve this including asOrderedCollection. I have chosen this in preference to asArray or asSortedCollection. asArray would constrain the gallery to only those paintings that are currently in stock. Should the gallery request inclusion of other paintings stored in his studio, a fixed size array would prevent this required behaviour. No requirement for sorting has been specified, which rules out asSortedCollection.
|index|
index := 1.
sSmithSmallPs := sSmithSmallPs asOrderedCollection.
[index <= sSmithSmallPs size]
whileTrue: [(sSmithSmallPs at: index)price:
(sSmithSmallPs at: index)price * 0.80.
index
:= index + 1].
^ sSmithSmallPs
A whileTrue: message can be used to solve any
type of looping problem. However caution is needed. The receiver of a
whileTrue: is always a block containing a Boolean
condition. This condition must, at some point in the looping process,
return false. If the block continually returns
true this will cause an infinite loop.
In this example a temporary variable index is
declared. It is initialised to 1 by the assignment
index := 1. The set is sent the message asOrderedCollection
and returns an ordered collection that is immediately assigned to sSmithSmallPs
On the first loop the index having value 1
is compared with the size of the collection by writing
index <= steveSmithSmallPaintings size. If this
returns true the code inside the argument block
is executed.
On the first loop sSmithSmallPs at: index answers
with the object at the index of 1. This instance
of Painting will receive a price:
message. Note that an appropriate message is sent rather than using
direct assignment to access the price instance
variable. Now price: is passed the argument
(sSmithSmallPs at: index)price * 0.80 i.e. 80%
of the current price. Finally, index is incremented
by writing
index := index + 1
This enables the value of index to increase
with each loop until such time as
index <= sSmithSmallPs size answers with false,
at which point the looping will stop.
The expression series will return nil unless
you force a return with
^sSmithSmallPs
The next solution uses a timesRepeat: message. The receiver for this message must be an integer object and takes as its argument a block. We can get an integer by sending a size message to the collection. The integer dictates the number of times the block is executed. Again a temporary variable index is used to reference each of the elements.
|index|
index := 1.
sSmithSmallPs := sSmithSmallPs asOrderedCollection.
(sSmithSmallPs size)
timesRepeat: [(sSmithSmallPs at: index)price:
(sSmithSmallPs at: index)price * 0.80.
index
:= index + 1].
^ sSmithSmallPs
By processing the work of each artist, the gallery can record all the small paintings it holds as stock and readjust their prices.
Finally, sSmithSmallPs can be placed, along with all the other artists' collections, in another set.
smallIsBeautifulSet := Set new.
smallIsBeautifulSet add: sSmithSmallPs
I have written some questions, together with answers, to test your knowledge of collections. You can find them by following these links:
Previous page » Queue and BuyerQueue
⇑ Up to top of page