lazily 0.0.1 → 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 CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- YTE1ODg4ZDgzNDFjMTc0YzlhMWE0ODU5MWRmNjcyYzQyMTNkMGNmYw==
4
+ OTA2Zjk3MzAxNDg4N2JhMGFhZDAyNTJjZmQ3YzExYzFkNDA4NmZlMw==
5
5
  data.tar.gz: !binary |-
6
- MDlhZjZhMWI5ZDNiMDdmMzk3OWU4NGM4MGVmYzhlODUyMzEyYzg3Ng==
6
+ YmY5MzM3N2ZiYTZkZWVjNzMzNTE2NjhjMmQ1NDZhN2UyYjgxODcxOQ==
7
7
  !binary "U0hBNTEy":
8
8
  metadata.gz: !binary |-
9
- NWNkYWNkOTViODA2OWM0ZWM1NWJkNTlhY2Y2NWUwNmQ0NGQ1MjZiZjFkODgz
10
- MGFiYjRlYmZjYTQzZDkyYjMyMzFhYjU1ZTE3Y2JmMTMwZWEwMzBhYWFhZDIw
11
- Nzg3Y2I0MjUyYzQzOTJkOTg2MmI4NjdlZmQ0MTkwZTkzMzA2YTY=
9
+ OThlZDA3NGU5ZTAzMmUxZjY0ZmJkMGU3OGE0MDZhZGE0ZTE5NDI5ZDBlZmVi
10
+ ODFmYjEyYmVmNGUwYmMwMzllYjU0NGUwMTgwN2I5NTc5NjExM2U1MTI0MmY3
11
+ ZjYyOTJiZTMwN2I1ZGMwNmUzYWM5ZWZkMmZjZjJhYTkxOWRlYzI=
12
12
  data.tar.gz: !binary |-
13
- OGQ5ZTBhZTgyNTUwOTc3NTEwOGE3MTgwMmM0OWFiZjBiZDY1ZGJjYWRlMDJl
14
- YTM2NmY5ZmYzMGRhNDI3Yjk4NTgwYTU5MTdmYjllM2U4Y2M0ZDVlODM1ZTI1
15
- MThmOTkyZDNhZDNjNDVkYWVkZjBmMDJmMGEwMGZjMGRiZTAxYzI=
13
+ NDA3OTM0ZWMzOGNkMDY3M2JjNmYwYzg2Zjk4NmY0MjNlZDY2ZWZkY2I0NzMx
14
+ Y2E5MzJmODU5MTgyYTk4MjUxZWIxYWQwNzM5NjExNWRhNDFhYTgzYjM3NGE3
15
+ Yzk5NjVmMjBjMjBkMDcyZmM2YTAwNzI1YzcwZTA1NTBiZmIyZDM=
data/.gitignore CHANGED
@@ -1,4 +1,6 @@
1
1
  *.gem
2
2
  .bundle
3
+ .yardoc
3
4
  Gemfile.lock
5
+ doc
4
6
  pkg/*
@@ -0,0 +1,5 @@
1
+ # Changelog
2
+
3
+ ## 0.0.1 (2013-04-19)
4
+
5
+ * Initial version, ported from "enumerating".
data/Gemfile CHANGED
@@ -4,3 +4,4 @@ gemspec
4
4
 
5
5
  gem "rake", "~> 10.0.0"
6
6
  gem "rspec", "~> 2.13.0"
7
+ gem "yard"
@@ -0,0 +1,147 @@
1
+ Lazily [![Build Status](https://secure.travis-ci.org/mdub/lazily.png?branch=master)](http://travis-ci.org/mdub/lazily)
2
+ ===========
3
+
4
+ Lazily provides various "lazy" Enumerable operations, similar to:
5
+
6
+ * Clojure's sequences
7
+ * Haskell's lists
8
+ * Scala's "views"
9
+ * Ruby 2.0's Enumerator::Lazy
10
+
11
+ Lazy filtering and transforming
12
+ -------------------------------
13
+
14
+ Lazy evaluation is triggered using `Enumerable#lazily`:
15
+
16
+ [1,2,3].lazily #=> #<Lazily::Proxy: [1, 2, 3]>
17
+
18
+ The resulting object implements lazy versions of various Enumerable methods. Rather than returning Arrays, the lazy forms return new Enumerable objects, which generate results incrementally, on demand.
19
+
20
+ Consider the following code, which squares a bunch of numbers, then takes the first 4 results.
21
+
22
+ >> (1..10).collect { |x| p x; x*x }.take(4)
23
+ 1
24
+ 2
25
+ 3
26
+ 4
27
+ 5
28
+ 6
29
+ 7
30
+ 8
31
+ 9
32
+ 10
33
+ => [1, 4, 9, 16]
34
+
35
+ See how it printed all the numbers from 1 to 10, indicating that the block given to `collect` was run ten times. We can do the same thing lazily, like this:
36
+
37
+ >> (1..10).lazily.collect { |x| p x; x*x }.take(4).to_a
38
+ 1
39
+ 2
40
+ 3
41
+ 4
42
+ => [1, 4, 9, 16]
43
+
44
+ Same result, but notice how the block was only evaluated four times.
45
+
46
+ Lazy pipelines
47
+ --------------
48
+
49
+ By combining two or more lazy operations, you can create an efficient "pipeline", e.g.
50
+
51
+ User.to_enum(:find_each).lazily.select do |u|
52
+ u.first_name[0] == u.last_name[0]
53
+ end.collect(&:company).uniq.to_a
54
+
55
+ In case you missed it:
56
+
57
+ # enumerate all users
58
+ users = User.to_enum(:find_each)
59
+
60
+ # lazily select those matching "X... X..." (e.g. "Mickey Mouse")
61
+ users = users.lazily.select { |u| u.first_name[0] == u.last_name[0] }
62
+
63
+ # grab their company (weeding out duplicates)
64
+ companies = users.collect(&:company).uniq
65
+
66
+ # force resolution
67
+ companies.to_a #=> ["Disney"]
68
+
69
+ Because the steps in the pipeline operate in parallel, without creation of intermediate collections (Arrays), you can efficiently operate on large (or even infinite) Enumerable collections.
70
+
71
+ This is analogous to a Unix shell pipeline, though of course here we're talking about streams of objects, rather than streams of text.
72
+
73
+ Lazy multi-threaded processing
74
+ ------------------------------
75
+
76
+ The `#in_threads` method is a multi-threaded version of `#collect`, allowing multiple elements of a collection to be processed in parallel. It requires a numeric argument specifying the maximum number of Threads to use.
77
+
78
+ Benchmark.realtime do
79
+ [1,2,3,4].lazily.in_threads(10) do |x|
80
+ sleep 1
81
+ x * 2
82
+ end.to_a #=> [2,4,6,8]
83
+ end.to_i #=> 1
84
+
85
+ Outputs will be yielded in the expected order, making it a drop-in replacement for `#collect`.
86
+
87
+ Unlike some other "parallel map" implementations, the output of `#in_threads` is lazy (though it does need to pre-fetch elements from the source collection as required to start Threads).
88
+
89
+ Lazy combination of Enumerables
90
+ -------------------------------
91
+
92
+ Lazily also provides some interesting ways to combine several Enumerable collections to create a new collection.
93
+
94
+ `Lazily.zip` pulls elements from a number of collections in parallel, yielding each group.
95
+
96
+ array1 = [1,3,6]
97
+ array2 = [2,4,7]
98
+ Lazily.zip(array1, array2) #=> [1,2], [3,4], [6,7]
99
+
100
+ `Lazily.concat` concatenates collections.
101
+
102
+ array1 = [1,3,6]
103
+ array2 = [2,4,7]
104
+ Lazily.concat(array1, array2) #=> [1,3,6,2,4,7]
105
+
106
+ `Lazily.merge` merges multiple collections, preserving sort-order. The inputs are assumed to be sorted already.
107
+
108
+ array1 = [1,4,5]
109
+ array2 = [2,3,6]
110
+ Lazily.merge(array1, array2) #=> [1,2,3,4,5,6]
111
+
112
+ A block can be provided to determine the sort-order.
113
+
114
+ array1 = %w(a dd cccc)
115
+ array2 = %w(eee bbbbb)
116
+ Lazily.merge(array1, array2) { |x| x.length }
117
+ #=> %w(a dd eee cccc bbbbb)
118
+
119
+ Same but different
120
+ ------------------
121
+
122
+ There are numerous similar implementations of lazy operations on Enumerables.
123
+
124
+ ### Lazily vs. Enumerating
125
+
126
+ Lazily supercedes "[Enumerating](http://github.com/mdub/enumerating)". Whereas Enumerating mixed lazy operations directly onto `Enumerable`, Lazily does not. Instead, it implements an API modelled on Ruby 2.0's `Enumerable#lazy`.
127
+
128
+ ### Lazily vs. Ruby 2.0
129
+
130
+ Q: Why use Lazily, when Ruby 2.x has built-in lazy enumerations?
131
+
132
+ - Compatibility: Perhaps you haven't managed to migrate to Ruby 2.0 yet. Lazily provides the same benefits, but works in older versions of Ruby (most features work even in Ruby-1.8.7).
133
+ - Consistency: Being pure-Ruby, you can use the same implementation of lazy enumeration across Ruby versions and interpreters.
134
+ - Features: Lazily provides some extra features not present in Ruby 2.0, such as multi-threaded lazy enumeration.
135
+ - Speed: Despite being implemented in pure Ruby, `Enumerable#lazily` actually performs a little better than `Enumerable#lazy`.
136
+
137
+ Q: When would you use built-in Ruby lazy enumerations, rather than Lazily?
138
+
139
+ - I wouldn't.
140
+
141
+ ### Others
142
+
143
+ See also:
144
+
145
+ * Greg Spurrier's gem "`lazing`"
146
+ * `Enumerable#defer` from the Ruby Facets library
147
+ * The "`backports`" gem, which implements `Enumerable#lazy` for Ruby pre-2.0.
data/Rakefile CHANGED
@@ -10,3 +10,9 @@ RSpec::Core::RakeTask.new do |t|
10
10
  end
11
11
 
12
12
  task "default" => "spec"
13
+
14
+ desc "Generate documentation"
15
+ task(:doc) { sh "yard" }
16
+
17
+ desc "Generate documentation incrementally"
18
+ task(:redoc) { sh "yard -c" }
@@ -0,0 +1,97 @@
1
+ require 'benchmark'
2
+
3
+ $: << File.expand_path("../../lib", __FILE__)
4
+
5
+ array = (1..100000).to_a
6
+
7
+ # Test scenario:
8
+ # - filter out even numbers
9
+ # - square them
10
+ # - grab the first thousand
11
+
12
+ printf "%-30s", "IMPLEMENTATION"
13
+ printf "%12s", "take(10)"
14
+ printf "%12s", "take(100)"
15
+ printf "%12s", "take(1000)"
16
+ printf "%12s", "to_a"
17
+ puts ""
18
+
19
+ def measure(&block)
20
+ printf "%12.5f", Benchmark.realtime(&block)
21
+ end
22
+
23
+ def benchmark(description, control_result = nil)
24
+ result = nil
25
+ printf "%-30s", description
26
+ measure { yield.take(10).to_a }
27
+ measure { yield.take(100).to_a }
28
+ measure { result = yield.take(1000).to_a }
29
+ measure { yield.to_a }
30
+ puts ""
31
+ unless control_result.nil? || result == control_result
32
+ raise "unexpected result from '#{description}': #{result.inspect}"
33
+ end
34
+ result
35
+ end
36
+
37
+ @control = benchmark "eager" do
38
+ array.select { |x| x.even? }.collect { |x| x*x }
39
+ end
40
+
41
+ def can_require?(library)
42
+ require(library)
43
+ true
44
+ rescue LoadError
45
+ false
46
+ end
47
+
48
+ if array.respond_to?(:lazy)
49
+
50
+ benchmark "ruby2", @control do
51
+ array.lazy.select { |x| x.even? }.collect { |x| x*x }
52
+ end
53
+
54
+ elsif can_require?("backports/2.0.0")
55
+
56
+ benchmark "backports", @control do
57
+ array.lazy.select { |x| x.even? }.collect { |x| x*x }
58
+ end
59
+
60
+ end
61
+
62
+ if can_require? "facets"
63
+
64
+ benchmark "facets", @control do
65
+ array.defer.select { |x| x.even? }.collect { |x| x*x }
66
+ end
67
+
68
+ end
69
+
70
+ if defined?(Fiber) && can_require?("lazing")
71
+
72
+ module Enumerable
73
+ alias :lazing_select :selecting
74
+ alias :lazing_collect :collecting
75
+ end
76
+
77
+ benchmark "lazing", @control do
78
+ array.lazing_select { |x| x.even? }.lazing_collect { |x| x*x }
79
+ end
80
+
81
+ end
82
+
83
+ if can_require?("enumerating")
84
+
85
+ benchmark "enumerating", @control do
86
+ array.selecting { |x| x.even? }.collecting { |x| x*x }
87
+ end
88
+
89
+ end
90
+
91
+ if can_require?("lazily")
92
+
93
+ benchmark "lazily", @control do
94
+ array.lazily.select { |x| x.even? }.collect { |x| x*x }
95
+ end
96
+
97
+ end
@@ -4,6 +4,11 @@ module Lazily
4
4
 
5
5
  class << self
6
6
 
7
+ # Concatenates two or more Enumerables.
8
+ #
9
+ # @param enumerables [Array<Enumerable>]
10
+ # @return [Enumerable] elements of all the enumerables
11
+ #
7
12
  def concat(*enumerables)
8
13
  Concatenator.new(enumerables)
9
14
  end
@@ -12,6 +17,13 @@ module Lazily
12
17
 
13
18
  module Enumerable
14
19
 
20
+ # Concatenates this and other Enumerables.
21
+ #
22
+ # @param others [Array<Enumerable>]
23
+ # @return [Enumerable] elements of this and other Enumerables
24
+ #
25
+ # @see Array#concat
26
+ #
15
27
  def concat(*others)
16
28
  Lazily.concat(self, *others)
17
29
  end
@@ -8,6 +8,10 @@ module Lazily
8
8
  true
9
9
  end
10
10
 
11
+ def lazily
12
+ self
13
+ end
14
+
11
15
  end
12
16
 
13
17
  end
@@ -4,8 +4,13 @@ module Lazily
4
4
 
5
5
  module Enumerable
6
6
 
7
- def collect
8
- Filter.new do |output|
7
+ # Transform elements using the block provided.
8
+ # @return [Enumerable] the transformed elements
9
+ #
10
+ # @see ::Enumerable#collect
11
+ #
12
+ def collect(&transformation)
13
+ filter("collect") do |output|
9
14
  each do |element|
10
15
  output.call yield(element)
11
16
  end
@@ -14,8 +19,14 @@ module Lazily
14
19
 
15
20
  alias map collect
16
21
 
17
- def select
18
- Filter.new do |output|
22
+ # Select elements using a predicate block.
23
+ #
24
+ # @return [Enumerable] the elements that pass the predicate
25
+ #
26
+ # @see ::Enumerable#select
27
+ #
28
+ def select(&predicate)
29
+ filter("select") do |output|
19
30
  each do |element|
20
31
  output.call(element) if yield(element)
21
32
  end
@@ -24,54 +35,87 @@ module Lazily
24
35
 
25
36
  alias find_all select
26
37
 
38
+ # Select elements that fail a test.
39
+ #
40
+ # @yield [element] each element
41
+ # @yieldreturn [Boolean] truthy if the element passed the test
42
+ # @return [Enumerable] the elements which failed the test
43
+ #
44
+ # @see ::Enumerable#reject
45
+ #
27
46
  def reject
28
- Filter.new do |output|
47
+ filter("reject") do |output|
29
48
  each do |element|
30
49
  output.call(element) unless yield(element)
31
50
  end
32
51
  end
33
52
  end
34
53
 
54
+ # Remove duplicate values.
55
+ #
56
+ # @return [Enumerable] elements which have not been previously encountered
57
+ # @overload uniq
58
+ #
59
+ # @overload uniq(&block)
60
+ #
61
+ # @see ::Enumerable#uniq
62
+ #
35
63
  def uniq
36
- Filter.new do |output|
64
+ filter("uniq") do |output|
37
65
  seen = Set.new
38
66
  each do |element|
39
- output.call(element) if seen.add?(element)
40
- end
41
- end
42
- end
43
-
44
- def uniq_by
45
- Filter.new do |output|
46
- seen = Set.new
47
- each do |element|
48
- output.call(element) if seen.add?(yield element)
67
+ key = if block_given?
68
+ yield element
69
+ else
70
+ element
71
+ end
72
+ output.call(element) if seen.add?(key)
49
73
  end
50
74
  end
51
75
  end
52
76
 
77
+ # Select the first n elements.
78
+ #
79
+ # @param n [Integer] the number of elements to take
80
+ # @return [Enumerable] the first N elements
81
+ #
82
+ # @see ::Enumerable#take
83
+ #
53
84
  def take(n)
54
- Filter.new do |output|
85
+ filter("take") do |output|
55
86
  if n > 0
56
87
  each_with_index do |element, index|
57
88
  output.call(element)
58
- throw Lazily::Filter::DONE if index + 1 == n
89
+ throw Filter::DONE if index + 1 == n
59
90
  end
60
91
  end
61
92
  end
62
93
  end
63
94
 
64
- def take_while
65
- Filter.new do |output|
95
+ # Select elements while a predicate returns true.
96
+ #
97
+ # @return [Enumerable] all elements before the first that fails the predicate
98
+ #
99
+ # @see ::Enumerable#take_while
100
+ #
101
+ def take_while(&predicate)
102
+ filter("take_while") do |output|
66
103
  each do |element|
67
- throw Lazily::Filter::DONE unless yield(element)
104
+ throw Filter::DONE unless yield(element)
68
105
  output.call(element)
69
106
  end
70
107
  end
71
108
  end
72
109
 
110
+ # Ignore the first n elements.
111
+ #
112
+ # @param n [Integer] the number of elements to drop
113
+ # @return [Enumerable] elements after the first N
114
+ #
115
+ # @see ::Enumerable#drop
116
+ #
73
117
  def drop(n)
74
- Filter.new do |output|
118
+ filter("drop") do |output|
75
119
  each_with_index do |element, index|
76
120
  next if index < n
77
121
  output.call(element)
@@ -79,8 +123,14 @@ module Lazily
79
123
  end
80
124
  end
81
125
 
82
- def drop_while
83
- Filter.new do |output|
126
+ # Reject elements while a predicate returns true.
127
+ #
128
+ # @return [Enumerable] all elements including and after the first that fails the predicate
129
+ #
130
+ # @see ::Enumerable#drop_while
131
+ #
132
+ def drop_while(&predicate)
133
+ filter("drop_while") do |output|
84
134
  take = false
85
135
  each do |element|
86
136
  take ||= !yield(element)
@@ -89,17 +139,102 @@ module Lazily
89
139
  end
90
140
  end
91
141
 
142
+ # Select elements matching a pattern.
143
+ #
144
+ # @return [Enumerable] elements for which the pattern matches
145
+ #
146
+ # @see ::Enumerable#grep
147
+ #
148
+ def grep(pattern)
149
+ filter("grep") do |output|
150
+ each do |element|
151
+ if pattern === element
152
+ result = if block_given?
153
+ yield element
154
+ else
155
+ element
156
+ end
157
+ output.call(result)
158
+ end
159
+ end
160
+ end
161
+ end
162
+
163
+ # Flatten the collection, such that Enumerable elements are included inline.
164
+ #
165
+ # @return [Enumerable] elements of elements of the collection
166
+ #
167
+ # @see ::Array#flatten
168
+ #
169
+ def flatten(level = 1)
170
+ filter("flatten") do |output|
171
+ each do |element|
172
+ if level > 0 && element.respond_to?(:each)
173
+ element.flatten(level - 1).each(&output)
174
+ else
175
+ output.call(element)
176
+ end
177
+ end
178
+ end
179
+ end
180
+
181
+ def flat_map(&block)
182
+ map(&block).flatten
183
+ end
184
+
185
+ alias collect_concat flat_map
186
+
187
+ # Ignore nil values.
188
+ #
189
+ # @return [Enumerable] the elements that are not nil
190
+ #
191
+ # @see ::Array#compact
192
+ #
193
+ def compact
194
+ filter("compact") do |output|
195
+ each do |element|
196
+ output.call(element) unless element.nil?
197
+ end
198
+ end
199
+ end
200
+
201
+ if ::Enumerable.method_defined?(:slice_before)
202
+
203
+ def slice_before(*args, &block)
204
+ super.lazily
205
+ end
206
+
207
+ end
208
+
209
+ if ::Enumerable.method_defined?(:chunk)
210
+
211
+ def chunk(*args, &block)
212
+ super.lazily
213
+ end
214
+
215
+ end
216
+
217
+ # @return the nth element
218
+ #
92
219
  def [](n)
93
220
  drop(n).first
94
221
  end
95
222
 
223
+ private
224
+
225
+ def filter(method, &block)
226
+ Filter.new(self, method, &block)
227
+ end
228
+
96
229
  end
97
230
 
98
231
  class Filter
99
232
 
100
233
  include Lazily::Enumerable
101
234
 
102
- def initialize(&generator)
235
+ def initialize(source, method, &generator)
236
+ @source = source
237
+ @method = method
103
238
  @generator = generator
104
239
  end
105
240
 
@@ -113,6 +248,10 @@ module Lazily
113
248
  end
114
249
  end
115
250
 
251
+ def inspect
252
+ "#<#{self.class}: #{@method} #{@source.inspect}>"
253
+ end
254
+
116
255
  end
117
256
 
118
257
  end
@@ -4,11 +4,7 @@ module Lazily
4
4
 
5
5
  class << self
6
6
 
7
- def merge(*enumerables)
8
- Merger.new(enumerables)
9
- end
10
-
11
- def merge_by(*enumerables, &block)
7
+ def merge(*enumerables, &block)
12
8
  Merger.new(enumerables, &block)
13
9
  end
14
10
 
@@ -27,6 +27,10 @@ module Lazily
27
27
  @source.each(&block)
28
28
  end
29
29
 
30
+ def inspect
31
+ "#<#{self.class}: #{@source.inspect}>"
32
+ end
33
+
30
34
  end
31
35
 
32
36
  end
@@ -8,7 +8,7 @@ module Lazily
8
8
  collect do |item|
9
9
  Thread.new { block.call(item) }
10
10
  end.prefetch(max_threads - 1).collect do |thread|
11
- thread.join; thread.value
11
+ thread.join.value
12
12
  end
13
13
  end
14
14
  end
@@ -1,3 +1,3 @@
1
1
  module Lazily
2
- VERSION = "0.0.1".freeze
2
+ VERSION = "0.1.0".freeze
3
3
  end
@@ -2,36 +2,32 @@ require "spec_helper"
2
2
 
3
3
  describe Lazily, "concatenating" do
4
4
 
5
- describe ".concat" do
5
+ let(:array1) { [1,5,3] }
6
+ let(:array2) { [2,9,4] }
6
7
 
7
- let(:array1) { [1,5,3] }
8
- let(:array2) { [2,9,4] }
8
+ describe ".concat" do
9
9
 
10
10
  it "concatenates multiple Enumerables" do
11
- result = Lazily.concat([1,5,3], [2,9,4])
12
- result.to_a.should == [1,5,3,2,9,4]
11
+ result = Lazily.concat(array1, array2)
12
+ result.to_a.should == array1 + array2
13
13
  end
14
14
 
15
15
  it "is lazy" do
16
- result = Lazily.concat([3,4], [1,2].with_time_bomb)
17
- result.take(3).to_a.should == [3,4,1]
16
+ Lazily.concat(array1, array2.ecetera).should be_lazy
18
17
  end
19
18
 
20
19
  end
21
20
 
22
21
  describe "#concat" do
23
22
 
24
- let(:array1) { [1,5,3] }
25
- let(:array2) { [2,9,4] }
26
-
27
23
  it "concatenates multiple Enumerables" do
28
- result = [1,5,3].lazily.concat([2,9,4])
29
- result.to_a.should == [1,5,3,2,9,4]
24
+ result = array1.lazily.concat(array2)
25
+ result.to_a.should == array1 + array2
30
26
  end
31
27
 
32
28
  it "is lazy" do
33
- result = [3,4].lazily.concat([1,2].with_time_bomb)
34
- result.take(3).to_a.should == [3,4,1]
29
+ result = array1.lazily.concat(array2.ecetera)
30
+ result.take(3).to_a.should == (array1 + array2).take(3)
35
31
  end
36
32
 
37
33
  end
@@ -9,7 +9,7 @@ describe Lazily, "filter" do
9
9
  end
10
10
 
11
11
  it "is lazy" do
12
- [1,2,3].with_time_bomb.lazily.collect { |x| x * 2 }.first.should == 2
12
+ [1,2,3].ecetera.lazily.collect { |x| x * 2 }.should be_lazy
13
13
  end
14
14
 
15
15
  end
@@ -21,7 +21,7 @@ describe Lazily, "filter" do
21
21
  end
22
22
 
23
23
  it "is lazy" do
24
- (1..6).with_time_bomb.lazily.select { |x| x%2 == 0 }.first == 2
24
+ (1..6).ecetera.lazily.select { |x| x%2 == 0 }.should be_lazy
25
25
  end
26
26
 
27
27
  end
@@ -33,7 +33,7 @@ describe Lazily, "filter" do
33
33
  end
34
34
 
35
35
  it "is lazy" do
36
- (1..6).with_time_bomb.lazily.reject { |x| x%2 == 0 }.first == 1
36
+ (1..6).ecetera.lazily.reject { |x| x%2 == 0 }.should be_lazy
37
37
  end
38
38
 
39
39
  end
@@ -45,16 +45,16 @@ describe Lazily, "filter" do
45
45
  end
46
46
 
47
47
  it "is lazy" do
48
- [1,2,3].with_time_bomb.lazily.uniq.first.should == 1
48
+ [1,2,3].ecetera.lazily.uniq.should be_lazy
49
49
  end
50
50
 
51
- end
51
+ context "with a block" do
52
52
 
53
- describe "#uniq_by" do
53
+ it "uses the block to derive identity" do
54
+ array = %w(A1 A2 B1 A3 C1 B2 C2)
55
+ array.lazily.uniq { |s| s[0,1] }.to_a.should == %w(A1 B1 C1)
56
+ end
54
57
 
55
- it "uses the block to derive identity" do
56
- @array = %w(A1 A2 B1 A3 C1 B2 C2)
57
- @array.lazily.uniq_by { |s| s[0,1] }.to_a.should == %w(A1 B1 C1)
58
58
  end
59
59
 
60
60
  end
@@ -62,16 +62,16 @@ describe Lazily, "filter" do
62
62
  describe "#take" do
63
63
 
64
64
  it "includes the specified number" do
65
- @array = [1,2,3,4]
66
- @array.lazily.take(3).to_a.should == [1,2,3]
65
+ array = [1,2,3,4]
66
+ array.lazily.take(3).to_a.should == [1,2,3]
67
67
  end
68
68
 
69
69
  it "is lazy" do
70
- [1,2].with_time_bomb.lazily.take(2).to_a.should == [1,2]
70
+ [1,2].ecetera.lazily.take(2).should be_lazy
71
71
  end
72
72
 
73
73
  it "copes with 0" do
74
- [].with_time_bomb.lazily.take(0).to_a.should == []
74
+ [].ecetera.lazily.take(0).to_a.should == []
75
75
  end
76
76
 
77
77
  end
@@ -79,12 +79,12 @@ describe Lazily, "filter" do
79
79
  describe "#take_while" do
80
80
 
81
81
  it "takees elements as long as the predicate is true" do
82
- @array = [2,4,6,3]
83
- @array.lazily.take_while(&:even?).to_a.should == [2,4,6]
82
+ array = [2,4,6,3]
83
+ array.lazily.take_while(&:even?).to_a.should == [2,4,6]
84
84
  end
85
85
 
86
86
  it "is lazy" do
87
- [2,3].with_time_bomb.lazily.take_while(&:even?).to_a.should == [2]
87
+ [2,3].ecetera.lazily.take_while(&:even?).should be_lazy
88
88
  end
89
89
 
90
90
  end
@@ -92,12 +92,12 @@ describe Lazily, "filter" do
92
92
  describe "#drop" do
93
93
 
94
94
  it "excludes the specified number" do
95
- @array = [1,2,3,4]
96
- @array.lazily.drop(2).to_a.should == [3,4]
95
+ array = [1,2,3,4]
96
+ array.lazily.drop(2).to_a.should == [3,4]
97
97
  end
98
98
 
99
99
  it "is lazy" do
100
- [1,2,3,4].with_time_bomb.lazily.drop(2).lazily.take(1).to_a.should == [3]
100
+ [1,2,3,4].ecetera.lazily.drop(2).lazily.take(1).to_a.should == [3]
101
101
  end
102
102
 
103
103
  end
@@ -105,28 +105,158 @@ describe Lazily, "filter" do
105
105
  describe "#drop_while" do
106
106
 
107
107
  it "drops elements as long as the predicate is true" do
108
- @array = [2,4,6,3,4]
109
- @array.lazily.drop_while(&:even?).to_a.should == [3,4]
108
+ array = [2,4,6,3,4]
109
+ array.lazily.drop_while(&:even?).to_a.should == [3,4]
110
+ end
111
+
112
+ it "is lazy" do
113
+ [2,3].ecetera.lazily.drop_while(&:even?).lazily.should be_lazy
114
+ end
115
+
116
+ end
117
+
118
+ describe "#grep" do
119
+
120
+ let(:fruits) { %w(apple banana pear orange) }
121
+
122
+ it "returns elements matching a pattern" do
123
+ fruits.lazily.grep(/e$/).to_a.should == %w(apple orange)
124
+ end
125
+
126
+ it "applies the associated block" do
127
+ fruits.lazily.grep(/e$/, &:capitalize).to_a.should == %w(Apple Orange)
128
+ end
129
+
130
+ it "is lazy" do
131
+ fruits.ecetera.lazily.grep(/p/).should be_lazy
132
+ end
133
+
134
+ end
135
+
136
+ describe "#flatten" do
137
+
138
+ let(:array1) { [1,2,3] }
139
+ let(:array2) { [4,5,6] }
140
+
141
+ it "flattens" do
142
+ [[1,2], [3,4]].lazily.flatten.to_a.should == [1,2,3,4]
143
+ end
144
+
145
+ it "handles non-Enumerable elements" do
146
+ [1,2].lazily.flatten.to_a.should == [1,2]
147
+ end
148
+
149
+ it "goes one level deep (by default)" do
150
+ [[1,2], [[3]]].lazily.flatten.to_a.should == [1,2,[3]]
151
+ end
152
+
153
+ it "allows the depth to be specified" do
154
+ [[1], [[2]], [[[3]]]].lazily.flatten(2).to_a.should == [1, 2, [3]]
155
+ end
156
+
157
+ it "is lazy" do
158
+ [array1.ecetera, array2].lazily.flatten.should be_lazy
159
+ end
160
+
161
+ end
162
+
163
+ describe "#flat_map" do
164
+
165
+ let(:array) { [1,2,3] }
166
+
167
+ it "flattens resulting Enumerables" do
168
+ array.lazily.flat_map { |n| [n] * n }.to_a.should == [1,2,2,3,3,3]
169
+ end
170
+
171
+ it "handles blocks that don't return Enumerables" do
172
+ array.lazily.flat_map { |n| n * n }.to_a.should == [1,4,9]
173
+ end
174
+
175
+ it "handles nils " do
176
+ array.lazily.flat_map { nil }.to_a.should == [nil, nil, nil]
177
+ end
178
+
179
+ it "is lazy" do
180
+ array.ecetera.lazily.flat_map { |n| [n] * n }.should be_lazy
181
+ end
182
+
183
+ end
184
+
185
+ if ::Enumerable.method_defined?(:slice_before)
186
+
187
+ describe "#slice_before" do
188
+
189
+ let(:words) do
190
+ %w(One two three Two two three)
191
+ end
192
+
193
+ it "can slice with a pattern" do
194
+ words.lazily.slice_before(/^[A-Z]/).to_a.should == [
195
+ %w(One two three),
196
+ %w(Two two three)
197
+ ]
198
+ end
199
+
200
+ it "can slice with a block" do
201
+ words.lazily.slice_before { |w| w =~ /^[A-Z]/ }.to_a.should == [
202
+ %w(One two three),
203
+ %w(Two two three)
204
+ ]
205
+ end
206
+
207
+ it "is lazy" do
208
+ words.ecetera.lazily.slice_before(/^[A-Z]/).should be_lazy
209
+ end
210
+
211
+ end
212
+
213
+ end
214
+
215
+ if ::Enumerable.method_defined?(:chunk)
216
+
217
+ describe "#chunk" do
218
+
219
+ it "groups elements by the result of a block" do
220
+ [3,1,4,1,5,9,2,6].lazily.chunk(&:even?).to_a.should == [
221
+ [false, [3, 1]],
222
+ [true, [4]],
223
+ [false, [1, 5, 9]],
224
+ [true, [2, 6]],
225
+ ]
226
+ end
227
+
228
+ it "is lazy" do
229
+ [3,1,4,1,5,9,2,6].ecetera.lazily.chunk(&:even?).should be_lazy
230
+ end
231
+
232
+ end
233
+
234
+ end
235
+
236
+ describe "#compact" do
237
+
238
+ it "omits nils" do
239
+ [1, "too", nil, true, nil, false].lazily.compact.to_a.should == [1, "too", true, false]
110
240
  end
111
241
 
112
242
  it "is lazy" do
113
- [2,3].with_time_bomb.lazily.drop_while(&:even?).lazily.take(1).to_a.should == [3]
243
+ [nil].ecetera.lazily.compact.should be_lazy
114
244
  end
115
245
 
116
246
  end
117
247
 
118
248
  describe "#[]" do
119
249
 
120
- before do
121
- @evens = [1,2,3,4,5].lazily.collect { |x| x * 2 }
250
+ let(:evens) do
251
+ (1..999).lazily.collect { |x| x * 2 }
122
252
  end
123
253
 
124
254
  it "finds the specified element" do
125
- @evens.lazily[2].should == 6
255
+ evens.lazily[2].should == 6
126
256
  end
127
257
 
128
258
  it "is lazy" do
129
- @evens.with_time_bomb.lazily[3].should == 8
259
+ evens.ecetera.lazily[3].should == 8
130
260
  end
131
261
 
132
262
  end
@@ -5,29 +5,29 @@ describe Lazily, :needs_enumerators => true do
5
5
  describe ".merge" do
6
6
 
7
7
  it "merges multiple Enumerators" do
8
- @array1 = [1,3,6]
9
- @array2 = [2,4,7]
10
- @array3 = [5,8]
11
- @merge = Lazily.merge(@array1, @array2, @array3)
12
- @merge.to_a.should == [1,2,3,4,5,6,7,8]
8
+ array1 = [1,3,6]
9
+ array2 = [2,4,7]
10
+ array3 = [5,8]
11
+ merged = Lazily.merge(array1, array2, array3)
12
+ merged.to_a.should == [1,2,3,4,5,6,7,8]
13
13
  end
14
14
 
15
15
  it "is lazy" do
16
- @enum1 = [1,3,6]
17
- @enum2 = [2,4,7].with_time_bomb
18
- @merge = Lazily.merge(@enum1, @enum2)
19
- @merge.take(4).to_a.should == [1,2,3,4]
16
+ enum1 = [1,3,6]
17
+ enum2 = [2,4,7].ecetera
18
+ merged = Lazily.merge(enum1, enum2)
19
+ merged.should be_lazy
20
20
  end
21
21
 
22
- end
22
+ context "with a block" do
23
23
 
24
- describe ".merge_by" do
24
+ it "uses the block to determine order" do
25
+ array1 = %w(cccc dd a)
26
+ array2 = %w(eeeee bbb)
27
+ merged = Lazily.merge(array1, array2) { |s| -s.length }
28
+ merged.to_a.should == %w(eeeee cccc bbb dd a)
29
+ end
25
30
 
26
- it "uses the block to determine order" do
27
- @array1 = %w(cccc dd a)
28
- @array2 = %w(eeeee bbb)
29
- @merge = Lazily.merge_by(@array1, @array2) { |s| -s.length }
30
- @merge.to_a.should == %w(eeeee cccc bbb dd a)
31
31
  end
32
32
 
33
33
  end
@@ -37,7 +37,7 @@ describe Lazily do
37
37
  end
38
38
 
39
39
  it "is lazy" do
40
- source.with_time_bomb.lazily.prefetch(2).first.should eq(source.first)
40
+ source.ecetera.lazily.prefetch(2).should be_lazy
41
41
  end
42
42
 
43
43
  it "pre-computes the specified number of elements" do
@@ -13,7 +13,7 @@ describe Lazily, "threading" do
13
13
  end
14
14
 
15
15
  it "is lazy" do
16
- [1,2,3].with_time_bomb.lazily.in_threads(2) { |x| x * 2 }.first.should == 2
16
+ [1,2,3].ecetera.lazily.in_threads(2) { |x| x * 2 }.should be_lazy
17
17
  end
18
18
 
19
19
  def round(n, accuracy = 0.02)
@@ -10,12 +10,11 @@ describe Lazily, "zipping", :needs_enumerators => true do
10
10
 
11
11
  it "zips together multiple Enumerables" do
12
12
  zip = Lazily.zip(array1, array2, array3)
13
- zip.to_a.should == [[1,2,5], [3,4,8], [6,7,nil]]
13
+ zip.to_a.should == array1.zip(array2, array3)
14
14
  end
15
15
 
16
16
  it "is lazy" do
17
- zip = Lazily.zip(%w(a b c), [1,2].with_time_bomb)
18
- zip.take(2).to_a.should == [["a", 1], ["b", 2]]
17
+ Lazily.zip(array1, array2.ecetera).should be_lazy
19
18
  end
20
19
 
21
20
  end
@@ -24,12 +23,11 @@ describe Lazily, "zipping", :needs_enumerators => true do
24
23
 
25
24
  it "zips an Enumerable with multiple others" do
26
25
  zip = array1.lazily.zip(array2, array3)
27
- zip.to_a.should == [[1,2,5], [3,4,8], [6,7,nil]]
26
+ zip.to_a.should == array1.zip(array2, array3)
28
27
  end
29
28
 
30
29
  it "is lazy" do
31
- zip = %w(a b c).lazily.zip([1,2].with_time_bomb)
32
- zip.take(2).to_a.should == [["a", 1], ["b", 2]]
30
+ array1.lazily.zip(array2.ecetera).should be_lazy
33
31
  end
34
32
 
35
33
  end
@@ -2,7 +2,7 @@ require "lazily"
2
2
 
3
3
  class NotLazyEnough < StandardError; end
4
4
 
5
- class WithTimeBomb
5
+ class Ecetera
6
6
 
7
7
  include Enumerable
8
8
 
@@ -20,8 +20,8 @@ end
20
20
  module Enumerable
21
21
 
22
22
  # extend an Enumerable to throw an exception after last element
23
- def with_time_bomb
24
- WithTimeBomb.new(self)
23
+ def ecetera
24
+ Ecetera.new(self)
25
25
  end
26
26
 
27
27
  unless method_defined?(:first)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lazily
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Williams
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-04-19 00:00:00.000000000 Z
11
+ date: 2013-05-13 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: ! " Lazily implements \"lazy\" versions of many Enumerable methods,\n
14
14
  \ allowing streamed processing of large (or even infinite) collections.\n\n It's
@@ -22,8 +22,11 @@ files:
22
22
  - .gitignore
23
23
  - .rspec
24
24
  - .travis.yml
25
+ - CHANGES.md
25
26
  - Gemfile
27
+ - README.md
26
28
  - Rakefile
29
+ - benchmarks/pipeline_bench.rb
27
30
  - lazily.gemspec
28
31
  - lib/lazily.rb
29
32
  - lib/lazily/combining.rb
@@ -76,3 +79,4 @@ test_files:
76
79
  - spec/lazily/zipping_spec.rb
77
80
  - spec/lazily_spec.rb
78
81
  - spec/spec_helper.rb
82
+ has_rdoc: