matilda-stream 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1 @@
1
+ coverage
@@ -0,0 +1 @@
1
+ 1.9.3-p327
@@ -0,0 +1,5 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - ruby-head
5
+ - jruby-head
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'coveralls', :require => false
4
+
5
+ group :test do
6
+ gem 'rake'
7
+ end
@@ -0,0 +1,27 @@
1
+ GEM
2
+ remote: https://rubygems.org/
3
+ specs:
4
+ colorize (0.5.8)
5
+ coveralls (0.6.7)
6
+ colorize
7
+ multi_json (~> 1.3)
8
+ rest-client
9
+ simplecov (>= 0.7)
10
+ thor
11
+ mime-types (1.23)
12
+ multi_json (1.7.6)
13
+ rake (10.0.3)
14
+ rest-client (1.6.7)
15
+ mime-types (>= 1.16)
16
+ simplecov (0.7.1)
17
+ multi_json (~> 1.0)
18
+ simplecov-html (~> 0.7.1)
19
+ simplecov-html (0.7.1)
20
+ thor (0.18.1)
21
+
22
+ PLATFORMS
23
+ ruby
24
+
25
+ DEPENDENCIES
26
+ coveralls
27
+ rake
@@ -0,0 +1,7 @@
1
+ The MIT License (MIT) Copyright (c) Callum Stott, http://www.seadowg.com
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,52 @@
1
+ # matilda-stream
2
+
3
+ [![Build Status](https://travis-ci.org/seadowg/matilda-stream.png?branch=master)](https://travis-ci.org/seadowg/matilda-stream)
4
+ [![Coverage
5
+ Status](https://coveralls.io/repos/seadowg/matilda-stream/badge.png)](https://coveralls.io/r/seadowg/matilda-stream)
6
+
7
+ Lazy stream implementation for Ruby because Enumerators are kinda silly.
8
+
9
+ ## Description
10
+
11
+ Streams are infinite, lazily evaluated, awesome lists. Here's an example
12
+ of a Stream that represents the set of positive integers:
13
+
14
+ def int_stream(i)
15
+ Stream.new(i) do
16
+ int_stream(i + 1)
17
+ end
18
+ end
19
+
20
+ integers = int_stream(1)
21
+
22
+ You can access streams like any other data structure:
23
+
24
+ integers[0] # => 1
25
+ integers[12] # => 13
26
+ integers[999999] # => 999998
27
+
28
+ Each value will be lazily calculated by materializing the Streams values
29
+ until the requested value. Streams are stateless so a Stream will
30
+ recalculate for every access.
31
+
32
+ You can also create finite Streams from infinite ones:
33
+
34
+ integers.take(5)
35
+
36
+ Finite streams have some extra functionality due to their ability to
37
+ end:
38
+
39
+ integers.take(5).each do |i|
40
+ puts i
41
+ end
42
+
43
+ integers.length # => 5
44
+
45
+ Infinite Streams also support interation and length calculations but you
46
+ will need to be prepared to wait, forever. No seriously, I mean for all
47
+ time. Its infinite.
48
+
49
+ Streams become most powerful when used with high order functions. At the
50
+ moment ruby-stream supports `map`, `filter`, `each`, `take` and `scan` operations that operate
51
+ as they would on normal collections (these operations will only actually be
52
+ applied to Stream elements on access or iteration however).
@@ -0,0 +1,9 @@
1
+ require 'rake/testtask'
2
+
3
+ task :default => :test
4
+
5
+ Rake::TestTask.new do |t|
6
+ t.libs << 'spec'
7
+ t.test_files = FileList['spec/*_spec.rb']
8
+ t.verbose = true
9
+ end
@@ -0,0 +1,178 @@
1
+ class Stream
2
+ def self.continually(&block)
3
+ Stream.new(yield) do
4
+ Stream.continually(&block)
5
+ end
6
+ end
7
+
8
+ attr_reader :head
9
+
10
+ def initialize(head, &block)
11
+ @head = head
12
+ @tail_block = block
13
+ end
14
+
15
+ def tail
16
+ @tail_block.call
17
+ end
18
+
19
+ def last
20
+ result = nil
21
+ self.each do |ele|
22
+ result = ele
23
+ end
24
+
25
+ result
26
+ end
27
+
28
+ def [](n)
29
+ if n == 0
30
+ self.head
31
+ elsif n < 0
32
+ nil
33
+ else
34
+ last_stream = self
35
+ n.times {
36
+ return nil if last_stream.kind_of?(EmptyStream)
37
+ last_stream = last_stream.tail
38
+ }
39
+
40
+ last_stream.head
41
+ end
42
+ end
43
+
44
+ def length
45
+ counter = 0
46
+ self.each { |ele| counter += 1 }
47
+ counter
48
+ end
49
+
50
+ def each(&block)
51
+ last_stream = self
52
+
53
+ until last_stream.kind_of?(EmptyStream)
54
+ block.call(last_stream.head)
55
+ last_stream = last_stream.tail
56
+ end
57
+
58
+ nil
59
+ end
60
+
61
+ def take(n)
62
+ if n <= 0
63
+ EmptyStream.new
64
+ else
65
+ Stream.new(head) do
66
+ tail.take(n - 1)
67
+ end
68
+ end
69
+ end
70
+
71
+ def take_while(&block)
72
+ if block.call(head)
73
+ Stream.new(head) do
74
+ tail.take_while(&block)
75
+ end
76
+ else
77
+ EmptyStream.new
78
+ end
79
+ end
80
+
81
+ def drop(n)
82
+ if n <= 0
83
+ Stream.new(head) do
84
+ tail
85
+ end
86
+ else
87
+ tail.drop(n - 1)
88
+ end
89
+ end
90
+
91
+ def map(&block)
92
+ Stream.new(yield head) do
93
+ tail.map(&block)
94
+ end
95
+ end
96
+
97
+ def scan(zero, &block)
98
+ Stream.new(zero) do
99
+ tail.scan(block.call(zero, head), &block)
100
+ end
101
+ end
102
+
103
+ def fold_left(*args, &block)
104
+ zero = args[0]
105
+ symbol = args[1] || args[0]
106
+
107
+ scan = if block
108
+ self.scan(zero, &block)
109
+ elsif args.length == 2
110
+ self.scan(zero, &symbol.to_proc)
111
+ elsif args.length == 1
112
+ self.drop(1).scan(head, &symbol.to_proc)
113
+ end
114
+
115
+ scan.last
116
+ end
117
+
118
+ alias :inject :fold_left
119
+ alias :reduce :fold_left
120
+
121
+ def filter(&block)
122
+ if block.call(head)
123
+ Stream.new(head) do
124
+ tail.filter(&block)
125
+ end
126
+ else
127
+ tail.filter(&block)
128
+ end
129
+ end
130
+
131
+ class EmptyStream < Stream
132
+ def initialize()
133
+ @head = nil
134
+ end
135
+
136
+ def tail
137
+ EmptyStream.new
138
+ end
139
+
140
+ def last
141
+ nil
142
+ end
143
+
144
+ def [](n)
145
+ nil
146
+ end
147
+
148
+ def each
149
+ nil
150
+ end
151
+
152
+ def take(n)
153
+ EmptyStream.new
154
+ end
155
+
156
+ def drop(n)
157
+ EmptyStream.new
158
+ end
159
+
160
+ def take_while(&block)
161
+ EmptyStream.new
162
+ end
163
+
164
+ def map(&block)
165
+ EmptyStream.new
166
+ end
167
+
168
+ def filter(&block)
169
+ EmptyStream.new
170
+ end
171
+
172
+ def scan(zero, &block)
173
+ Stream.new(zero) do
174
+ EmptyStream.new
175
+ end
176
+ end
177
+ end
178
+ end
@@ -0,0 +1,15 @@
1
+ lib = File.expand_path('../lib/', __FILE__)
2
+ $:.unshift lib unless $:.include?(lib)
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "matilda-stream"
6
+ s.version = "0.4.0"
7
+ s.platform = Gem::Platform::RUBY
8
+ s.authors = ["Callum Stott"]
9
+ s.email = ["callum@seadowg.com"]
10
+ s.summary = "Lazy stream implementation for Ruby"
11
+ s.license = 'MIT'
12
+
13
+ s.require_paths = ['lib']
14
+ s.files = `git ls-files`.split("\n")
15
+ end
@@ -0,0 +1,4 @@
1
+ require 'coveralls'
2
+ Coveralls.wear!
3
+
4
+ require 'minitest/autorun'
@@ -0,0 +1,299 @@
1
+ require 'spec_helper'
2
+ require 'matilda-stream'
3
+
4
+ describe Stream do
5
+ before do
6
+ int_helper = lambda { |i|
7
+ Stream.new(i) {
8
+ int_helper.call(i + 1)
9
+ }
10
+ }
11
+
12
+ @stream = int_helper.call(1)
13
+ end
14
+
15
+ describe "#head" do
16
+ it "returns the first element of the Stream" do
17
+ @stream.head.must_equal 1
18
+ end
19
+ end
20
+
21
+ describe "#tail" do
22
+ it "returns another Stream" do
23
+ @stream.tail.kind_of?(Stream).must_equal true
24
+ end
25
+
26
+ it "returns a Stream with the next element as its head" do
27
+ @stream.tail.head.must_equal 2
28
+ @stream.tail.tail.head.must_equal 3
29
+ end
30
+ end
31
+
32
+ describe "#last" do
33
+ it "returns the last element for finite Stream" do
34
+ @stream.take(5).last.must_equal 5
35
+ end
36
+ end
37
+
38
+ describe "#[](n)" do
39
+ it "calculates the nth element of the stream" do
40
+ @stream[999].must_equal 1000
41
+ end
42
+
43
+ it "returns nil for negative n" do
44
+ @stream[-1].must_equal nil
45
+ end
46
+
47
+ describe "for a finite Stream" do
48
+ it "returns nil for n greater than the limit of the Stream" do
49
+ @stream.take(10)[10].must_equal nil
50
+ end
51
+ end
52
+ end
53
+
54
+ describe "#take(n)" do
55
+ it "returns another Stream" do
56
+ @stream.take(10).kind_of?(Stream).must_equal true
57
+ end
58
+
59
+ it "returns a Stream with the correct #length" do
60
+ @stream.take(10).length.must_equal 10
61
+ end
62
+
63
+ it "returns a Stream with the correct values" do
64
+ stream = @stream.take(10)
65
+ stream[0].must_equal 1
66
+ stream[5].must_equal 6
67
+ stream[7].must_equal 8
68
+ stream[9].must_equal 10
69
+ end
70
+
71
+ it "returns a Stream that can be iterated through finitely" do
72
+ @stream.take(10).each do |element|
73
+ true.must_equal true
74
+ end
75
+ end
76
+
77
+ describe "when operating on the result Stream" do
78
+ it "returns a finite Stream for #map" do
79
+ stream = @stream.take(10).map { |i| i.to_s }
80
+ stream.length.must_equal 10
81
+ end
82
+
83
+ it "returns a finite Stream for #filter" do
84
+ stream = @stream.take(10).filter { |i| true }
85
+ stream.length.must_equal 10
86
+ end
87
+ end
88
+
89
+ describe "for a finite Stream" do
90
+ it "returns a Stream with length limit if n > limit" do
91
+ original = @stream.take(10)
92
+ original.take(100).length.must_equal 10
93
+ end
94
+ end
95
+ end
96
+
97
+ describe "#drop(n)" do
98
+ it "returns another Stream" do
99
+ @stream.drop(1).kind_of?(Stream).must_equal true
100
+ end
101
+
102
+ it "returns a Stream that skips the first n elements of the original" do
103
+ stream = @stream.drop(5)
104
+ stream[0].must_equal 6
105
+ stream[1].must_equal 7
106
+ end
107
+
108
+ describe "for a finite Stream" do
109
+ it "returns a Stream with the correct length" do
110
+ @stream.take(5).drop(1).length.must_equal 4
111
+ end
112
+ end
113
+ end
114
+
115
+ describe "#each(func)" do
116
+ describe "for a finite Stream" do
117
+ it "should return nil" do
118
+ @stream.take(5).each do
119
+ 1
120
+ end.must_equal nil
121
+ end
122
+
123
+ it "should execute the passed block for every element of the stream" do
124
+ i = 0
125
+ @stream.take(5).each do
126
+ i += 1
127
+ end
128
+
129
+ i.must_equal 5
130
+ end
131
+ end
132
+ end
133
+
134
+ describe "#length" do
135
+ it "returns the lenght for a finite array" do
136
+ @stream.take(5).length.must_equal 5
137
+ end
138
+ end
139
+
140
+ describe "#map(func)" do
141
+ it "returns a new Stream" do
142
+ @stream.map(&:to_s).kind_of?(Stream).must_equal true
143
+ end
144
+
145
+ it "returns a new Stream with mapped values" do
146
+ mapped = @stream.map { |i| i + 1 }
147
+ mapped[0].must_equal(2)
148
+ mapped[3].must_equal(5)
149
+ mapped[1000].must_equal(1002)
150
+ end
151
+ end
152
+
153
+ describe "#filter(func)" do
154
+ it "returns a new Stream" do
155
+ @stream.filter { |i| i % 2 == 0 }.kind_of?(Stream).must_equal true
156
+ end
157
+
158
+ it "returns a new Stream with only elements matching the predicate" do
159
+ filtered = @stream.filter { |i| i % 2 == 0 }
160
+ filtered[0].must_equal 2
161
+ filtered[1].must_equal 4
162
+ filtered[3].must_equal 8
163
+ end
164
+ end
165
+
166
+ describe "#take_while(func)" do
167
+ it "returns a new Stream" do
168
+ @stream.take_while { |i| i < 10 }.kind_of?(Stream).must_equal true
169
+ end
170
+
171
+ describe "when the return Stream is finite" do
172
+ it "returns a Stream with the correct length" do
173
+ @stream.take_while { |i| i < 10 }.length.must_equal 9
174
+ end
175
+
176
+ it "returns a Stream that iterates through finitely" do
177
+ stream = @stream.take_while { |i| i < 10 }
178
+ counter = 0
179
+ stream.each { |i| counter += 1 }
180
+ counter.must_equal 9
181
+ end
182
+
183
+ it "returns a finite Stream for #map" do
184
+ stream = @stream.take_while { |i|
185
+ i < 10
186
+ }.map { |i| i.to_s }
187
+
188
+ stream.length.must_equal 9
189
+ end
190
+
191
+ it "returns a finite Stream for #filter" do
192
+ stream = @stream.take_while { |i|
193
+ i < 10
194
+ }.filter { |i| true }
195
+
196
+ stream.length.must_equal 9
197
+ end
198
+ end
199
+ end
200
+
201
+ describe "#scan(zero, func)" do
202
+ it "returns a new Stream" do
203
+ @stream.scan(0) { |x, i| i }.kind_of?(Stream).must_equal true
204
+ end
205
+
206
+ it "returns a Stream with a head equivelant to the passed zero" do
207
+ @stream.scan(-1) { |x, i| i }.head.must_equal -1
208
+ end
209
+
210
+ it "returns a Stream that is the scan of the receiver" do
211
+ scan = @stream.scan(0) { |x, i| x + i }
212
+ scan[1].must_equal(1)
213
+ scan[2].must_equal(3)
214
+ scan[3].must_equal(6)
215
+ scan[100].must_equal(5050)
216
+ end
217
+
218
+ it "works correctly with finite streams" do
219
+ scan = @stream.take(1).scan(0) { |x, i| x + i }
220
+ scan[1].must_equal 1
221
+ end
222
+ end
223
+
224
+ describe "#fold_left(zero, func)" do
225
+ it "returns the left fold for an finite Stream" do
226
+ sum = @stream.take(5).fold_left(1) { |memo, ele|
227
+ memo + ele
228
+ }
229
+ sum.must_equal 16
230
+ end
231
+
232
+ it "returns the zero value for empty lists" do
233
+ value = @stream.take(0).fold_left(101) { |i, j| j }
234
+ value.must_equal 101
235
+ end
236
+
237
+ it "allows the func to be a symbol" do
238
+ sum = @stream.take(5).fold_left(1, :+)
239
+ sum.must_equal 16
240
+ end
241
+
242
+ it "is aliased with 'inject'" do
243
+ @stream.method(:fold_left).must_equal @stream.method(:inject)
244
+ end
245
+
246
+ it "is aliased with 'reduce'" do
247
+ @stream.method(:fold_left).must_equal @stream.method(:reduce)
248
+ end
249
+ end
250
+
251
+ describe "#fold_left(symbol)" do
252
+ it "returns the left fold for a finite Stream but skips the zero value step" do
253
+ sum = @stream.take(5).fold_left(:+)
254
+ sum.must_equal 15
255
+ end
256
+
257
+ it "returns nil for an empty Stream" do
258
+ sum = @stream.take(0).fold_left(:+)
259
+ sum.must_equal nil
260
+ end
261
+ end
262
+
263
+ describe ".continually(func)" do
264
+ it "returns a new Stream" do
265
+ Stream.continually {
266
+ true
267
+ }.kind_of?(Stream).must_equal true
268
+ end
269
+
270
+ it "returns a Stream with the calculated block as each element" do
271
+ stream_block = Proc.new do
272
+ counter = 0
273
+ Stream.continually {
274
+ counter += 1
275
+ }
276
+ end
277
+
278
+ stream_block.call.head.must_equal 1
279
+ stream_block.call.tail.head.must_equal 2
280
+ stream_block.call.tail.tail.head.must_equal 3
281
+ end
282
+ end
283
+
284
+ describe "EmptyStream" do
285
+ it "ends the Stream" do
286
+ stream = lambda { |i|
287
+ Stream.new(i) do
288
+ if i < 1
289
+ Stream::EmptyStream.new
290
+ else
291
+ stream.call(i - 1)
292
+ end
293
+ end
294
+ }
295
+
296
+ stream.call(10).length.must_equal(11)
297
+ end
298
+ end
299
+ end
metadata ADDED
@@ -0,0 +1,58 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: matilda-stream
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.4.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Callum Stott
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-08-07 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description:
15
+ email:
16
+ - callum@seadowg.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - .gitignore
22
+ - .ruby-version
23
+ - .travis.yml
24
+ - Gemfile
25
+ - Gemfile.lock
26
+ - LICENSE.md
27
+ - README.md
28
+ - Rakefile
29
+ - lib/matilda-stream.rb
30
+ - matilda-stream.gemspec
31
+ - spec/spec_helper.rb
32
+ - spec/stream_spec.rb
33
+ homepage:
34
+ licenses:
35
+ - MIT
36
+ post_install_message:
37
+ rdoc_options: []
38
+ require_paths:
39
+ - lib
40
+ required_ruby_version: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ required_rubygems_version: !ruby/object:Gem::Requirement
47
+ none: false
48
+ requirements:
49
+ - - ! '>='
50
+ - !ruby/object:Gem::Version
51
+ version: '0'
52
+ requirements: []
53
+ rubyforge_project:
54
+ rubygems_version: 1.8.23
55
+ signing_key:
56
+ specification_version: 3
57
+ summary: Lazy stream implementation for Ruby
58
+ test_files: []