Ninju-streams 1.0
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 +23 -0
- data/Rakefile +32 -0
- data/lib/streams.rb +156 -0
- data/spec/spec_helper.rb +7 -0
- data/spec/streams_spec.rb +430 -0
- data/streams.gemspec +31 -0
- metadata +65 -0
data/README
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
== Description
|
2
|
+
|
3
|
+
Streams is designed to be a lightweight streams processing library written in Ruby. I intend to keep this very small (infact, it's unlikely to change at all), and have added most common methods to make it immediately useable.
|
4
|
+
|
5
|
+
== Example
|
6
|
+
|
7
|
+
require 'streams'
|
8
|
+
|
9
|
+
class Stream
|
10
|
+
def sieve
|
11
|
+
Stream.new( head ) { tail.select { | n | n % head > 0 }.sieve }
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
module Math
|
16
|
+
PRIMES = 2.enumerate.sieve
|
17
|
+
end
|
18
|
+
|
19
|
+
>> Math::PRIMES.at( 30 )
|
20
|
+
=> 127
|
21
|
+
|
22
|
+
>> Math::PRIMES.select { | n | n > 100 }.take( 50 )
|
23
|
+
=> [101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379]
|
data/Rakefile
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "rake"
|
3
|
+
require "echoe"
|
4
|
+
|
5
|
+
Echoe.new( "streams", "1.0" ) do | p |
|
6
|
+
p.description = ""
|
7
|
+
p.url = "http://github.com/Ninju/streams"
|
8
|
+
p.author = "Alex Watt"
|
9
|
+
p.email = "alex.watt@me.com"
|
10
|
+
p.ignore_pattern = []
|
11
|
+
p.development_dependencies = []
|
12
|
+
end
|
13
|
+
|
14
|
+
task :default => "spec:run"
|
15
|
+
|
16
|
+
|
17
|
+
namespace :spec do
|
18
|
+
task :run do
|
19
|
+
system( "spec spec/" )
|
20
|
+
end
|
21
|
+
|
22
|
+
task :doc do
|
23
|
+
system( "spec spec/ --format specdoc" )
|
24
|
+
end
|
25
|
+
|
26
|
+
task :coverage do
|
27
|
+
system( "rcov spec/*_spec.rb" )
|
28
|
+
system( "open coverage/index.html" )
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
task :spec => "spec:run"
|
data/lib/streams.rb
ADDED
@@ -0,0 +1,156 @@
|
|
1
|
+
class Stream
|
2
|
+
attr_reader :head, :delayed_tail
|
3
|
+
|
4
|
+
def initialize( head = nil, &block )
|
5
|
+
@head = head
|
6
|
+
@delayed_tail = block || head && Proc.new { Stream.new }
|
7
|
+
end
|
8
|
+
|
9
|
+
def tail
|
10
|
+
@tail ||= delayed_tail && delayed_tail.call
|
11
|
+
end
|
12
|
+
|
13
|
+
def empty?
|
14
|
+
head.nil?
|
15
|
+
end
|
16
|
+
|
17
|
+
def size
|
18
|
+
return 0 if empty?
|
19
|
+
1 + tail.size
|
20
|
+
end
|
21
|
+
|
22
|
+
def each( &block )
|
23
|
+
return if empty?
|
24
|
+
block.call( head )
|
25
|
+
tail.each( &block )
|
26
|
+
end
|
27
|
+
|
28
|
+
def map( &block )
|
29
|
+
return self if empty?
|
30
|
+
Stream.new( block.call( head ) ) { tail.map( &block ) }
|
31
|
+
end
|
32
|
+
|
33
|
+
def select( &block )
|
34
|
+
return self if empty?
|
35
|
+
|
36
|
+
if block.call( head )
|
37
|
+
return Stream.new( head ) { tail.select( &block ) }
|
38
|
+
end
|
39
|
+
|
40
|
+
tail.select( &block )
|
41
|
+
end
|
42
|
+
|
43
|
+
def reject( &block )
|
44
|
+
select do | element |
|
45
|
+
!block.call( element )
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def merge( other )
|
50
|
+
return self if other.empty?
|
51
|
+
return other if self.empty?
|
52
|
+
Stream.new( head + other.head ) { tail.merge( other.tail ) }
|
53
|
+
end
|
54
|
+
|
55
|
+
def append( other )
|
56
|
+
return self if other.empty?
|
57
|
+
return other if empty?
|
58
|
+
Stream.new( head ) { tail.append( other ) }
|
59
|
+
end
|
60
|
+
|
61
|
+
def take( n )
|
62
|
+
return Stream.new if empty? || n.zero?
|
63
|
+
return Stream.new( head ) if n == 1
|
64
|
+
Stream.new( head ) { tail.take( n - 1 ) }
|
65
|
+
end
|
66
|
+
|
67
|
+
def at( n )
|
68
|
+
raise ArgumentError, "Index out of bounds" if empty? && n >= 0
|
69
|
+
return head if n.zero?
|
70
|
+
tail.at( n - 1 )
|
71
|
+
end
|
72
|
+
|
73
|
+
def drop( n )
|
74
|
+
return self if n.zero?
|
75
|
+
return Stream.new if empty?
|
76
|
+
tail.drop( n - 1 )
|
77
|
+
end
|
78
|
+
|
79
|
+
def uniq
|
80
|
+
return self if empty?
|
81
|
+
Stream.new( head ) { tail.select { | element | element != head }.uniq }
|
82
|
+
end
|
83
|
+
|
84
|
+
def last
|
85
|
+
return nil if empty?
|
86
|
+
return head if tail.empty?
|
87
|
+
tail.last
|
88
|
+
end
|
89
|
+
|
90
|
+
def to_a
|
91
|
+
return [] if empty?
|
92
|
+
[ head ] + tail.to_a
|
93
|
+
end
|
94
|
+
|
95
|
+
def join( separator = "" )
|
96
|
+
return "" if empty?
|
97
|
+
return head.to_s if tail.empty?
|
98
|
+
head.to_s + separator + tail.join( separator )
|
99
|
+
end
|
100
|
+
|
101
|
+
def inspect
|
102
|
+
"[" + join( ", " ) + "]"
|
103
|
+
end
|
104
|
+
|
105
|
+
def include?( element )
|
106
|
+
return false if empty?
|
107
|
+
head == element || tail.include?( element )
|
108
|
+
end
|
109
|
+
|
110
|
+
def ==( other )
|
111
|
+
head == other.head && tail == other.tail
|
112
|
+
end
|
113
|
+
|
114
|
+
def take_while( &block )
|
115
|
+
if block.call( head )
|
116
|
+
Stream.new( head ) { tail.take_while( &block ) }
|
117
|
+
else
|
118
|
+
Stream.new
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def all?( &block )
|
123
|
+
return true if empty?
|
124
|
+
block.call( head ) && tail.all?( &block )
|
125
|
+
end
|
126
|
+
|
127
|
+
def any?( &block )
|
128
|
+
return false if empty?
|
129
|
+
block.call( head ) || tail.any?( &block )
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
class Array
|
134
|
+
def to_stream
|
135
|
+
return Stream.new if empty?
|
136
|
+
Stream.new( first ) { self[ 1..-1 ].to_stream }
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
class Integer
|
141
|
+
def enumerate( step = 1, &block )
|
142
|
+
if block_given?
|
143
|
+
return Stream.new( self ) { block.call( self ).enumerate( &block ) }
|
144
|
+
end
|
145
|
+
Stream.new( self ) { ( self + step ).enumerate( step ) }
|
146
|
+
end
|
147
|
+
|
148
|
+
def enumerate_to( value, step = 1, &block )
|
149
|
+
return Stream.new if ( step >= 0 && self > value ) || ( step < 0 && self < value )
|
150
|
+
if block_given?
|
151
|
+
return Stream.new( self ) { block.call( self ).enumerate_to( value, &block ) }
|
152
|
+
end
|
153
|
+
|
154
|
+
Stream.new( self ) { ( self + step ).enumerate_to( value, step ) }
|
155
|
+
end
|
156
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,430 @@
|
|
1
|
+
require File.join( File.dirname(__FILE__), *%w[spec_helper] )
|
2
|
+
require 'streams'
|
3
|
+
|
4
|
+
describe Stream do
|
5
|
+
it "should create a new instance" do
|
6
|
+
Proc.new { Stream.new }.should_not raise_error
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "basic" do
|
10
|
+
before do
|
11
|
+
@empty_stream = Stream.new
|
12
|
+
@stream = Stream.new( 43 ) { @empty_stream }
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should return the head" do
|
16
|
+
@stream.head.should == 43
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should return the tail" do
|
20
|
+
@stream.tail.should == @empty_stream
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should call the delayed tail" do
|
24
|
+
@stream.delayed_tail.expects( :call ).once.returns( @empty_stream )
|
25
|
+
@stream.tail
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should store the delayed tail in a block" do
|
29
|
+
@stream.delayed_tail.is_a?( Proc ).should be_true
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should not be empty" do
|
33
|
+
@stream.should_not be_empty
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should have the correct size" do
|
37
|
+
@stream.size.should == 1 + @stream.tail.size
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe "stream with head and without tail" do
|
42
|
+
before do
|
43
|
+
@stream = Stream.new( 42 )
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should return the empty stream" do
|
47
|
+
@stream.tail.should be_empty
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should return a stream" do
|
51
|
+
@stream.tail.is_a?( Stream ).should be_true
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe "the empty stream" do
|
56
|
+
before do
|
57
|
+
@empty_stream = Stream.new
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should be empty" do
|
61
|
+
@empty_stream.should be_empty
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should have zero size" do
|
65
|
+
@empty_stream.size.should == 0
|
66
|
+
end
|
67
|
+
|
68
|
+
it "should return nil if you access the tail" do
|
69
|
+
@empty_stream.tail.should be_nil
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
describe "infinite stream" do
|
74
|
+
before do
|
75
|
+
@stream = Stream.new( 1 ) { @stream }
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should return itself" do
|
79
|
+
@stream.stubs( :inspect ).returns( nil )
|
80
|
+
@stream.tail.should === @stream
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
describe "basic stream behaviour: " do
|
85
|
+
before do
|
86
|
+
@stream = Stream.new( 1 ) { Stream.new( 2 ) { Stream.new( 3 ) } }
|
87
|
+
end
|
88
|
+
|
89
|
+
describe "Stream#each" do
|
90
|
+
it "should loop through each element" do
|
91
|
+
array = []
|
92
|
+
@stream.each do | element |
|
93
|
+
array << element
|
94
|
+
end
|
95
|
+
|
96
|
+
@stream.to_a.should == array
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
describe "Stream#map" do
|
101
|
+
it "should add 1 to each element" do
|
102
|
+
add_one = Proc.new { | n | n + 1 }
|
103
|
+
new_stream = @stream.map( &add_one )
|
104
|
+
|
105
|
+
new_stream.head.should == add_one.call( @stream.head )
|
106
|
+
new_stream.tail.should == @stream.tail.map( &add_one )
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
describe "Stream#select" do
|
111
|
+
it "should select elements that return true when the predicate is applied to the element" do
|
112
|
+
even = Proc.new { | n | ( n % 2 ).zero? }
|
113
|
+
|
114
|
+
odd_starting_stream = @stream
|
115
|
+
odd_starting_filtered_stream = odd_starting_stream.select( &even )
|
116
|
+
|
117
|
+
odd_starting_filtered_stream.head.should_not == odd_starting_stream.head
|
118
|
+
odd_starting_filtered_stream.should == odd_starting_stream.tail.select( &even )
|
119
|
+
|
120
|
+
even_starting_stream = Stream.new( 0 ) { odd_starting_stream }
|
121
|
+
even_starting_filtered_stream = even_starting_stream.select( &even )
|
122
|
+
|
123
|
+
even_starting_filtered_stream.head.should == even_starting_stream.head
|
124
|
+
even_starting_filtered_stream.tail.should == even_starting_stream.tail.select( &even )
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
describe "Stream#take" do
|
129
|
+
it "should return a stream composed of the first N elements" do
|
130
|
+
first_two = @stream.take( 2 )
|
131
|
+
first_two.head.should == @stream.head
|
132
|
+
first_two.tail.should == @stream.tail.take( 1 )
|
133
|
+
end
|
134
|
+
|
135
|
+
it "should return an empty stream if the stream is empty" do
|
136
|
+
Stream.new.take( 100 ).should == Stream.new
|
137
|
+
end
|
138
|
+
|
139
|
+
it "should return the empty stream is N is zero" do
|
140
|
+
@stream.take( 0 ).should == Stream.new
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
describe "Stream#at" do
|
145
|
+
it "should raise an ArgumentError if the index is out of bounds" do
|
146
|
+
Proc.new { Stream.new.at( 50 ) }.should raise_error( ArgumentError )
|
147
|
+
end
|
148
|
+
|
149
|
+
it "should raise an ArgumentError if there are no elements in the stream" do
|
150
|
+
Proc.new { Stream.new.at( 0 ) }.should raise_error( ArgumentError )
|
151
|
+
end
|
152
|
+
|
153
|
+
it "should return the element at the Nth position" do
|
154
|
+
@stream.at( 1 ).should == 2
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
describe "Stream#drop" do
|
159
|
+
it "should remove the first n elements from the array" do
|
160
|
+
@stream.drop( 1 ).should == @stream.tail
|
161
|
+
end
|
162
|
+
|
163
|
+
it "should return the empty stream if the amount of elements to drop is greater than the size of the stream" do
|
164
|
+
Stream.new.drop( 1 ).should == Stream.new
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
describe "Stream#last" do
|
169
|
+
it "should return the last element" do
|
170
|
+
@stream.last.should == 3
|
171
|
+
end
|
172
|
+
|
173
|
+
it "should return nil if the stream is empty" do
|
174
|
+
Stream.new.last.should be_nil
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
describe "Stream#uniq" do
|
179
|
+
before do
|
180
|
+
@stream = Stream.new( 1 ) { Stream.new( 2 ) { Stream.new( 2 ) { Stream.new( 1 ) } } }
|
181
|
+
end
|
182
|
+
|
183
|
+
it "should remove duplicate elements" do
|
184
|
+
@stream.uniq.should == Stream.new( 1 ) { Stream.new( 2 ) }
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
describe "Stream#==(other)" do
|
189
|
+
before do
|
190
|
+
@other_stream = @stream.dup
|
191
|
+
end
|
192
|
+
|
193
|
+
it "should return false if the elements are different" do
|
194
|
+
Stream.new.should_not == @stream
|
195
|
+
end
|
196
|
+
|
197
|
+
it "should return true if they have the same elements" do
|
198
|
+
@stream.should == @other_stream
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
describe "Stream#reject" do
|
203
|
+
it "should remove elements which return true when the predicate is applied to the element" do
|
204
|
+
even = Proc.new { | n | ( n % 2 ).zero? }
|
205
|
+
odd_starting_stream = @stream
|
206
|
+
even_starting_stream = Stream.new( 0 ) { odd_starting_stream }
|
207
|
+
|
208
|
+
even_starting_filtered_stream = even_starting_stream.reject( &even )
|
209
|
+
even_starting_filtered_stream.head.should_not == even_starting_stream.head
|
210
|
+
even_starting_filtered_stream.should == even_starting_stream.tail.reject( &even )
|
211
|
+
|
212
|
+
odd_starting_filtered_stream = odd_starting_stream.reject( &even )
|
213
|
+
odd_starting_filtered_stream.head.should == odd_starting_stream.head
|
214
|
+
odd_starting_filtered_stream.tail.should == odd_starting_stream.tail.reject( &even )
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
describe "Stream#merge" do
|
219
|
+
before do
|
220
|
+
@ones = Stream.new( 1 ) { Stream.new( 1 ) }
|
221
|
+
@twos = Stream.new( 2 ) { Stream.new( 2 ) }
|
222
|
+
end
|
223
|
+
|
224
|
+
it "should merge the streams by summing each pairing element and constructing a new stream" do
|
225
|
+
threes = @ones.merge( @twos )
|
226
|
+
threes.head.should == @ones.head + @twos.head
|
227
|
+
|
228
|
+
threes.tail.should == @ones.tail.merge( @twos.tail )
|
229
|
+
end
|
230
|
+
|
231
|
+
it "should return the mergee if the merger is empty" do
|
232
|
+
Stream.new.merge( @ones ).should == @ones
|
233
|
+
end
|
234
|
+
|
235
|
+
it "should return the merger if the mergee is empty" do
|
236
|
+
@ones.merge( Stream.new ).should == @ones
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
describe "Stream#take_while( &block )" do
|
241
|
+
it "should return a stream of the elements to the point where block.call( element ) is false" do
|
242
|
+
@stream.take_while { | n | n == 1 }.should == Stream.new( 1 )
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
describe "Stream#append" do
|
247
|
+
before do
|
248
|
+
@ones = Stream.new( 1 ) { Stream.new( 1 ) }
|
249
|
+
@twos = Stream.new( 2 ) { Stream.new( 2 ) }
|
250
|
+
end
|
251
|
+
|
252
|
+
it "should append the streams" do
|
253
|
+
appended_stream = @ones.append( @twos )
|
254
|
+
appended_stream.head.should == @ones.head
|
255
|
+
appended_stream.tail.should == @ones.tail.append( @twos )
|
256
|
+
end
|
257
|
+
|
258
|
+
it "should return the appendee if the appender is empty" do
|
259
|
+
Stream.new.append( @ones ).should == @ones
|
260
|
+
end
|
261
|
+
|
262
|
+
it "should return the appender if the appendee is empty" do
|
263
|
+
@ones.append( Stream.new ).should == @ones
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
describe "Stream#include?" do
|
268
|
+
it "should return false if the object is not an element in the stream" do
|
269
|
+
Stream.new.should_not include( "Hello, world!" )
|
270
|
+
end
|
271
|
+
|
272
|
+
it "should return true if the object is an element in the stream" do
|
273
|
+
@stream.should include( 1 )
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
describe "Stream#to_a" do
|
278
|
+
it "should return the elements of the stream as an array" do
|
279
|
+
@stream.to_a.should == [ 1, 2, 3 ]
|
280
|
+
end
|
281
|
+
|
282
|
+
it "should return an empty array if the stream is empty" do
|
283
|
+
Stream.new.to_a.should == []
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
describe "Stream#join" do
|
288
|
+
it "should be an empty string if the stream is empty" do
|
289
|
+
Stream.new.join.should == ""
|
290
|
+
end
|
291
|
+
|
292
|
+
it "should print the head as a string if there is only one element" do
|
293
|
+
Stream.new( 1 ).join.should == 1.to_s
|
294
|
+
end
|
295
|
+
|
296
|
+
it "should join the elements by the separator" do
|
297
|
+
@stream.join( ", " ).should == "1, 2, 3"
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
describe "Stream#any?( &block )" do
|
302
|
+
it "should return false if the stream is empty" do
|
303
|
+
Stream.new.any? { | n | true }.should be_false
|
304
|
+
end
|
305
|
+
|
306
|
+
it "should return true if any of the elements return true when passed to the block" do
|
307
|
+
@stream.any? { | n | n == 2 }.should be_true
|
308
|
+
end
|
309
|
+
|
310
|
+
it "should return false if none of the elements return true when passed to the block" do
|
311
|
+
@stream.any? { | n | n < -500 }.should be_false
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
describe "Stream#all?( &block )" do
|
316
|
+
it "should return true if the stream is empty" do
|
317
|
+
Stream.new.all? { | element | false }.should be_true
|
318
|
+
end
|
319
|
+
|
320
|
+
it "should return true if all the elements return true when the block is applied to the element" do
|
321
|
+
@stream.all? { | n | n > 0 }.should be_true
|
322
|
+
end
|
323
|
+
|
324
|
+
it "should return false if any of the elements return false when the block is applied to the element" do
|
325
|
+
@stream.all? { | n | n != 2 }.should be_false
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
329
|
+
describe "Stream#inspect" do
|
330
|
+
it "should show [].to_s if the stream is empty" do
|
331
|
+
Stream.new.inspect.should == "[]"
|
332
|
+
end
|
333
|
+
|
334
|
+
it "should show the elements in an array - stream.to_s.inspect" do
|
335
|
+
@stream.inspect.should == "[1, 2, 3]"
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
it "should cache the tail" do
|
340
|
+
@stream.delayed_tail.expects( :call ).with.once.returns( @stream )
|
341
|
+
2.times { @stream.tail }
|
342
|
+
end
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
describe Array, "Stream helpers" do
|
347
|
+
before do
|
348
|
+
@array = [ 1, 2, 3 ]
|
349
|
+
end
|
350
|
+
|
351
|
+
describe "Array#to_stream" do
|
352
|
+
it "should convert the array to a stream" do
|
353
|
+
@array.to_stream.should be_kind_of( Stream )
|
354
|
+
end
|
355
|
+
|
356
|
+
it "should fill the stream with the correct elements" do
|
357
|
+
stream = @array.to_stream
|
358
|
+
@array.first.should == stream.head
|
359
|
+
@array[ 1..-1 ].to_stream.should == stream.tail
|
360
|
+
end
|
361
|
+
|
362
|
+
it "should return an empty stream if the array is empty" do
|
363
|
+
[].to_stream.should == Stream.new
|
364
|
+
end
|
365
|
+
end
|
366
|
+
end
|
367
|
+
|
368
|
+
describe Integer, "Stream helpers" do
|
369
|
+
describe "Integer#enumerate( step = 1, &block )" do
|
370
|
+
it "should create an infinite stream of the natural numbers" do
|
371
|
+
natural_numbers = 0.enumerate
|
372
|
+
natural_numbers.head.should == 0
|
373
|
+
natural_numbers.tail.head.should == 1
|
374
|
+
natural_numbers.at( 30 ).should == 30
|
375
|
+
end
|
376
|
+
|
377
|
+
it "should return a stream" do
|
378
|
+
2.enumerate.should be_kind_of( Stream )
|
379
|
+
end
|
380
|
+
|
381
|
+
it "should create an infinite stream with the difference between each element being the step passed" do
|
382
|
+
multiples_of_5 = 0.enumerate( 5 )
|
383
|
+
multiples_of_5.head.should == 0
|
384
|
+
multiples_of_5.at( 5 ).should == 25
|
385
|
+
multiples_of_5.at( 10 ).should == 50
|
386
|
+
end
|
387
|
+
|
388
|
+
it "should create an infinite stream with the difference between element being the difference between the proc applied to each element" do
|
389
|
+
powers_of_2 = 1.enumerate { | n | n * 2 }
|
390
|
+
powers_of_2.head.should == 1
|
391
|
+
powers_of_2.at( 2 ).should == 4
|
392
|
+
powers_of_2.at( 4 ).should == 16
|
393
|
+
end
|
394
|
+
|
395
|
+
it "should generate the next number based on the block rather than the step" do
|
396
|
+
multiples_of_10 = 0.enumerate( 5 ) { | n | n + 10 }
|
397
|
+
multiples_of_10.at( 0 ).should == 0
|
398
|
+
multiples_of_10.at( 1 ).should == 10
|
399
|
+
multiples_of_10.at( 2 ).should == 20
|
400
|
+
end
|
401
|
+
end
|
402
|
+
|
403
|
+
describe "Integer#enumerate_to( value, step = 1, &block )" do
|
404
|
+
it "should return a stream" do
|
405
|
+
0.enumerate_to( 100 ).should be_kind_of( Stream )
|
406
|
+
end
|
407
|
+
|
408
|
+
it "should not contain an element less than value if the step is negative" do
|
409
|
+
100.enumerate_to( 0, -3 ).any? { | n | n < 0 }.should_not be_true
|
410
|
+
end
|
411
|
+
|
412
|
+
it "should not contain an element greater than value if the step is positive" do
|
413
|
+
0.enumerate_to( 100, 3 ).any? { | n | n > 100 }.should_not be_true
|
414
|
+
end
|
415
|
+
|
416
|
+
it "should generate the next number based on the block rather than the step" do
|
417
|
+
stream = 0.enumerate_to( 100, 3 ) { | n | n + 11 }
|
418
|
+
stream.head.should == 0
|
419
|
+
stream.at( 1 ).should == 11
|
420
|
+
stream.at( 2 ).should == 22
|
421
|
+
end
|
422
|
+
|
423
|
+
it "should generate the next number based on the proc passed" do
|
424
|
+
stream = 0.enumerate_to( 100 ) { | n | n + 11 }
|
425
|
+
stream.head == 0
|
426
|
+
stream.at( 1 ).should == 11
|
427
|
+
stream.at( 2 ).should == 22
|
428
|
+
end
|
429
|
+
end
|
430
|
+
end
|
data/streams.gemspec
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{streams}
|
5
|
+
s.version = "1.0"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["Alex Watt"]
|
9
|
+
s.date = %q{2009-02-12}
|
10
|
+
s.description = %q{}
|
11
|
+
s.email = %q{alex.watt@me.com}
|
12
|
+
s.extra_rdoc_files = ["lib/streams.rb", "README"]
|
13
|
+
s.files = ["lib/streams.rb", "Rakefile", "README", "spec/spec_helper.rb", "spec/streams_spec.rb", "Manifest", "streams.gemspec"]
|
14
|
+
s.has_rdoc = true
|
15
|
+
s.homepage = %q{http://github.com/Ninju/streams}
|
16
|
+
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Streams", "--main", "README"]
|
17
|
+
s.require_paths = ["lib"]
|
18
|
+
s.rubyforge_project = %q{streams}
|
19
|
+
s.rubygems_version = %q{1.3.1}
|
20
|
+
s.summary = %q{}
|
21
|
+
|
22
|
+
if s.respond_to? :specification_version then
|
23
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
24
|
+
s.specification_version = 2
|
25
|
+
|
26
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
27
|
+
else
|
28
|
+
end
|
29
|
+
else
|
30
|
+
end
|
31
|
+
end
|
metadata
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: Ninju-streams
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: "1.0"
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Alex Watt
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-02-12 00:00:00 -08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: ""
|
17
|
+
email: alex.watt@me.com
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- lib/streams.rb
|
24
|
+
- README
|
25
|
+
files:
|
26
|
+
- lib/streams.rb
|
27
|
+
- Rakefile
|
28
|
+
- README
|
29
|
+
- spec/spec_helper.rb
|
30
|
+
- spec/streams_spec.rb
|
31
|
+
- Manifest
|
32
|
+
- streams.gemspec
|
33
|
+
has_rdoc: true
|
34
|
+
homepage: http://github.com/Ninju/streams
|
35
|
+
post_install_message:
|
36
|
+
rdoc_options:
|
37
|
+
- --line-numbers
|
38
|
+
- --inline-source
|
39
|
+
- --title
|
40
|
+
- Streams
|
41
|
+
- --main
|
42
|
+
- README
|
43
|
+
require_paths:
|
44
|
+
- lib
|
45
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: "0"
|
50
|
+
version:
|
51
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: "1.2"
|
56
|
+
version:
|
57
|
+
requirements: []
|
58
|
+
|
59
|
+
rubyforge_project: streams
|
60
|
+
rubygems_version: 1.2.0
|
61
|
+
signing_key:
|
62
|
+
specification_version: 2
|
63
|
+
summary: ""
|
64
|
+
test_files: []
|
65
|
+
|