lazy_stream 0.5.1 → 0.5.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.
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