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 +8 -8
- data/.gitignore +2 -0
- data/CHANGES.md +5 -0
- data/Gemfile +1 -0
- data/README.md +147 -0
- data/Rakefile +6 -0
- data/benchmarks/pipeline_bench.rb +97 -0
- data/lib/lazily/concatenating.rb +12 -0
- data/lib/lazily/enumerable.rb +4 -0
- data/lib/lazily/filtering.rb +164 -25
- data/lib/lazily/merging.rb +1 -5
- data/lib/lazily/proxy.rb +4 -0
- data/lib/lazily/threading.rb +1 -1
- data/lib/lazily/version.rb +1 -1
- data/spec/lazily/concatenating_spec.rb +10 -14
- data/spec/lazily/filtering_spec.rb +156 -26
- data/spec/lazily/merging_spec.rb +16 -16
- data/spec/lazily/prefetching_spec.rb +1 -1
- data/spec/lazily/threading_spec.rb +1 -1
- data/spec/lazily/zipping_spec.rb +4 -6
- data/spec/spec_helper.rb +3 -3
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
OTA2Zjk3MzAxNDg4N2JhMGFhZDAyNTJjZmQ3YzExYzFkNDA4NmZlMw==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
YmY5MzM3N2ZiYTZkZWVjNzMzNTE2NjhjMmQ1NDZhN2UyYjgxODcxOQ==
|
7
7
|
!binary "U0hBNTEy":
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
OThlZDA3NGU5ZTAzMmUxZjY0ZmJkMGU3OGE0MDZhZGE0ZTE5NDI5ZDBlZmVi
|
10
|
+
ODFmYjEyYmVmNGUwYmMwMzllYjU0NGUwMTgwN2I5NTc5NjExM2U1MTI0MmY3
|
11
|
+
ZjYyOTJiZTMwN2I1ZGMwNmUzYWM5ZWZkMmZjZjJhYTkxOWRlYzI=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
NDA3OTM0ZWMzOGNkMDY3M2JjNmYwYzg2Zjk4NmY0MjNlZDY2ZWZkY2I0NzMx
|
14
|
+
Y2E5MzJmODU5MTgyYTk4MjUxZWIxYWQwNzM5NjExNWRhNDFhYTgzYjM3NGE3
|
15
|
+
Yzk5NjVmMjBjMjBkMDcyZmM2YTAwNzI1YzcwZTA1NTBiZmIyZDM=
|
data/.gitignore
CHANGED
data/CHANGES.md
ADDED
data/Gemfile
CHANGED
data/README.md
ADDED
@@ -0,0 +1,147 @@
|
|
1
|
+
Lazily [](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
@@ -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
|
data/lib/lazily/concatenating.rb
CHANGED
@@ -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
|
data/lib/lazily/enumerable.rb
CHANGED
data/lib/lazily/filtering.rb
CHANGED
@@ -4,8 +4,13 @@ module Lazily
|
|
4
4
|
|
5
5
|
module Enumerable
|
6
6
|
|
7
|
-
|
8
|
-
|
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
|
-
|
18
|
-
|
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
|
-
|
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
|
-
|
64
|
+
filter("uniq") do |output|
|
37
65
|
seen = Set.new
|
38
66
|
each do |element|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
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
|
-
|
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
|
89
|
+
throw Filter::DONE if index + 1 == n
|
59
90
|
end
|
60
91
|
end
|
61
92
|
end
|
62
93
|
end
|
63
94
|
|
64
|
-
|
65
|
-
|
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
|
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
|
-
|
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
|
-
|
83
|
-
|
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
|
data/lib/lazily/merging.rb
CHANGED
data/lib/lazily/proxy.rb
CHANGED
data/lib/lazily/threading.rb
CHANGED
data/lib/lazily/version.rb
CHANGED
@@ -2,36 +2,32 @@ require "spec_helper"
|
|
2
2
|
|
3
3
|
describe Lazily, "concatenating" do
|
4
4
|
|
5
|
-
|
5
|
+
let(:array1) { [1,5,3] }
|
6
|
+
let(:array2) { [2,9,4] }
|
6
7
|
|
7
|
-
|
8
|
-
let(:array2) { [2,9,4] }
|
8
|
+
describe ".concat" do
|
9
9
|
|
10
10
|
it "concatenates multiple Enumerables" do
|
11
|
-
result = Lazily.concat(
|
12
|
-
result.to_a.should ==
|
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
|
-
|
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 =
|
29
|
-
result.to_a.should ==
|
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 =
|
34
|
-
result.take(3).to_a.should ==
|
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].
|
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).
|
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).
|
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].
|
48
|
+
[1,2,3].ecetera.lazily.uniq.should be_lazy
|
49
49
|
end
|
50
50
|
|
51
|
-
|
51
|
+
context "with a block" do
|
52
52
|
|
53
|
-
|
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
|
-
|
66
|
-
|
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].
|
70
|
+
[1,2].ecetera.lazily.take(2).should be_lazy
|
71
71
|
end
|
72
72
|
|
73
73
|
it "copes with 0" do
|
74
|
-
[].
|
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
|
-
|
83
|
-
|
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].
|
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
|
-
|
96
|
-
|
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].
|
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
|
-
|
109
|
-
|
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
|
-
[
|
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
|
-
|
121
|
-
|
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
|
-
|
255
|
+
evens.lazily[2].should == 6
|
126
256
|
end
|
127
257
|
|
128
258
|
it "is lazy" do
|
129
|
-
|
259
|
+
evens.ecetera.lazily[3].should == 8
|
130
260
|
end
|
131
261
|
|
132
262
|
end
|
data/spec/lazily/merging_spec.rb
CHANGED
@@ -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
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
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
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
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
|
-
|
22
|
+
context "with a block" do
|
23
23
|
|
24
|
-
|
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
|
data/spec/lazily/zipping_spec.rb
CHANGED
@@ -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 ==
|
13
|
+
zip.to_a.should == array1.zip(array2, array3)
|
14
14
|
end
|
15
15
|
|
16
16
|
it "is lazy" do
|
17
|
-
|
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 ==
|
26
|
+
zip.to_a.should == array1.zip(array2, array3)
|
28
27
|
end
|
29
28
|
|
30
29
|
it "is lazy" do
|
31
|
-
|
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
|
data/spec/spec_helper.rb
CHANGED
@@ -2,7 +2,7 @@ require "lazily"
|
|
2
2
|
|
3
3
|
class NotLazyEnough < StandardError; end
|
4
4
|
|
5
|
-
class
|
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
|
24
|
-
|
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
|
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-
|
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:
|