lazy_stream 0.5.1 → 0.5.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: fa9d0377f7f4989c9dc30bc5c815a26a3aceec0a
4
- data.tar.gz: a9442c1c658d99b5b79a90a8dad437481898cb45
3
+ metadata.gz: 55023408f0cd578d29bf48c769b001f29c3e70dc
4
+ data.tar.gz: 6de9083f43b08e1c0e448116f553c904c4ee2550
5
5
  SHA512:
6
- metadata.gz: c16adf4696ca3bba83c9b6c42923eeb387e3ea0b63426d0a3fc88486fdda41be57d6029edff0b32d355aea81af295b21733f7514ba6fd92b3989ff2f8cec84e3
7
- data.tar.gz: cafc007afb204107a4954b991b44e861e5172fe975b6c0bc95c8a1e4c93944d42144f707c679c1cdbbdae9a6993c3b7ab10b8afe12e138650ee9398f73ae8791
6
+ metadata.gz: ac332caa2e2b89a8d8818e511761a04d6e61aaa1e0021b34e483218c8ae1d5976f741db6d6e0fd99c6df40d196a8e1c9f302b998fe711aa71642e2f8f90fbc77
7
+ data.tar.gz: 5b309210ab23fc28c4a2eaf0878b6bca09df7e906768032b00f6e87ece306b103827723fdfb91396cb7adb1b7ed8867fc61f0c68ef76d9f79e5a6d8ba4a71442
@@ -0,0 +1,122 @@
1
+ lazy_stream
2
+ ===========
3
+
4
+ A Ruby class to represent lazy infinite stream.
5
+ It is implemented in the same way as the streams in the book [SICP]
6
+ (http://mitpress.mit.edu/sicp/full-text/sicp/book/node69.html).
7
+
8
+ ## Installation
9
+ The library is distributed via [RubyGems](http://rubygems.org/gems/lazy_stream):
10
+
11
+ $ gem install lazy_stream
12
+
13
+ ## Usage
14
+
15
+ A stream is just a delayed list. We do lazy evaluation in Ruby with code blocks.
16
+ To create a stream:
17
+
18
+ ``` ruby
19
+ require 'lazy_stream'
20
+
21
+ s = lazy_stream(1) { lazy_stream(2) { lazy_stream(3) } }
22
+ s.to_a #=> [1, 2, 3]
23
+ s.first #=> 1
24
+ s.rest #=> A LazyStream object for the rest of the list [2, 3]
25
+ lazy_stream #=> An empty LazyStream object
26
+ ```
27
+
28
+ lazy_stream is just a shortcut for LazyStream.new.
29
+ Methods empty?, at, drop, each, map, reduce, select, take are also implemented
30
+ like Array.
31
+
32
+ It becomes powerful when we construct infinite streams like these:
33
+
34
+ ``` ruby
35
+ def integers_starting_from(n)
36
+ lazy_stream(n) { integers_starting_from(n + 1) }
37
+ end
38
+
39
+ integers_starting_from(1).take(10).reduce(&:+) #=> 55
40
+
41
+ def fibgen(a, b)
42
+ lazy_stream(a) { fibgen(b, a + b) }
43
+ end
44
+
45
+ fibgen(0, 1).take(10).to_a #=> [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
46
+
47
+ def sieve(stream)
48
+ lazy_stream(stream.first) do
49
+ sieve(stream.rest.select { |x| x % stream.first > 0 })
50
+ end
51
+ end
52
+
53
+ def primes
54
+ sieve(integers_starting_from(2))
55
+ end
56
+
57
+ primes.take(10).to_a #=> [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
58
+ ```
59
+
60
+ Or define streams implicitly:
61
+
62
+ ``` ruby
63
+ ones = lazy_stream(1) { ones }
64
+ ones.take(10).to_a #=> [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
65
+
66
+ integers = lazy_stream(1) { LazyStream.add(ones, integers) }
67
+ integers.take(10).to_a #=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
68
+
69
+ # fibs is a stream beginning with 0 and 1, such that the rest of the stream can
70
+ # be generated by adding fibs to itself shifted by one place:
71
+ # 1 1 2 3 5 8 13 21 ... = fibs.rest
72
+ # 0 1 1 2 3 5 8 13 ... = fibs
73
+ # 0 1 1 2 3 5 8 13 21 34 ... = fibs
74
+ fibs = lazy_stream(0) { lazy_stream(1) { LazyStream.add(fibs.rest, fibs) } }
75
+ fibs.take(10).to_a #=> [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
76
+ ```
77
+
78
+ More complicated example, approximating *π*, with Euler's transform and the
79
+ tableau sequence accelerator:
80
+
81
+ ``` ruby
82
+ def pi_summands(n)
83
+ lazy_stream(1.0 / n) { pi_summands(n + 2).map(&:-@) }
84
+ end
85
+
86
+ def pi_stream
87
+ pi_summands(1).partial_sums.scale(4)
88
+ end
89
+
90
+ def euler_transform(s)
91
+ lazy_stream(s[2] - ((s[2] - s[1])**2 / (s[0] - 2 * s[1] + s[2]))) do
92
+ euler_transform(s.rest)
93
+ end
94
+ end
95
+
96
+ def make_tableau(s, &transform)
97
+ lazy_stream(s) { make_tableau(transform.call(s), &transform) }
98
+ end
99
+
100
+ def accelerated_sequence(s, &transform)
101
+ make_tableau(s, &transform).map(&:first)
102
+ end
103
+
104
+ accelerated_sequence(pi_stream, &method(:euler_transform)).take(8).print
105
+ # 4.0
106
+ # 3.166666666666667
107
+ # 3.142105263157895
108
+ # 3.141599357319005
109
+ # 3.1415927140337785
110
+ # 3.1415926539752927
111
+ # 3.1415926535911765
112
+ # 3.141592653589778
113
+ ```
114
+
115
+ Since the functions are all recursive here, to make them actually work for large
116
+ data set, we need to enable the
117
+ [tail call optimization](https://github.com/melvinxie/ruby/blob/master/tco.rb)
118
+ in Ruby, otherwise you will hit the stack level too deep error soon.
119
+
120
+ For more examples, see my
121
+ [blog post](http://melvinxie.github.io/blog/2013/05/04/ruby-lazy-infinite-stream-in-the-sicp-way/)
122
+ and [example spec](https://github.com/melvinxie/lazy_stream/blob/master/spec/example_spec.rb).
@@ -0,0 +1,6 @@
1
+ require 'rake/testtask'
2
+ require 'rspec/core/rake_task'
3
+ RSpec::Core::RakeTask.new('spec')
4
+
5
+ desc "Run tests"
6
+ task :default => :spec
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  class LazyStream
4
+ attr_reader :first
5
+
4
6
  def initialize(first=nil, &proc)
5
7
  @first = first
6
8
  @proc = block_given? ? proc : lambda { LazyStream.new }
7
9
  end
8
10
 
9
- attr_reader :first
10
-
11
11
  def rest
12
12
  @rest ||= @proc.call
13
13
  end
@@ -17,13 +17,8 @@ class LazyStream
17
17
  end
18
18
 
19
19
  def [](n)
20
- if empty?
21
- nil
22
- elsif n == 0
23
- first
24
- else
25
- rest[n - 1]
26
- end
20
+ return nil if empty?
21
+ n == 0 ? first : rest[n - 1]
27
22
  end
28
23
 
29
24
  alias_method :at, :[]
@@ -106,6 +101,12 @@ class LazyStream
106
101
  def self.interleave(s1, s2)
107
102
  s1.empty? ? s2 : LazyStream.new(s1.first) { interleave(s2, s1.rest) }
108
103
  end
104
+
105
+ def ==(other)
106
+ return false if !other.is_a?(LazyStream)
107
+ return true if empty? && other.empty?
108
+ first == other.first && rest == other.rest
109
+ end
109
110
  end
110
111
 
111
112
  module Kernel
@@ -0,0 +1,176 @@
1
+ require_relative 'spec_helper'
2
+
3
+ describe "Example" do
4
+ def integers_starting_from(n)
5
+ lazy_stream(n) { integers_starting_from(n + 1) }
6
+ end
7
+
8
+ it "defines integers stream explicitly" do
9
+ integers = integers_starting_from(1)
10
+ integers.take(10).to_a.should == [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
11
+ integers.take(10).reduce(&:+).should == 55
12
+ no_sevens = integers.select { |i| i % 7 > 0 }
13
+ no_sevens.take(10).to_a.should == [1, 2, 3, 4, 5, 6, 8, 9, 10, 11]
14
+ end
15
+
16
+ def fibgen(a, b)
17
+ lazy_stream(a) { fibgen(b, a + b) }
18
+ end
19
+
20
+ it "defines fibonacci stream explicitly" do
21
+ fibgen(0, 1).take(10).to_a.should == [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
22
+ end
23
+
24
+ def sieve(stream)
25
+ lazy_stream(stream.first) do
26
+ sieve(stream.rest.select { |x| x % stream.first > 0 })
27
+ end
28
+ end
29
+
30
+ it "defines primes stream with sieve explicitly" do
31
+ primes = sieve(integers_starting_from(2))
32
+ primes.take(10).to_a.should == [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
33
+ end
34
+
35
+ it "defines integers stream implicitly" do
36
+ ones = lazy_stream(1) { ones }
37
+ ones.take(10).to_a.should == [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
38
+ integers = lazy_stream(1) { LazyStream.add(ones, integers) }
39
+ integers.take(10).to_a.should == [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
40
+ end
41
+
42
+ it "defines fibonacci stream implicitly" do
43
+ # fibs is a stream beginning with 0 and 1, such that the rest of the stream
44
+ # can be generated by adding fibs to itself shifted by one place:
45
+ # 1 1 2 3 5 8 13 21 ... = fibs.rest
46
+ # 0 1 1 2 3 5 8 13 ... = fibs
47
+ # 0 1 1 2 3 5 8 13 21 34 ... = fibs
48
+ fibs = lazy_stream(0) { lazy_stream(1) { LazyStream.add(fibs.rest, fibs) } }
49
+ fibs.take(10).to_a.should == [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
50
+ end
51
+
52
+ def prime?(n)
53
+ iter = -> ps do
54
+ if ps.first**2 > n
55
+ true
56
+ elsif n % ps.first == 0
57
+ false
58
+ else
59
+ iter.call(ps.rest)
60
+ end
61
+ end
62
+ iter.call(@primes)
63
+ end
64
+
65
+ it "defines primes stream implicitly" do
66
+ # The reason this definition works is that, at any point, enough of the
67
+ # primes stream has been generated to test the primality of the numbers we
68
+ # need to check next.
69
+ @primes = lazy_stream(2) do
70
+ integers_starting_from(3).select(&method(:prime?))
71
+ end
72
+ @primes.take(10).to_a.should == [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
73
+ end
74
+
75
+ def enumerate_interval(low, high)
76
+ low > high ? lazy_stream :
77
+ lazy_stream(low) { enumerate_interval(low + 1, high) }
78
+ end
79
+
80
+ def prime_enumerate_interval(low, high)
81
+ enumerate_interval(low, high).select(&method(:prime?))
82
+ end
83
+
84
+ it "enumerates intervals of primes stream" do
85
+ @primes = lazy_stream(2) do
86
+ integers_starting_from(3).select(&method(:prime?))
87
+ end
88
+ prime_enumerate_interval(10, 30).to_a.should == [11, 13, 17, 19, 23, 29]
89
+ prime_enumerate_interval(10000, 40000).reduce(&:+).should == 73434270
90
+ end
91
+
92
+ def sqrt_improve(guess, x)
93
+ (guess + x / guess) / 2
94
+ end
95
+
96
+ def sqrt_stream(x)
97
+ guesses = lazy_stream(1.0) do
98
+ guesses.map { |guess| sqrt_improve(guess, x) }
99
+ end
100
+ end
101
+
102
+ it "defines sqrt approximation stream" do
103
+ sqrt_stream(2).take(5).to_a.should ==
104
+ [1.0, 1.5, 1.4166666666666665, 1.4142156862745097, 1.4142135623746899]
105
+ end
106
+
107
+ def pi_summands(n)
108
+ lazy_stream(1.0 / n) { pi_summands(n + 2).map(&:-@) }
109
+ end
110
+
111
+ def pi_stream
112
+ pi_summands(1).partial_sums.scale(4)
113
+ end
114
+
115
+ it "defines pi approximation stream" do
116
+ pi_stream.take(8).to_a.should ==
117
+ [4.0, 2.666666666666667, 3.466666666666667, 2.8952380952380956,
118
+ 3.3396825396825403, 2.9760461760461765, 3.2837384837384844,
119
+ 3.017071817071818]
120
+ end
121
+
122
+ def euler_transform(s)
123
+ lazy_stream(s[2] - ((s[2] - s[1])**2 / (s[0] - 2 * s[1] + s[2]))) do
124
+ euler_transform(s.rest)
125
+ end
126
+ end
127
+
128
+ it "defines pi approximation stream with euler transform" do
129
+ euler_transform(pi_stream).take(8).to_a.should ==
130
+ [3.166666666666667, 3.1333333333333337, 3.1452380952380956,
131
+ 3.13968253968254, 3.1427128427128435, 3.1408813408813416,
132
+ 3.142071817071818, 3.1412548236077655]
133
+ end
134
+
135
+ def make_tableau(s, &transform)
136
+ lazy_stream(s) { make_tableau(transform.call(s), &transform) }
137
+ end
138
+
139
+ def accelerated_sequence(s, &transform)
140
+ make_tableau(s, &transform).map(&:first)
141
+ end
142
+
143
+ it "defines pi approximation stream with accelerated sequence" do
144
+ accelerated_sequence(pi_stream, &method(:euler_transform)).take(8).to_a.
145
+ should == [4.0, 3.166666666666667, 3.142105263157895, 3.141599357319005,
146
+ 3.1415927140337785, 3.1415926539752927, 3.1415926535911765,
147
+ 3.141592653589778]
148
+ end
149
+
150
+ def pairs(s, t)
151
+ lazy_stream([s.first, t.first]) do
152
+ LazyStream.interleave(t.rest.map { |x| [s.first, x] },
153
+ pairs(s.rest, t.rest))
154
+ end
155
+ end
156
+
157
+ it "defines pairs stream" do
158
+ integers = integers_starting_from(1)
159
+ pairs(integers, integers).take(5).to_a.should ==
160
+ [[1, 1], [1, 2], [2, 2], [1, 3], [2, 3]]
161
+ end
162
+
163
+ # integrand is a delayed argument that promises to give result after a loop
164
+ def integral(integrand, initial, dt)
165
+ int = lazy_stream(initial) { LazyStream.add(integrand.call.scale(dt), int) }
166
+ end
167
+
168
+ # sets up the delayed integrand with lambda
169
+ def solve(f, y0, dt)
170
+ y = integral(lambda { y.map(&f) }, y0, dt)
171
+ end
172
+
173
+ it "solves the differential equation dy / dt = f(y)" do
174
+ solve(lambda { |y| y }, 1, 0.001)[1000].should == 2.716923932235896
175
+ end
176
+ end
@@ -0,0 +1,163 @@
1
+ require_relative 'spec_helper'
2
+
3
+ describe LazyStream do
4
+ it "initializes an empty stream" do
5
+ empty_stream = LazyStream.new
6
+ empty_stream.should be_empty
7
+ end
8
+
9
+ it "returns the first element" do
10
+ stream = LazyStream.new(1)
11
+ stream.first.should == 1
12
+ end
13
+
14
+ it "has the rest as another lazy stream itself" do
15
+ stream = LazyStream.new(1) { LazyStream.new(2) }
16
+ stream.rest.first.should == 2
17
+ end
18
+
19
+ it "caches the rest" do
20
+ cached = false
21
+ rest = lambda do
22
+ if !cached
23
+ cached = true
24
+ LazyStream.new(2)
25
+ else
26
+ raise Exception, "Did not cache"
27
+ end
28
+ end
29
+ stream = LazyStream.new(1, &rest)
30
+ stream.rest.first.should == 2
31
+ stream.rest.first.should == 2
32
+ end
33
+
34
+ it "knows when it's not empty" do
35
+ LazyStream.new(1).should_not be_empty
36
+ end
37
+
38
+ context "#[]" do
39
+ it "returns nil when is empty" do
40
+ LazyStream.new[0].should == nil
41
+ end
42
+
43
+ it "returns first for zero index" do
44
+ LazyStream.new(1)[0].should == 1
45
+ end
46
+
47
+ it "returns from the rest for greater than zero indices" do
48
+ stream = LazyStream.new(1) { LazyStream.new(2) }
49
+ stream[1].should == 2
50
+ end
51
+
52
+ it "aliases to [] from at" do
53
+ stream = LazyStream.new(1) { LazyStream.new(2) }
54
+ stream.at(1).should == 2
55
+ end
56
+ end
57
+
58
+ context "#drop" do
59
+ it "returns the stream when it's empty" do
60
+ stream = LazyStream.new
61
+ stream.drop(10).should == stream
62
+ end
63
+
64
+ it "returns itself when nothing has been dropped" do
65
+ stream = LazyStream.new(1)
66
+ stream.drop(0).should == stream
67
+ end
68
+
69
+ it "returns the rest after dropping the first element" do
70
+ stream = LazyStream.new(1) { LazyStream.new(2) }
71
+ stream.drop(1).should == LazyStream.new(2)
72
+ end
73
+ end
74
+
75
+ context "#each" do
76
+ it "iterates through all elements in stream" do
77
+ stream = LazyStream.new(1) { LazyStream.new(2) }
78
+ elements = []
79
+ stream.each { |element| elements << element }
80
+ elements.should == [1, 2]
81
+ end
82
+ end
83
+
84
+ context "#map" do
85
+ it "maps the elements one at a time" do
86
+ stream = LazyStream.new(1) { LazyStream.new(2) }
87
+ mapped = stream.map do |item|
88
+ item * 2
89
+ end
90
+ mapped.should == LazyStream.new(2) { LazyStream.new(4) }
91
+ end
92
+ end
93
+
94
+ context "#select" do
95
+ it "returns self if stream is empty" do
96
+ stream = LazyStream.new
97
+ selected = stream.select { |element| element % 2 == 0 }
98
+ selected.should == stream
99
+ end
100
+
101
+ it "selects first when it only applies to it" do
102
+ stream = LazyStream.new(2) { LazyStream.new(1) }
103
+ selected = stream.select { |element| element % 2 == 0 }
104
+ selected == LazyStream.new(2)
105
+ end
106
+
107
+ it "selects as longs as predicate applies" do
108
+ stream = LazyStream.new(2) { LazyStream.new(4) }
109
+ selected = stream.select { |element| element % 2 == 0 }
110
+ selected.should == stream
111
+ end
112
+ end
113
+
114
+ context "#take" do
115
+ it "returns an empty steam if stream is empty" do
116
+ stream = LazyStream.new
117
+ stream.take(10).should be_empty
118
+ end
119
+
120
+ it "returns empty stream if n is less than one" do
121
+ stream = LazyStream.new(1)
122
+ stream.take(0).should == LazyStream.new
123
+ end
124
+
125
+ it "returns the first element for n equals 1" do
126
+ stream = LazyStream.new(2)
127
+ stream.take(1).should == stream
128
+ end
129
+
130
+ it "returns first & rest from rest when n is greater than 1" do
131
+ stream = LazyStream.new(1) { LazyStream.new(2) }
132
+ stream.take(2).should == stream
133
+ end
134
+ end
135
+
136
+ context "#==" do
137
+ it "is not equal with something other than LazyStream" do
138
+ LazyStream.new.should_not == 2
139
+ end
140
+
141
+ it "is equal when both empty" do
142
+ LazyStream.new.should == LazyStream.new
143
+ end
144
+
145
+ it "is equal when they both have single same element" do
146
+ LazyStream.new(1).should == LazyStream.new(1)
147
+ end
148
+
149
+ it "is not equal when they have different first" do
150
+ LazyStream.new(1).should_not == LazyStream.new(2)
151
+ end
152
+
153
+ it "is equal when first and rest are equal" do
154
+ stream = LazyStream.new(1) { LazyStream.new(2) }
155
+ stream.should == LazyStream.new(1) { LazyStream.new(2) }
156
+ end
157
+
158
+ it "is not equal when rests are not equal" do
159
+ stream = LazyStream.new(1) { LazyStream.new(2) }
160
+ stream.should_not == LazyStream.new(1) { LazyStream.new(3) }
161
+ end
162
+ end
163
+ end
@@ -0,0 +1,7 @@
1
+ require 'rspec'
2
+ require_relative '../lib/lazy_stream'
3
+
4
+ RSpec.configure do |config|
5
+ config.color_enabled = true
6
+ config.formatter = 'documentation'
7
+ end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lazy_stream
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 0.5.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mingmin Xie
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-05-06 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2013-05-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rspec
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
13
27
  description: Ruby infinite lazy stream
14
28
  email: melvinxie@gmail.com
15
29
  executables: []
@@ -17,6 +31,11 @@ extensions: []
17
31
  extra_rdoc_files: []
18
32
  files:
19
33
  - lib/lazy_stream.rb
34
+ - Rakefile
35
+ - README.md
36
+ - spec/example_spec.rb
37
+ - spec/lazy_stream_spec.rb
38
+ - spec/spec_helper.rb
20
39
  homepage: https://github.com/melvinxie/lazy_stream
21
40
  licenses:
22
41
  - MIT
@@ -37,8 +56,11 @@ required_rubygems_version: !ruby/object:Gem::Requirement
37
56
  version: '0'
38
57
  requirements: []
39
58
  rubyforge_project:
40
- rubygems_version: 2.0.3
59
+ rubygems_version: 2.0.0
41
60
  signing_key:
42
61
  specification_version: 4
43
62
  summary: lazy_stream
44
- test_files: []
63
+ test_files:
64
+ - spec/example_spec.rb
65
+ - spec/lazy_stream_spec.rb
66
+ - spec/spec_helper.rb