b-lazy 0.1.1 → 0.1.2
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 +14 -0
- data/b-lazy.gemspec +15 -0
- data/lib/b-lazy.rb +28 -0
- data/specs/blazy_spec.rb +0 -0
- data/specs/enumerable_spec.rb +298 -0
- data/specs/integer_spec.rb +64 -0
- metadata +7 -2
data/README
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
=========================================================
|
2
|
+
= b-lazy : Why work hard for lazy evaluation? =
|
3
|
+
=========================================================
|
4
|
+
|
5
|
+
b-lazy extends a handful of core Ruby objects to provide
|
6
|
+
a more natural syntax for lazy evaluation.
|
7
|
+
|
8
|
+
|
9
|
+
Installation:
|
10
|
+
==================
|
11
|
+
|
12
|
+
gem install --remote b-lazy
|
13
|
+
|
14
|
+
|
data/b-lazy.gemspec
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
|
2
|
+
Gem::Specification.new do |s|
|
3
|
+
s.name = 'b-lazy'
|
4
|
+
s.version = '0.1.2'
|
5
|
+
s.date = '2011-02-04'
|
6
|
+
s.summary = "Why work hard for lazy-evaluation?"
|
7
|
+
s.description = "Extends core Ruby objects to provide inherent support for lazy-evaluation."
|
8
|
+
s.authors = ["Brian Lauber"]
|
9
|
+
s.email = 'constructible.truth@gmail.com'
|
10
|
+
s.homepage = 'http://b-lazy.rubyforge.org'
|
11
|
+
s.files = Dir['{specs/*,lib/*}'] + ['README','b-lazy.gemspec']
|
12
|
+
s.required_ruby_version = '>= 1.9.1'
|
13
|
+
s.rubyforge_project = 'b-lazy'
|
14
|
+
end
|
15
|
+
|
data/lib/b-lazy.rb
CHANGED
@@ -300,6 +300,10 @@ module Enumerable
|
|
300
300
|
end
|
301
301
|
|
302
302
|
|
303
|
+
|
304
|
+
|
305
|
+
|
306
|
+
|
303
307
|
# When #to_enum is called on an Enumerator, it creates a copy
|
304
308
|
# and rewinds it (when possible). Unfortunately, this is
|
305
309
|
# not actually the behavior that we want; we just want to make
|
@@ -312,6 +316,9 @@ module Enumerable
|
|
312
316
|
self.to_enum
|
313
317
|
end
|
314
318
|
end
|
319
|
+
|
320
|
+
|
321
|
+
|
315
322
|
|
316
323
|
end
|
317
324
|
|
@@ -344,3 +351,24 @@ class Integer
|
|
344
351
|
end
|
345
352
|
|
346
353
|
end
|
354
|
+
|
355
|
+
|
356
|
+
|
357
|
+
|
358
|
+
module BLazy
|
359
|
+
|
360
|
+
# Occassionally, we find that it is necessary to compute all values upfront
|
361
|
+
# before the enumeration. For example, if we are extracting lines from a
|
362
|
+
# file, then we might decide that guaranteeing that the file is closed cleanly
|
363
|
+
# is more important than lazily enumerating.
|
364
|
+
#
|
365
|
+
# This function serves as a compromise: compute all of the values, but only
|
366
|
+
# do so if I attempt to enumerate over them.
|
367
|
+
def when_needed(&f)
|
368
|
+
Enumerator.new do |out|
|
369
|
+
values = f.call
|
370
|
+
values.each{|v| out.yield v}
|
371
|
+
end
|
372
|
+
end
|
373
|
+
|
374
|
+
end
|
data/specs/blazy_spec.rb
ADDED
File without changes
|
@@ -0,0 +1,298 @@
|
|
1
|
+
|
2
|
+
require 'b-lazy'
|
3
|
+
|
4
|
+
describe Enumerable do
|
5
|
+
|
6
|
+
let(:list) { (1..10).to_a }
|
7
|
+
let(:mapping) { Proc.new{|x| 2 * x} }
|
8
|
+
let(:filter) { Proc.new{|x| x % 2 == 0} }
|
9
|
+
|
10
|
+
let(:left) { 3 }
|
11
|
+
let(:left_cond) { Proc.new{|x| x >= left} }
|
12
|
+
|
13
|
+
let(:right) { 8 }
|
14
|
+
let(:right_cond) { Proc.new{|x| x <= right} }
|
15
|
+
|
16
|
+
|
17
|
+
context "#lmap" do
|
18
|
+
|
19
|
+
it "produces one value for each input" do
|
20
|
+
list.lmap(&mapping).to_a.size.should == list.size
|
21
|
+
end
|
22
|
+
|
23
|
+
it "yields the same elements as #map" do
|
24
|
+
list.lmap(&mapping).to_a.should == list.map(&mapping)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
context "#touch" do
|
30
|
+
it "ignores the return value of the block" do
|
31
|
+
list.touch{|x| 'ignore'}.to_a.should == list
|
32
|
+
end
|
33
|
+
|
34
|
+
it "allows for the modification of object state" do
|
35
|
+
[[1, 2, 3], [4, 5, 6]].touch{|x| x.pop}.to_a.should == [[1, 2], [4, 5]]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
context "#lselect" do
|
41
|
+
it "yields the same elements as #select" do
|
42
|
+
list.lselect(&filter).to_a.should == list.select(&filter)
|
43
|
+
end
|
44
|
+
|
45
|
+
it "returns all elements when every element satisfies the filter" do
|
46
|
+
list.lselect{true}.to_a.should == list
|
47
|
+
end
|
48
|
+
|
49
|
+
it "returns no elements when none of the elements satisfy the filter" do
|
50
|
+
list.lselect{false}.should be_empty
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
context "#lreject" do
|
56
|
+
it "yields the same elements as #reject" do
|
57
|
+
list.lreject(&filter).to_a.should == list.reject(&filter)
|
58
|
+
end
|
59
|
+
|
60
|
+
it "returns all elements when none of the elements satisfy the filter" do
|
61
|
+
list.lreject{false}.to_a.should == list
|
62
|
+
end
|
63
|
+
|
64
|
+
it "returns no elements when every element satisfies the filter" do
|
65
|
+
list.lreject{true}.should be_empty
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
|
71
|
+
|
72
|
+
context "#start_when" do
|
73
|
+
|
74
|
+
it "starts with the element that satisfied the condition" do
|
75
|
+
list.start_when(&left_cond).peek.should == left
|
76
|
+
end
|
77
|
+
|
78
|
+
it "does not care if subsequent elements fail the condition" do
|
79
|
+
[1, 2, 3, 2, 1].start_when(&left_cond).reject(&left_cond).should_not be_empty
|
80
|
+
end
|
81
|
+
|
82
|
+
it "yields no elements if the condition is never satisfied" do
|
83
|
+
list.start_when{false}.should be_empty
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
context "#start_after" do
|
90
|
+
|
91
|
+
it "starts with the element after the one that satisfied the condition" do
|
92
|
+
list.start_after(&left_cond).peek.should == left + 1
|
93
|
+
end
|
94
|
+
|
95
|
+
it "does not care if subsequent elements fail the condition" do
|
96
|
+
[1, 2, 3, 2, 1].start_after(&left_cond).reject(&left_cond).should_not be_empty
|
97
|
+
end
|
98
|
+
|
99
|
+
it "yields no elements if the condition is never satisfied" do
|
100
|
+
list.start_after{false}.should be_empty
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
|
105
|
+
|
106
|
+
|
107
|
+
|
108
|
+
context "#do_while" do
|
109
|
+
|
110
|
+
it "only yields elements for which the condition is true" do
|
111
|
+
list.do_while(&right_cond).reject(&right_cond).should be_empty
|
112
|
+
end
|
113
|
+
|
114
|
+
it "stops yielding elements once the condition becomes false" do
|
115
|
+
[1, 2, 3, 4, 5].do_while{|x| x <= 3}.to_a.should == [1, 2, 3]
|
116
|
+
end
|
117
|
+
|
118
|
+
it "yields all elements if the condition is always satisfied" do
|
119
|
+
list.do_while{true}.to_a.should == list
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
|
124
|
+
context "#do_until" do
|
125
|
+
it "only yields elements for which the condition is false" do
|
126
|
+
list.do_until(&left_cond).select(&left_cond).should be_empty
|
127
|
+
end
|
128
|
+
|
129
|
+
it "stops yielding elements once the condition becomes true" do
|
130
|
+
[1, 2, 3, 4, 5].do_until{|x| x >= 3}.to_a.should == [1, 2]
|
131
|
+
end
|
132
|
+
|
133
|
+
|
134
|
+
it "yields no elements if the condition is immediately satisfied" do
|
135
|
+
list.do_until{true}.should be_empty
|
136
|
+
end
|
137
|
+
|
138
|
+
|
139
|
+
it "yields all elements if the condition is never satisfied" do
|
140
|
+
list.do_until{false}.to_a.should == list
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
|
145
|
+
context "#stop_when" do
|
146
|
+
it "only satisfies the condition with its final element" do
|
147
|
+
results = list.stop_when(&left_cond).to_a.map(&left_cond)
|
148
|
+
|
149
|
+
# The last element is true
|
150
|
+
results[-1].should == true
|
151
|
+
|
152
|
+
# All other elements are false
|
153
|
+
results.pop
|
154
|
+
results.should == [false] * results.length
|
155
|
+
end
|
156
|
+
|
157
|
+
it "yields exactly one element if the condition is immediately true" do
|
158
|
+
list.stop_when{true}.to_a.length.should == 1
|
159
|
+
end
|
160
|
+
|
161
|
+
it "yields all elements if the condition is never satisfied" do
|
162
|
+
list.stop_when{false}.to_a.should == list
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
|
167
|
+
context "#skip" do
|
168
|
+
it "yields all elements when n = 0" do
|
169
|
+
list.skip(0).to_a.should == list
|
170
|
+
end
|
171
|
+
|
172
|
+
it "yields nothing when n exceeds the number of elements" do
|
173
|
+
list.skip(list.size + 100).to_a.should be_empty
|
174
|
+
end
|
175
|
+
|
176
|
+
it "yields [total elements - n] elements when n <= [total elements]" do
|
177
|
+
list.skip(3).to_a.size.should == list.size - 3
|
178
|
+
end
|
179
|
+
|
180
|
+
it "omits the first n elements" do
|
181
|
+
list.skip(3).to_a.should == list[3..-1]
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
|
186
|
+
context "#cons" do
|
187
|
+
|
188
|
+
it "combines the elements of Enumerable objects into one Enumerator" do
|
189
|
+
[[1, 2, 3], [4, 5], [6]].cons.to_a.should == [1, 2, 3, 4, 5, 6]
|
190
|
+
end
|
191
|
+
|
192
|
+
it "yields nothing when given an empty Enumerable object" do
|
193
|
+
[].cons.should be_empty
|
194
|
+
end
|
195
|
+
|
196
|
+
end
|
197
|
+
|
198
|
+
|
199
|
+
context "#weave" do
|
200
|
+
|
201
|
+
it "repeatedly takes one element from each Enumerable" do
|
202
|
+
[[1, 2, 3], [4, 5, 6], [7, 8, 9]].weave.to_a.should == [1, 4, 7, 2, 5, 8, 3, 6, 9]
|
203
|
+
end
|
204
|
+
|
205
|
+
it "discards an Enumerable once it is empty" do
|
206
|
+
[[1, 2, 3], [4], [7, 8, 9]].weave.to_a.should == [1, 4, 7, 2, 8, 3, 9]
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
|
211
|
+
context "#diagonalize" do
|
212
|
+
|
213
|
+
it "yields nothing when all Enumerables are empty" do
|
214
|
+
[[], [], []].diagonalize.should be_empty
|
215
|
+
end
|
216
|
+
|
217
|
+
|
218
|
+
|
219
|
+
it "gradually introduces new Enumerables" do
|
220
|
+
source = [(1..10).to_a] * 10
|
221
|
+
source.diagonalize.take(10).should == [1, 1, 2, 1, 2, 3, 1, 2, 3, 4]
|
222
|
+
end
|
223
|
+
|
224
|
+
it "handles size mismatches without any surprises" do
|
225
|
+
|
226
|
+
end
|
227
|
+
|
228
|
+
end
|
229
|
+
|
230
|
+
|
231
|
+
context "#randomly" do
|
232
|
+
|
233
|
+
it "yields each element exactly once" do
|
234
|
+
r = list.randomly.to_a
|
235
|
+
list.each{|x| r.count(x).should == 1}
|
236
|
+
end
|
237
|
+
|
238
|
+
it "yields the elements in a random order" do
|
239
|
+
# This test has a 1 in 2^100 chance of failing. Meh.
|
240
|
+
(1..100).randomly.to_a.should_not == (1..100)
|
241
|
+
end
|
242
|
+
|
243
|
+
end
|
244
|
+
|
245
|
+
|
246
|
+
context "#ltranspose" do
|
247
|
+
|
248
|
+
it "yields arrays with length equal to the number of Enumerables provided" do
|
249
|
+
[[1, 2, 3], [4, 5, 6]].ltranspose.map{|x| x.size}.should == [2, 2, 2]
|
250
|
+
end
|
251
|
+
|
252
|
+
it "yields zero elements when one of its Enumerables is already empty" do
|
253
|
+
[[1, 2, 3], [], [7, 8, 9]].ltranspose.should be_empty
|
254
|
+
end
|
255
|
+
|
256
|
+
end
|
257
|
+
|
258
|
+
|
259
|
+
context "#cycle" do
|
260
|
+
it "indefinitely repeats the same sequence" do
|
261
|
+
(1..3).cycle.take(9).should == [1, 2, 3, 1, 2, 3, 1, 2, 3]
|
262
|
+
end
|
263
|
+
|
264
|
+
it "yields nothing when performed on an empty Enumerator" do
|
265
|
+
[].cycle.take(3).should == []
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
context "#repeat" do
|
270
|
+
|
271
|
+
it "repeats the sequence exactly n times" do
|
272
|
+
(1..3).repeat(3).to_a.should == [1, 2, 3, 1, 2, 3, 1, 2, 3]
|
273
|
+
end
|
274
|
+
|
275
|
+
it "yields nothing when n is less than 1" do
|
276
|
+
(1..3).repeat(0).to_a.should == []
|
277
|
+
end
|
278
|
+
|
279
|
+
it "treats floating-point values of n as floor(n)" do
|
280
|
+
(1..3).repeat(2.2).to_a.should == [1, 2, 3, 1, 2, 3]
|
281
|
+
end
|
282
|
+
|
283
|
+
end
|
284
|
+
|
285
|
+
|
286
|
+
context "#ensure_enum" do
|
287
|
+
it "returns self when called on an Enumerator" do
|
288
|
+
x = [1, 2, 3].each
|
289
|
+
x.ensure_enum.should be_equal(x)
|
290
|
+
end
|
291
|
+
|
292
|
+
it "returns a new Enumerator when called on an Enumerable" do
|
293
|
+
x = [1, 2, 3]
|
294
|
+
x.ensure_enum.should_not be_equal(x)
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
|
2
|
+
require 'b-lazy'
|
3
|
+
|
4
|
+
describe Integer do
|
5
|
+
|
6
|
+
context "#positives" do
|
7
|
+
it "starts at 1" do
|
8
|
+
Integer.positives.next.should == 1
|
9
|
+
end
|
10
|
+
|
11
|
+
it "produces an ascending sequence" do
|
12
|
+
Integer.positives.grab(10).should == [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
context "#non_negatives" do
|
18
|
+
it "starts at 0" do
|
19
|
+
Integer.non_negatives.next.should == 0
|
20
|
+
end
|
21
|
+
|
22
|
+
it "produces an ascending sequence" do
|
23
|
+
Integer.non_negatives.grab(10).should == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
|
29
|
+
|
30
|
+
|
31
|
+
context "#negatives" do
|
32
|
+
it "starts at -1" do
|
33
|
+
Integer.negatives.next.should == -1
|
34
|
+
end
|
35
|
+
|
36
|
+
it "produces a descending sequence" do
|
37
|
+
Integer.negatives.grab(10).should == [-1, -2, -3, -4, -5, -6, -7, -8, -9, -10]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
context "#non_positives" do
|
43
|
+
it "starts at 0" do
|
44
|
+
Integer.non_positives.next.should == 0
|
45
|
+
end
|
46
|
+
|
47
|
+
it "produces an ascending sequences" do
|
48
|
+
Integer.non_positives.grab(10).should == [0, -1, -2, -3, -4, -5, -6, -7, -8, -9]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
|
54
|
+
context "#all" do
|
55
|
+
it "starts at 0" do
|
56
|
+
Integer.all.next.should == 0
|
57
|
+
end
|
58
|
+
|
59
|
+
it "alternates between positives and negatives" do
|
60
|
+
Integer.all.grab(10).should == [0, 1, -1, 2, -2, 3, -3, 4, -4, 5]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 1
|
8
|
-
-
|
9
|
-
version: 0.1.
|
8
|
+
- 2
|
9
|
+
version: 0.1.2
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Brian Lauber
|
@@ -27,7 +27,12 @@ extensions: []
|
|
27
27
|
extra_rdoc_files: []
|
28
28
|
|
29
29
|
files:
|
30
|
+
- specs/enumerable_spec.rb
|
31
|
+
- specs/blazy_spec.rb
|
32
|
+
- specs/integer_spec.rb
|
30
33
|
- lib/b-lazy.rb
|
34
|
+
- README
|
35
|
+
- b-lazy.gemspec
|
31
36
|
has_rdoc: true
|
32
37
|
homepage: http://b-lazy.rubyforge.org
|
33
38
|
licenses: []
|