stream 0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README +174 -0
- data/Rakefile +161 -0
- data/examples/examples.rb +51 -0
- data/examples/streamtester.rb +59 -0
- data/install.rb +24 -0
- data/lib/generator2stream.rb +22 -0
- data/lib/stream.rb +585 -0
- data/test/bm.rb +49 -0
- data/test/testgenerator.rb +24 -0
- data/test/teststream.rb +177 -0
- metadata +64 -0
@@ -0,0 +1,22 @@
|
|
1
|
+
# Let a Generator look like a Stream.
|
2
|
+
require 'stream'
|
3
|
+
require 'generator'
|
4
|
+
|
5
|
+
# The generator is made a Stream by aliasing
|
6
|
+
# the methods at_end?, basic_forward, basic_peek, set_to_begin to the approriate
|
7
|
+
# methods #end?, #next, #peek and #rewind of the Generator class.
|
8
|
+
#
|
9
|
+
# Be careful if you already use a version of Akinori MUSHAs generator.rb. Check out
|
10
|
+
# the version numbers of the one you use and the one comming with the stream package.
|
11
|
+
class Generator
|
12
|
+
include Stream
|
13
|
+
|
14
|
+
alias_method :at_end?, :end?
|
15
|
+
alias_method :basic_forward, :next
|
16
|
+
alias_method :basic_peek, :peek
|
17
|
+
alias_method :set_to_begin, :rewind
|
18
|
+
|
19
|
+
# Returns the generator itself.
|
20
|
+
def create_stream; self; end
|
21
|
+
end
|
22
|
+
|
data/lib/stream.rb
ADDED
@@ -0,0 +1,585 @@
|
|
1
|
+
STREAM_VERSION = "0.5"
|
2
|
+
|
3
|
+
##
|
4
|
+
# Module Stream defines an interface for an external Iterator which
|
5
|
+
# can move forward and backwards. See README for more information.
|
6
|
+
#
|
7
|
+
# The functionality is similar to Smalltalk's ReadStream.
|
8
|
+
|
9
|
+
module Stream
|
10
|
+
include Enumerable
|
11
|
+
|
12
|
+
# This exception is raised when the Stream is requested to move past
|
13
|
+
# the end or beginning.
|
14
|
+
class EndOfStreamException < StandardError; end
|
15
|
+
|
16
|
+
# Returns false if the next #forward will return an element.
|
17
|
+
def at_end?; raise NotImplementedError; end
|
18
|
+
|
19
|
+
# Returns false if the next #backward will return an element.
|
20
|
+
def at_beginning?; raise NotImplementedError; end
|
21
|
+
|
22
|
+
# Move forward one position. Returns the _target_ of current_edge.
|
23
|
+
# Raises Stream::EndOfStreamException if at_end? is true.
|
24
|
+
def forward
|
25
|
+
raise EndOfStreamException if at_end?
|
26
|
+
basic_forward
|
27
|
+
end
|
28
|
+
|
29
|
+
# Move backward one position. Returns the _source_ of current_edge. Raises
|
30
|
+
# Stream::EndOfStreamException if at_beginning? is true.
|
31
|
+
def backward
|
32
|
+
raise EndOfStreamException if at_beginning?
|
33
|
+
basic_backward
|
34
|
+
end
|
35
|
+
|
36
|
+
# Position the stream before its first element, i.e. the next #forward
|
37
|
+
# will return the first element.
|
38
|
+
def set_to_begin
|
39
|
+
until at_beginning?; basic_backward; end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Position the stream behind its last element, i.e. the next #backward
|
43
|
+
# will return the last element.
|
44
|
+
def set_to_end
|
45
|
+
until at_end?; basic_forward; end
|
46
|
+
end
|
47
|
+
|
48
|
+
protected
|
49
|
+
|
50
|
+
def basic_forward; raise NotImplementedError; end
|
51
|
+
def basic_backward; raise NotImplementedError; end
|
52
|
+
|
53
|
+
def basic_current; backward; forward; end
|
54
|
+
def basic_peek; forward; backward; end
|
55
|
+
|
56
|
+
public
|
57
|
+
|
58
|
+
# Move forward until the boolean block is not false and returns the element
|
59
|
+
# found. Returns nil if no object matches.
|
60
|
+
#
|
61
|
+
# This is similar to #detect, but starts the search from the
|
62
|
+
# current position. #detect, which is inherited from Enumerable uses
|
63
|
+
# #each, which implicitly calls #set_to_begin.
|
64
|
+
def move_forward_until
|
65
|
+
until at_end?
|
66
|
+
element = basic_forward
|
67
|
+
return element if yield(element)
|
68
|
+
end
|
69
|
+
nil
|
70
|
+
end
|
71
|
+
|
72
|
+
# Move backward until the boolean block is not false and returns the element
|
73
|
+
# found. Returns nil if no object matches.
|
74
|
+
def move_backward_until
|
75
|
+
until at_beginning?
|
76
|
+
element = basic_backward
|
77
|
+
return element if yield(element)
|
78
|
+
end
|
79
|
+
nil
|
80
|
+
end
|
81
|
+
|
82
|
+
# Returns the element returned by the last call of #forward. If at_beginning? is
|
83
|
+
# true self is returned.
|
84
|
+
def current; at_beginning? ? self : basic_current; end
|
85
|
+
|
86
|
+
# Returns the element returned by the last call of #backward. If at_end? is
|
87
|
+
# true self is returned.
|
88
|
+
def peek; at_end? ? self : basic_peek; end
|
89
|
+
|
90
|
+
# Returns the array [#current,#peek].
|
91
|
+
def current_edge; [current,peek]; end
|
92
|
+
|
93
|
+
# Returns the first element of the stream. This is accomplished by calling
|
94
|
+
# set_to_begin and #forward, which means a state change.
|
95
|
+
def first; set_to_begin; forward; end
|
96
|
+
|
97
|
+
# Returns the last element of the stream. This is accomplished by calling
|
98
|
+
# set_to_begin and #backward, which means a state change.
|
99
|
+
def last; set_to_end; backward; end
|
100
|
+
|
101
|
+
# Returns true if the stream is empty which is equivalent to at_end? and
|
102
|
+
# at_beginning? both being true.
|
103
|
+
def empty?; at_end? and at_beginning?; end
|
104
|
+
|
105
|
+
# Implements the standard iterator used by module Enumerable, by calling
|
106
|
+
# set_to_begin and basic_forward until at_end? is true.
|
107
|
+
def each
|
108
|
+
set_to_begin
|
109
|
+
until at_end?
|
110
|
+
yield basic_forward
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# create_stream is used for each Enumerable to create a stream for it. A Stream as
|
115
|
+
# an Enumerable returns itself.
|
116
|
+
def create_stream; self end
|
117
|
+
|
118
|
+
# A Stream::WrappedStream should return the wrapped stream unwrapped. If the
|
119
|
+
# stream is not a wrapper around another stream it simply returns itself.
|
120
|
+
def unwrapped; self; end
|
121
|
+
|
122
|
+
# The abstract super class of all concrete Classes implementing the Stream
|
123
|
+
# interface. Only used for including module Stream.
|
124
|
+
class BasicStream
|
125
|
+
include Stream
|
126
|
+
end
|
127
|
+
|
128
|
+
# A Singleton class for an empty stream. EmptyStream.instance is the sole instance
|
129
|
+
# which answers true for both at_end? and at_beginning?
|
130
|
+
class EmptyStream < BasicStream
|
131
|
+
require 'singleton'
|
132
|
+
include Singleton
|
133
|
+
|
134
|
+
def at_end?; true; end
|
135
|
+
def at_beginning?; true; end
|
136
|
+
end
|
137
|
+
|
138
|
+
# A CollectionStream can be used as an external iterator for each interger-indexed
|
139
|
+
# collection. The state of the iterator is stored in instance variable @pos.
|
140
|
+
#
|
141
|
+
# A CollectionStream for an array is created by the method Array#create_stream.
|
142
|
+
class CollectionStream < BasicStream
|
143
|
+
attr_reader :pos
|
144
|
+
|
145
|
+
# Creates a new CollectionStream for the indexable sequence _seq_.
|
146
|
+
def initialize(seq)
|
147
|
+
@seq = seq
|
148
|
+
set_to_begin
|
149
|
+
end
|
150
|
+
|
151
|
+
def at_end?; @pos + 1 >= @seq.size; end
|
152
|
+
def at_beginning?; @pos < 0; end
|
153
|
+
|
154
|
+
# positioning
|
155
|
+
|
156
|
+
#
|
157
|
+
def set_to_begin; @pos = -1; end
|
158
|
+
def set_to_end; @pos = @seq.size - 1; end
|
159
|
+
|
160
|
+
def basic_forward; @pos += 1; @seq[@pos]; end
|
161
|
+
def basic_backward; r = @seq[@pos]; @pos -= 1; r; end
|
162
|
+
|
163
|
+
protected
|
164
|
+
|
165
|
+
# basic_current and basic_peek can be implemented more efficiently than in
|
166
|
+
# superclass
|
167
|
+
def basic_current; @seq[@pos]; end
|
168
|
+
def basic_peek; @seq[@pos+1]; end
|
169
|
+
|
170
|
+
end # CollectionStream
|
171
|
+
|
172
|
+
# A simple Iterator for iterating over a sequence of integers starting from
|
173
|
+
# zero up to a given upper bound. Mainly used by Stream::FilteredStream. Could be
|
174
|
+
# made private but if somebody needs it here it is. Is there a better name for it?
|
175
|
+
#
|
176
|
+
# The upper bound is stored in the instance variable @stop which can be incremented
|
177
|
+
# dynamically by the method increment_stop.
|
178
|
+
class IntervalStream < BasicStream
|
179
|
+
attr_reader :pos
|
180
|
+
|
181
|
+
# Create a new IntervalStream with upper bound _stop_. stop - 1 is the last
|
182
|
+
# element. By default _stop_ is zero which means that the stream is empty.
|
183
|
+
def initialize (stop=0)
|
184
|
+
@stop = stop - 1
|
185
|
+
set_to_begin
|
186
|
+
end
|
187
|
+
|
188
|
+
def at_beginning?; @pos < 0; end
|
189
|
+
def at_end?; @pos == @stop; end
|
190
|
+
|
191
|
+
def set_to_end; @pos = @stop; end
|
192
|
+
def set_to_begin; @pos = -1; end
|
193
|
+
|
194
|
+
# Increment the upper bound by incr.
|
195
|
+
def increment_stop (incr=1); @stop += incr; end
|
196
|
+
|
197
|
+
def basic_forward; @pos += 1; end
|
198
|
+
def basic_backward; @pos -= 1; @pos + 1; end
|
199
|
+
end
|
200
|
+
|
201
|
+
# Class WrappedStream is the abstract superclass for stream classes that wrap
|
202
|
+
# another stream. The basic methods are simple delegated to the wrapped
|
203
|
+
# stream. Thus creating a WrappedStream on a CollectionStream would yield an
|
204
|
+
# equivalent stream:
|
205
|
+
#
|
206
|
+
# arrayStream = [1,2,3].create_stream
|
207
|
+
#
|
208
|
+
# arrayStream.to_a => [1,2,3]
|
209
|
+
# Stream::WrappedStream.new(arrayStream).to_a => [1,2,3]
|
210
|
+
class WrappedStream < BasicStream
|
211
|
+
attr_reader :wrapped_stream
|
212
|
+
|
213
|
+
# Create a new WrappedStream wrapping the Stream _otherStream_.
|
214
|
+
def initialize (otherStream)
|
215
|
+
@wrapped_stream = otherStream
|
216
|
+
end
|
217
|
+
|
218
|
+
def at_beginning?; @wrapped_stream.at_beginning?; end
|
219
|
+
def at_end?; @wrapped_stream.at_end?; end
|
220
|
+
|
221
|
+
def set_to_end; @wrapped_stream.set_to_end; end
|
222
|
+
def set_to_begin; @wrapped_stream.set_to_begin; end
|
223
|
+
|
224
|
+
# Returns the wrapped stream unwrapped.
|
225
|
+
def unwrapped; @wrapped_stream.unwrapped; end
|
226
|
+
|
227
|
+
public # but should be protected. Would like to have a friend concept here.
|
228
|
+
def basic_forward; @wrapped_stream.basic_forward; end
|
229
|
+
def basic_backward; @wrapped_stream.basic_backward; end
|
230
|
+
end
|
231
|
+
|
232
|
+
##
|
233
|
+
# A FilteredStream selects all elements which satisfy a given booelan block of
|
234
|
+
# another stream being wrapped.
|
235
|
+
#
|
236
|
+
# A FilteredStream is created by the method #filtered:
|
237
|
+
#
|
238
|
+
# (1..6).create_stream.filtered { |x| x % 2 == 0 }.to_a ==> [2, 4, 6]
|
239
|
+
class FilteredStream < WrappedStream
|
240
|
+
|
241
|
+
# Create a new FilteredStream wrapping _otherStream_ and selecting all its
|
242
|
+
# elements which satisfy the condition defined by the block_filter_.
|
243
|
+
def initialize (otherStream, &filter)
|
244
|
+
super otherStream
|
245
|
+
@filter = filter
|
246
|
+
@positionHolder = IntervalStream.new
|
247
|
+
set_to_begin
|
248
|
+
end
|
249
|
+
|
250
|
+
def at_beginning?; @positionHolder.at_beginning?; end
|
251
|
+
|
252
|
+
# at_end? has to look ahead if there is an element satisfing the filter
|
253
|
+
def at_end?
|
254
|
+
@positionHolder.at_end? and
|
255
|
+
begin
|
256
|
+
if @peek.nil?
|
257
|
+
@peek = wrapped_stream.move_forward_until( &@filter ) or return true
|
258
|
+
@positionHolder.increment_stop
|
259
|
+
end
|
260
|
+
false
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
def basic_forward
|
265
|
+
result =
|
266
|
+
if @peek.nil?
|
267
|
+
wrapped_stream.move_forward_until(&@filter)
|
268
|
+
else
|
269
|
+
# Do not move!!
|
270
|
+
@peek
|
271
|
+
end
|
272
|
+
@peek = nil
|
273
|
+
@positionHolder.forward
|
274
|
+
result
|
275
|
+
end
|
276
|
+
|
277
|
+
def basic_backward
|
278
|
+
wrapped_stream.backward unless @peek.nil?
|
279
|
+
@peek = nil
|
280
|
+
@positionHolder.backward
|
281
|
+
wrapped_stream.move_backward_until(&@filter) or self
|
282
|
+
end
|
283
|
+
|
284
|
+
def set_to_end
|
285
|
+
# Not super which is a WrappedStream, but same behavior as in Stream
|
286
|
+
until at_end?; basic_forward; end
|
287
|
+
end
|
288
|
+
|
289
|
+
def set_to_begin
|
290
|
+
super
|
291
|
+
@peek = nil
|
292
|
+
@positionHolder.set_to_begin
|
293
|
+
end
|
294
|
+
|
295
|
+
# Returns the current position of the stream.
|
296
|
+
def pos; @positionHolder.pos; end
|
297
|
+
end # FilteredStream
|
298
|
+
|
299
|
+
##
|
300
|
+
# Each reversable stream (a stream that implements #backward and at_beginning?) can
|
301
|
+
# be wrapped by a ReversedStream.
|
302
|
+
#
|
303
|
+
# A ReversedStream is created by the method #reverse:
|
304
|
+
#
|
305
|
+
# (1..6).create_stream.reverse.to_a ==> [6, 5, 4, 3, 2, 1]
|
306
|
+
class ReversedStream < WrappedStream
|
307
|
+
|
308
|
+
# Create a reversing wrapper for the reversable stream _otherStream_. If
|
309
|
+
# _otherStream_ does not support backward moving a NotImplementedError is signaled
|
310
|
+
# on the first backward move.
|
311
|
+
def initialize (otherStream)
|
312
|
+
super otherStream
|
313
|
+
set_to_begin
|
314
|
+
end
|
315
|
+
|
316
|
+
# Returns true if the wrapped stream is at_end?.
|
317
|
+
def at_beginning?; wrapped_stream.at_end?; end
|
318
|
+
# Returns true if the wrapped stream is at_beginning?.
|
319
|
+
def at_end?; wrapped_stream.at_beginning?; end
|
320
|
+
|
321
|
+
# Moves the wrapped stream one step backward.
|
322
|
+
def basic_forward; wrapped_stream.basic_backward; end
|
323
|
+
# Moves the wrapped stream one step forward.
|
324
|
+
def basic_backward; wrapped_stream.basic_forward; end
|
325
|
+
|
326
|
+
# Sets the wrapped stream to the beginning.
|
327
|
+
def set_to_end; wrapped_stream.set_to_begin; end
|
328
|
+
# Sets the wrapped stream to the end.
|
329
|
+
def set_to_begin; wrapped_stream.set_to_end; end
|
330
|
+
end
|
331
|
+
|
332
|
+
##
|
333
|
+
# The analog to Enumerable#collect for a stream is a MappedStream wrapping another
|
334
|
+
# stream. A MappedStream is created by the method #collect, thus modifying
|
335
|
+
# the behavior mixed in by Enumerable:
|
336
|
+
#
|
337
|
+
# (1..5).create_stream.collect {|x| x**2}.type ==> Stream::MappedStream
|
338
|
+
# (1..5).collect {|x| x**2} ==> [1, 4, 9, 16, 25]
|
339
|
+
# (1..5).create_stream.collect {|x| x**2}.to_a ==> [1, 4, 9, 16, 25]
|
340
|
+
class MappedStream < WrappedStream
|
341
|
+
|
342
|
+
##
|
343
|
+
# Creates a new MappedStream wrapping _otherStream_ which calls the block
|
344
|
+
# _mapping_ on each move.
|
345
|
+
def initialize (otherStream, &mapping)
|
346
|
+
super otherStream
|
347
|
+
@mapping = mapping
|
348
|
+
end
|
349
|
+
|
350
|
+
# Apply the stored closure for the next element in the wrapped stream and return
|
351
|
+
# the result.
|
352
|
+
def basic_forward; @mapping.call(super); end
|
353
|
+
# Apply the stored closure for the previous element in the wrapped stream and return
|
354
|
+
# the result.
|
355
|
+
def basic_backward; @mapping.call(super); end
|
356
|
+
end
|
357
|
+
|
358
|
+
##
|
359
|
+
# Given a stream of streams. Than a ConcatenatedStream is obtained by concatenating
|
360
|
+
# these in the given order. A ConcatenatedStream is created by the methods
|
361
|
+
# Stream#concatenate or Stream#concatenate_collected send to a stream of streams or
|
362
|
+
# by the method + which concatenats two streams:
|
363
|
+
#
|
364
|
+
# ((1..3).create_stream + [4,5].create_stream).to_a ==> [1, 2, 3, 4, 5]
|
365
|
+
class ConcatenatedStream < WrappedStream
|
366
|
+
alias :streamOfStreams :wrapped_stream
|
367
|
+
private :streamOfStreams
|
368
|
+
|
369
|
+
# Creates a new ConcatenatedStream wrapping the stream of streams _streamOfStreams_.
|
370
|
+
def initialize (streamOfStreams)
|
371
|
+
super
|
372
|
+
set_to_begin
|
373
|
+
end
|
374
|
+
|
375
|
+
# If the current stream is at end, than at_end? has to look ahead to find a non
|
376
|
+
# empty in the stream of streams, which than gets the current stream.
|
377
|
+
def at_end?
|
378
|
+
@currentStream.at_end? and
|
379
|
+
begin
|
380
|
+
until streamOfStreams.at_end?
|
381
|
+
dir, @dirOfLastMove = @dirOfLastMove, :forward
|
382
|
+
s = streamOfStreams.basic_forward
|
383
|
+
# if last move was backwards, then @currentStream is
|
384
|
+
# equivalent to s. Move to next stream.
|
385
|
+
next if dir == :backward
|
386
|
+
s.set_to_begin
|
387
|
+
if s.at_end? # empty stream?
|
388
|
+
next # skip it
|
389
|
+
else
|
390
|
+
@currentStream = s
|
391
|
+
return false # found non empty stream
|
392
|
+
end
|
393
|
+
end
|
394
|
+
reachedBoundary # sets @dirOfLastMove and @currentStream
|
395
|
+
end
|
396
|
+
end
|
397
|
+
|
398
|
+
# Same as at_end? the other way round.
|
399
|
+
def at_beginning?
|
400
|
+
# same algorithm as at_end? the other way round. Could we do it
|
401
|
+
# with metaprogramming?
|
402
|
+
@currentStream.at_beginning? and
|
403
|
+
begin
|
404
|
+
until streamOfStreams.at_beginning?
|
405
|
+
dir, @dirOfLastMove = @dirOfLastMove, :backward
|
406
|
+
s = streamOfStreams.basic_backward
|
407
|
+
next if dir == :forward
|
408
|
+
s.set_to_end
|
409
|
+
if s.at_beginning?
|
410
|
+
next
|
411
|
+
else
|
412
|
+
@currentStream = s
|
413
|
+
return false
|
414
|
+
end
|
415
|
+
end
|
416
|
+
reachedBoundary
|
417
|
+
end
|
418
|
+
end
|
419
|
+
|
420
|
+
def set_to_begin; super; reachedBoundary end
|
421
|
+
def set_to_end; super; reachedBoundary end
|
422
|
+
|
423
|
+
# Returns the next element of @currentStream. at_end? ensured that there is one.
|
424
|
+
def basic_forward; @currentStream.basic_forward end
|
425
|
+
# Returns the previous element of @currentStream. at_beginning? ensured that
|
426
|
+
# there is one.
|
427
|
+
def basic_backward; @currentStream.basic_backward end
|
428
|
+
|
429
|
+
private
|
430
|
+
|
431
|
+
def reachedBoundary
|
432
|
+
@currentStream = EmptyStream.instance
|
433
|
+
@dirOfLastMove = :none # not :forward or :backward
|
434
|
+
true
|
435
|
+
end
|
436
|
+
# Uff, this was the hardest stream to implement.
|
437
|
+
end # ConcatenatedStream
|
438
|
+
|
439
|
+
# An ImplicitStream is an easy way to create a stream on the fly without defining a
|
440
|
+
# subclass of BasicStream. The basic methods required for a stream are defined with
|
441
|
+
# blocks:
|
442
|
+
#
|
443
|
+
# s = Stream::ImplicitStream.new { |s|
|
444
|
+
# x = 0
|
445
|
+
# s.at_end_proc = proc {x == 5}
|
446
|
+
# s.forward_proc = proc {x += 1 }
|
447
|
+
# }
|
448
|
+
#
|
449
|
+
# s.to_a ==> [1, 2, 3, 4, 5]
|
450
|
+
#
|
451
|
+
# Note that this stream is only partially defined since backward_proc and
|
452
|
+
# at_beginning_proc are not defined. It may as well be useful if only moving
|
453
|
+
# forward is required by the code fragment.
|
454
|
+
#
|
455
|
+
# ImplicitStreams can be based on other streams using the method modify
|
456
|
+
# which is for example used in the methods for creating stream wrappers which
|
457
|
+
# remove the first or last element of an existing stream (see remove_first
|
458
|
+
# and remove_last).
|
459
|
+
class ImplicitStream < BasicStream
|
460
|
+
attr_writer :at_beginning_proc, :at_end_proc, :forward_proc, :backward_proc, :set_to_begin_proc, :set_to_end_proc
|
461
|
+
attr_reader :wrapped_stream
|
462
|
+
|
463
|
+
# Create a new ImplicitStream which might wrap an existing stream
|
464
|
+
# _otherStream_. If _otherStream_ is supplied the blocks for the basic stream
|
465
|
+
# methods are initialized with closures that delegate all operations to the
|
466
|
+
# wrapped stream.
|
467
|
+
#
|
468
|
+
# If a block is given to new, than it is called with the new ImplicitStream
|
469
|
+
# stream as parameter letting the client overwriting the default blocks.
|
470
|
+
def initialize (otherStream=nil)
|
471
|
+
if otherStream
|
472
|
+
@wrapped_stream = otherStream
|
473
|
+
@at_beginning_proc = proc {otherStream.at_beginning?}
|
474
|
+
@at_end_proc = proc {otherStream.at_end?}
|
475
|
+
@forward_proc = proc {otherStream.basic_forward}
|
476
|
+
@backward_proc = proc {otherStream.basic_backward}
|
477
|
+
@set_to_end_proc = proc {otherStream.set_to_end}
|
478
|
+
@set_to_begin_proc = proc {otherStream.set_to_begin}
|
479
|
+
end
|
480
|
+
yield self if block_given? # let client overwrite defaults
|
481
|
+
|
482
|
+
@at_beginning_proc = proc {true} unless @at_beginning_proc
|
483
|
+
@at_end_proc = proc {true} unless @at_end_proc
|
484
|
+
end
|
485
|
+
|
486
|
+
# Returns the value of @at_beginning_proc.
|
487
|
+
def at_beginning?; @at_beginning_proc.call; end
|
488
|
+
# Returns the value of @at_end_proc.
|
489
|
+
def at_end?; @at_end_proc.call; end
|
490
|
+
|
491
|
+
# Returns the value of @forward_proc.
|
492
|
+
def basic_forward; @forward_proc.call; end
|
493
|
+
# Returns the value of @backward_proc_proc.
|
494
|
+
def basic_backward; @backward_proc.call; end
|
495
|
+
|
496
|
+
# Calls set_to_end_proc or super if set_to_end_proc is undefined.
|
497
|
+
def set_to_end
|
498
|
+
@set_to_end_proc ? @set_to_end_proc.call : super
|
499
|
+
end
|
500
|
+
|
501
|
+
# Calls set_to_begin_proc or super if set_to_begin_proc is undefined.
|
502
|
+
def set_to_begin
|
503
|
+
@set_to_begin_proc ? @set_to_begin_proc.call : super
|
504
|
+
end
|
505
|
+
end # ImplicitStream
|
506
|
+
|
507
|
+
# Stream creation functions
|
508
|
+
|
509
|
+
##
|
510
|
+
# Return a Stream::FilteredStream which iterates over all my elements
|
511
|
+
# satisfying the condition specified by the block.
|
512
|
+
def filtered (&block); FilteredStream.new(self,&block); end
|
513
|
+
|
514
|
+
# Create a Stream::ReversedStream wrapper on self.
|
515
|
+
def reverse; ReversedStream.new self; end
|
516
|
+
|
517
|
+
# Create a Stream::MappedStream wrapper on self. Instead of returning the stream
|
518
|
+
# element on each move, the value of calling _mapping_ is returned instead. See
|
519
|
+
# Stream::MappedStream for examples.
|
520
|
+
def collect (&mapping); MappedStream.new(self, &mapping); end
|
521
|
+
|
522
|
+
# Create a Stream::ConcatenatedStream on self, which must be a stream of streams.
|
523
|
+
def concatenate; ConcatenatedStream.new self; end
|
524
|
+
|
525
|
+
# Create a Stream::ConcatenatedStream, concatenated from streams build with the
|
526
|
+
# block for each element of self:
|
527
|
+
#
|
528
|
+
# s = [1, 2, 3].create_stream.concatenate_collected { |i|
|
529
|
+
# [i,-i].create_stream
|
530
|
+
# }.
|
531
|
+
# s.to_a ==> [1, -1, 2, -2, 3, -3]
|
532
|
+
def concatenate_collected (&mapping); self.collect(&mapping).concatenate; end
|
533
|
+
|
534
|
+
# Create a Stream::ConcatenatedStream by concatenatating the receiver and _otherStream_
|
535
|
+
#
|
536
|
+
# (%w(a b c).create_stream + [4,5].create_stream).to_a ==> ["a", "b", "c", 4, 5]
|
537
|
+
def + (otherStream)
|
538
|
+
[self, otherStream].create_stream.concatenate
|
539
|
+
end
|
540
|
+
|
541
|
+
# Create a Stream::ImplicitStream which wraps the receiver stream by modifying one
|
542
|
+
# or more basic methods of the receiver. As an example the method remove_first uses
|
543
|
+
# #modify to create an ImplicitStream which filters the first element away.
|
544
|
+
def modify (&block); ImplicitStream.new(self, &block); end
|
545
|
+
|
546
|
+
# Returns a Stream::ImplicitStream wrapping a Stream::FilteredStream, which
|
547
|
+
# eliminates the first element of the receiver.
|
548
|
+
#
|
549
|
+
# (1..3).create_stream.remove_first.to_a ==> [2,3]
|
550
|
+
def remove_first
|
551
|
+
i = 0
|
552
|
+
filter = self.filtered { | element | i += 1; i > 1 }
|
553
|
+
filter.modify { |s|
|
554
|
+
s.set_to_begin_proc = proc {filter.set_to_begin; i = 0}
|
555
|
+
}
|
556
|
+
end
|
557
|
+
|
558
|
+
# Returns a Stream which eliminates the first element of the receiver.
|
559
|
+
#
|
560
|
+
# (1..3).create_stream.remove_last.to_a ==> [1,2]
|
561
|
+
#
|
562
|
+
# <em>Take a look at the source. The implementation is inefficient but elegant.</em>
|
563
|
+
def remove_last
|
564
|
+
self.reverse.remove_first.reverse # I like this one
|
565
|
+
end
|
566
|
+
end
|
567
|
+
|
568
|
+
# extensions
|
569
|
+
|
570
|
+
# The extension on Array could be done for all Objects supporting []
|
571
|
+
# and size.
|
572
|
+
class Array
|
573
|
+
# Creates a new Stream::CollectionStream on self.
|
574
|
+
def create_stream
|
575
|
+
Stream::CollectionStream.new self
|
576
|
+
end
|
577
|
+
end
|
578
|
+
|
579
|
+
module Enumerable
|
580
|
+
# If not an array the enumerable is converted to an array and then
|
581
|
+
# to a stream using a Stream::CollectionStream.
|
582
|
+
def create_stream
|
583
|
+
to_a.create_stream
|
584
|
+
end
|
585
|
+
end
|