ruby-stream 0.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.
- checksums.yaml +7 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +10 -0
- data/README.md +50 -0
- data/Rakefile +9 -0
- data/lib/ruby-stream.rb +123 -0
- data/ruby-stream.gemspec +14 -0
- data/spec/ruby-stream_spec.rb +197 -0
- metadata +50 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 671bd48c3710f55b50a919a2e5649f04e431e576
|
4
|
+
data.tar.gz: f8af1124273e7f59bd118c1b83b7f8a0951cb761
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d1f62a98ae959111f0733f0ca186412bf04a2c397bec51445e4e74f3e16ed9e941147bde62577ac52a5c1270f0ce5b9443203f5ea123e2a0266a68382a945143
|
7
|
+
data.tar.gz: d3e05556757511d3ce946a714a79b7fe207b0d0a1e5366ecc1a11b9ac32890aaee7d2803deba99704967592f0b0ca6e2b8a43a90a162438cf13f4ec6db736f4f
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
data/README.md
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
#ruby-stream
|
2
|
+
|
3
|
+
[](https://travis-ci.org/oetzi/ruby-stream)
|
4
|
+
|
5
|
+
Lazy stream implementation for Ruby because Enumerators are kinda silly.
|
6
|
+
|
7
|
+
## Description
|
8
|
+
|
9
|
+
Streams are infinite, lazily evaluated, awesome lists. Here's an example
|
10
|
+
of a Stream that represents the set of positive integers:
|
11
|
+
|
12
|
+
def int_stream(i)
|
13
|
+
Stream.new(i) do
|
14
|
+
int_stream(i + 1)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
integers = int_stream(1)
|
19
|
+
|
20
|
+
You can access streams like any other data structure:
|
21
|
+
|
22
|
+
integers[0] # => 1
|
23
|
+
integers[12] # => 13
|
24
|
+
integers[999999] # => 999998
|
25
|
+
|
26
|
+
Each value will be lazily calculated by materializing the Streams values
|
27
|
+
until the requested value. Streams are stateless so a Stream will
|
28
|
+
recalculate for every access.
|
29
|
+
|
30
|
+
You can also create finite Streams from infinite ones:
|
31
|
+
|
32
|
+
integers.take(5)
|
33
|
+
|
34
|
+
Finite streams have some extra functionality due to their ability to
|
35
|
+
end:
|
36
|
+
|
37
|
+
integers.take(5).each do |i|
|
38
|
+
puts i
|
39
|
+
end
|
40
|
+
|
41
|
+
integers.length # => 5
|
42
|
+
|
43
|
+
Infinite Streams also support interation and length calculations but you
|
44
|
+
will need to be prepared to wait, forever. No seriously, I mean for all
|
45
|
+
time. Its infinite.
|
46
|
+
|
47
|
+
Streams become most powerful when used with high order functions. At the
|
48
|
+
moment ruby-stream supports `map` and `filter` operations that operate
|
49
|
+
as they would on normal collections (these operations will only actually be
|
50
|
+
applied to Stream elements on access or iteration however).
|
data/Rakefile
ADDED
data/lib/ruby-stream.rb
ADDED
@@ -0,0 +1,123 @@
|
|
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 [](n)
|
20
|
+
if n == 0
|
21
|
+
self.head
|
22
|
+
elsif n < 0
|
23
|
+
nil
|
24
|
+
else
|
25
|
+
last_stream = self
|
26
|
+
n.times {
|
27
|
+
return nil if last_stream.kind_of?(EmptyStream)
|
28
|
+
last_stream = last_stream.tail
|
29
|
+
}
|
30
|
+
|
31
|
+
last_stream.head
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def length
|
36
|
+
counter = 0
|
37
|
+
self.each { |ele| counter += 1 }
|
38
|
+
counter
|
39
|
+
end
|
40
|
+
|
41
|
+
def each(&block)
|
42
|
+
last_stream = self
|
43
|
+
|
44
|
+
until last_stream.kind_of?(EmptyStream)
|
45
|
+
block.call(last_stream.head)
|
46
|
+
last_stream = last_stream.tail
|
47
|
+
end
|
48
|
+
|
49
|
+
nil
|
50
|
+
end
|
51
|
+
|
52
|
+
def take(n)
|
53
|
+
if n <= 0
|
54
|
+
EmptyStream.new
|
55
|
+
else
|
56
|
+
Stream.new(head) do
|
57
|
+
tail.take(n - 1)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def take_while(&block)
|
63
|
+
if block.call(head)
|
64
|
+
Stream.new(head) do
|
65
|
+
tail.take_while(&block)
|
66
|
+
end
|
67
|
+
else
|
68
|
+
EmptyStream.new
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def map(&block)
|
73
|
+
Stream.new(yield head) do
|
74
|
+
tail.map(&block)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def filter(&block)
|
79
|
+
if block.call(head)
|
80
|
+
Stream.new(head) do
|
81
|
+
tail.filter(&block)
|
82
|
+
end
|
83
|
+
else
|
84
|
+
tail.filter(&block)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
class EmptyStream < Stream
|
91
|
+
def initialize()
|
92
|
+
@head = nil
|
93
|
+
end
|
94
|
+
|
95
|
+
def tail
|
96
|
+
EmptyStream.new
|
97
|
+
end
|
98
|
+
|
99
|
+
def [](n)
|
100
|
+
nil
|
101
|
+
end
|
102
|
+
|
103
|
+
def each
|
104
|
+
nil
|
105
|
+
end
|
106
|
+
|
107
|
+
def take(n)
|
108
|
+
EmptyStream.new
|
109
|
+
end
|
110
|
+
|
111
|
+
def take_while(&block)
|
112
|
+
EmptyStream.new
|
113
|
+
end
|
114
|
+
|
115
|
+
def map(&block)
|
116
|
+
EmptyStream.new
|
117
|
+
end
|
118
|
+
|
119
|
+
def filter(&block)
|
120
|
+
EmptyStream.new
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
data/ruby-stream.gemspec
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
lib = File.expand_path('../lib/', __FILE__)
|
2
|
+
$:.unshift lib unless $:.include?(lib)
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = "ruby-stream"
|
6
|
+
s.version = "0.1.0"
|
7
|
+
s.platform = Gem::Platform::RUBY
|
8
|
+
s.authors = ["Callum Stott"]
|
9
|
+
s.email = ["callum.stott@me.com"]
|
10
|
+
s.summary = "Lazy stream implementation for Ruby"
|
11
|
+
|
12
|
+
s.require_paths = ['lib']
|
13
|
+
s.files = `git ls-files`.split("\n")
|
14
|
+
end
|
@@ -0,0 +1,197 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'ruby-stream'
|
3
|
+
|
4
|
+
def int_helper(i)
|
5
|
+
Stream.new(i) do
|
6
|
+
int_helper(i + 1)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
describe Stream do
|
11
|
+
before do
|
12
|
+
@stream = int_helper(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 "#[](n)" do
|
33
|
+
it "calculates the nth element of the stream" do
|
34
|
+
@stream[999].must_equal 1000
|
35
|
+
end
|
36
|
+
|
37
|
+
it "returns nil for negative n" do
|
38
|
+
@stream[-1].must_equal nil
|
39
|
+
end
|
40
|
+
|
41
|
+
describe "for a finite Stream" do
|
42
|
+
it "returns nil for n greater than the limit of the Stream" do
|
43
|
+
@stream.take(10)[10].must_equal nil
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe "#take(n)" do
|
49
|
+
it "returns another Stream" do
|
50
|
+
@stream.take(10).kind_of?(Stream).must_equal true
|
51
|
+
end
|
52
|
+
|
53
|
+
it "returns a Stream with the correct #length" do
|
54
|
+
@stream.take(10).length.must_equal 10
|
55
|
+
end
|
56
|
+
|
57
|
+
it "returns a Stream with the correct values" do
|
58
|
+
stream = @stream.take(10)
|
59
|
+
stream[0].must_equal 1
|
60
|
+
stream[5].must_equal 6
|
61
|
+
stream[7].must_equal 8
|
62
|
+
stream[9].must_equal 10
|
63
|
+
end
|
64
|
+
|
65
|
+
it "returns a Stream that can be iterated through finitely" do
|
66
|
+
@stream.take(10).each do |element|
|
67
|
+
true.must_equal true
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe "when operating on the result Stream" do
|
72
|
+
it "returns a finite Stream for #map" do
|
73
|
+
stream = @stream.take(10).map { |i| i.to_s }
|
74
|
+
stream.length.must_equal 10
|
75
|
+
end
|
76
|
+
|
77
|
+
it "returns a finite Stream for #filter" do
|
78
|
+
stream = @stream.take(10).filter { |i| true }
|
79
|
+
stream.length.must_equal 10
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
describe "for a finite Stream" do
|
84
|
+
it "returns a Stream with length limit if n > limit" do
|
85
|
+
original = @stream.take(10)
|
86
|
+
original.take(100).length.must_equal 10
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
describe "#each(func)" do
|
92
|
+
describe "for a finite Stream" do
|
93
|
+
it "should return nil" do
|
94
|
+
@stream.take(5).each do
|
95
|
+
1
|
96
|
+
end.must_equal nil
|
97
|
+
end
|
98
|
+
|
99
|
+
it "should execute the passed block for every element of the stream" do
|
100
|
+
i = 0
|
101
|
+
@stream.take(5).each do
|
102
|
+
i += 1
|
103
|
+
end
|
104
|
+
|
105
|
+
i.must_equal 5
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
describe "#length" do
|
111
|
+
it "returns the lenght for a finite array" do
|
112
|
+
@stream.take(5).length.must_equal 5
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
describe "#map(func)" do
|
117
|
+
it "returns a new Stream" do
|
118
|
+
@stream.map(&:to_s).kind_of?(Stream).must_equal true
|
119
|
+
end
|
120
|
+
|
121
|
+
it "returns a new Stream with mapped values" do
|
122
|
+
mapped = @stream.map { |i| i + 1 }
|
123
|
+
mapped[0].must_equal(2)
|
124
|
+
mapped[3].must_equal(5)
|
125
|
+
mapped[1000].must_equal(1002)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
describe "#filter(func)" do
|
130
|
+
it "returns a new Stream" do
|
131
|
+
@stream.filter { |i| i % 2 == 0 }.kind_of?(Stream).must_equal true
|
132
|
+
end
|
133
|
+
|
134
|
+
it "returns a new Stream with only elements matching the predicate" do
|
135
|
+
filtered = @stream.filter { |i| i % 2 == 0 }
|
136
|
+
filtered[0].must_equal 2
|
137
|
+
filtered[1].must_equal 4
|
138
|
+
filtered[3].must_equal 8
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
describe "#take_while(func)" do
|
143
|
+
it "returns a new Stream" do
|
144
|
+
@stream.take_while { |i| i < 10 }.kind_of?(Stream).must_equal true
|
145
|
+
end
|
146
|
+
|
147
|
+
describe "when the return Stream is finite" do
|
148
|
+
it "returns a Stream with the correct length" do
|
149
|
+
@stream.take_while { |i| i < 10 }.length.must_equal 9
|
150
|
+
end
|
151
|
+
|
152
|
+
it "returns a Stream that iterates through finitely" do
|
153
|
+
stream = @stream.take_while { |i| i < 10 }
|
154
|
+
counter = 0
|
155
|
+
stream.each { |i| counter += 1 }
|
156
|
+
counter.must_equal 9
|
157
|
+
end
|
158
|
+
|
159
|
+
it "returns a finite Stream for #map" do
|
160
|
+
stream = @stream.take_while { |i|
|
161
|
+
i < 10
|
162
|
+
}.map { |i| i.to_s }
|
163
|
+
|
164
|
+
stream.length.must_equal 9
|
165
|
+
end
|
166
|
+
|
167
|
+
it "returns a finite Stream for #filter" do
|
168
|
+
stream = @stream.take_while { |i|
|
169
|
+
i < 10
|
170
|
+
}.filter { |i| true }
|
171
|
+
|
172
|
+
stream.length.must_equal 9
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
describe ".continually(func)" do
|
178
|
+
it "returns a new Stream" do
|
179
|
+
Stream.continually {
|
180
|
+
true
|
181
|
+
}.kind_of?(Stream).must_equal true
|
182
|
+
end
|
183
|
+
|
184
|
+
it "returns a Stream with the calculated block as each element" do
|
185
|
+
stream_block = Proc.new do
|
186
|
+
counter = 0
|
187
|
+
Stream.continually {
|
188
|
+
counter += 1
|
189
|
+
}
|
190
|
+
end
|
191
|
+
|
192
|
+
stream_block.call.head.must_equal 1
|
193
|
+
stream_block.call.tail.head.must_equal 2
|
194
|
+
stream_block.call.tail.tail.head.must_equal 3
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
metadata
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ruby-stream
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Callum Stott
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-03-04 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description:
|
14
|
+
email:
|
15
|
+
- callum.stott@me.com
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- Gemfile
|
21
|
+
- Gemfile.lock
|
22
|
+
- README.md
|
23
|
+
- Rakefile
|
24
|
+
- lib/ruby-stream.rb
|
25
|
+
- ruby-stream.gemspec
|
26
|
+
- spec/ruby-stream_spec.rb
|
27
|
+
homepage:
|
28
|
+
licenses: []
|
29
|
+
metadata: {}
|
30
|
+
post_install_message:
|
31
|
+
rdoc_options: []
|
32
|
+
require_paths:
|
33
|
+
- lib
|
34
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
35
|
+
requirements:
|
36
|
+
- - '>='
|
37
|
+
- !ruby/object:Gem::Version
|
38
|
+
version: '0'
|
39
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
requirements: []
|
45
|
+
rubyforge_project:
|
46
|
+
rubygems_version: 2.0.0
|
47
|
+
signing_key:
|
48
|
+
specification_version: 4
|
49
|
+
summary: Lazy stream implementation for Ruby
|
50
|
+
test_files: []
|