lazily 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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 [![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
@@ -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:
|