stream 0.5
Sign up to get free protection for your applications and to get access to all the features.
- 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
|