stream := WriteStream on: (String new: gifts size * "estimated size per
gift" 10).
"The number after #new: is the *initial* allocation; the stream will
reallocate it
 as needed."
gifts allButLastDo: [:each |
   stream nextPutAll: each; nextPutAll: ', '].
stream nextPutAll: 'and '; nextPutAll: gifts last.
^stream contents
Using #allButLastDo: saves making a copy of (most of) gifts.
How could you find out about these things?

You want to join some strings, so you look for a "join" method in String
and you
will find

    join: aCollection
^ self class new: (aCollection size * self size) streamContents: [:stream |
            aCollection
        do: [:each | stream nextPutAll: each asString]
separatedBy: [stream nextPutAll: self]]


You might wonder if there is already something close to what you want, so
you
might enter "commas" into Spotter.  If you did that, you would find
Collection>>asCommaStringAnd so that the whole thing is very nearly
    gifts asCommaStringAnd

Just as the concatenation selector #, works with most kinds of sequences,
so building sequences up using a WriteStream works with most kinds of
sequences.

Let's work through a little example.  We are just going to build up the
string
'Quick' one character at a time.
s := ''.
s := s , 'Q'.   "creates a new string holding 'Q'"
s := s , 'u'.   "creates a new string holding 'Qu'"
s := s , 'i'.   "creates a new string holding 'Qui'"
s := s , 'c'.   "creates a new string holding 'Quic'"
s := s , 'k'.   "creates a new string holding 'Quick'"

You see that building a string of n characters will actually create n new
strings,
all but the last of which will be thrown away, taking O(n**2) time.

Now let's use a stream.
w := WriteStream on: (String new: 4).  "Yes, I know that's too small."
w nextPutAll: 'Q'. "The stream now holds 'Q...' in its buffer."
w nextPutAll: 'u'. "The stream now holds 'Qu..' in its buffer."
w nextPutAll: 'i'. "The stream now holds 'Qui.' in its buffer."
w nextPutAll: 'c'. "The stream now holds 'Quic' in its buffer."
s nextPutAll: 'k'. "There is no room left in the buffer, so the stream
allocates
                    a new buffer twice the size and copies the old one into
it.
                    Now it has 'Quic....' and it has room.
                    The stream now holds 'Quick...' in its buffer."
s := w contents.   "We are asking for the defined elements of the buffer.
                    This means s := buffer copyFrom: 1 to: w position."

You see that building a string of n characters this way requires a minimum
of
two strings, the buffer and the final result.  The buffer may be
periodically
resized, but growing by doubling means the average cost is still O(n).

Let's time these to get an idea.
Time millisecondsToRun: [
|s|
s := ''.
1 to: 10000 do: [:i |
s := s , i printString].
s size]
=> 124
Time millisecondsToRun: [
|w|
w := WriteStream on: (String new: 10000).
1 to: 10000 do: [:i |
w nextPutAll: i printString].
w contents size]
=> 7

This is exactly the reason that Java has both String and StringBuilder.
The tragedy of Java (well, not the only one) is that they had the example
of Smalltalk before them, showing very very clearly that the best way to
handle object to text conversion is to use #printOn: as the primitive,
not #printString, and they *still* went ahead and did the worse thing.
(Ruby has even less excuse for privileging to_s.)

There are quite a few books about Smalltalk available as free PDFs from
the Pharo web site, a wonderful resource.  The Blue Book (Smalltalk-80
The Language and its Implementation) describes streams in Chapter 12.


On Fri, 17 May 2019 at 07:21, Roelof Wobben <r.wob...@home.nl> wrote:

> Hello,
>
> Im testing all my solutions with critiz and can solve almost all problems,
>
> Only this one I cannot figure out.
>
> I have this code
>
> (gifts allButLast
>                 inject: ''
>                 into: [ :str :each | str , each , ', ' ]) , 'and ' , gifts 
> last ]
>
> and critiz says I should use a stream .
>
> How can I make this work ?
>
> Roelof
>
>
>

Reply via email to