enumerating 1.1.1 → 1.2.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 +15 -0
- data/.rspec +1 -0
- data/CHANGES.md +10 -0
- data/Gemfile +1 -1
- data/{README.markdown → README.md} +18 -2
- data/benchmarks/pipeline_bench.rb +10 -6
- data/lib/enumerating.rb +2 -0
- data/lib/enumerating/filtering.rb +4 -1
- data/lib/enumerating/prefetching.rb +41 -0
- data/lib/enumerating/threading.rb +51 -0
- data/lib/enumerating/version.rb +1 -1
- data/spec/enumerating/filtering_spec.rb +35 -19
- data/spec/enumerating/prefetching_spec.rb +69 -0
- data/spec/enumerating/threading_spec.rb +46 -0
- data/spec/spec_helper.rb +2 -2
- metadata +14 -14
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
MGI2YTQ2Y2Y0ZmFjOTljNmEzMjU2ZDc4YTFkNWE3ZjY0MWY4Mjg1Ng==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
ODUwMjlhZmYzN2M1ZTgxNTkwNmI4N2E4YTg5YzI5ZmY2NTJiNTgyZQ==
|
7
|
+
!binary "U0hBNTEy":
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
MWYzMjdhYTNlYjE3ZjRkODI0NDQzMmQ0YTA3NDIwNjFjMDViMWYwNjZhMmVk
|
10
|
+
OTBiZWEwMTJjODY2NjFiYzhlYzMwMGI4ODUwNTU3MWViNjYyYzNhNzIwNTdj
|
11
|
+
ZWEwNGRkMWZjY2ZkYjM1YzUwZjJhNzc2YzZmNDcxNDBjYTg0MTE=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
M2U3ZWJjYjY0NjlkOGQyOTg2MjVhOTYzMTk1NjgzYjg2OGFkM2E5MzBiMGYz
|
14
|
+
ZjU2ZmZmNTJjMjIwYmRiNGNjYTk4Yzg3YTBkNjVmMTQzYjNhMjUxMzRlNDE1
|
15
|
+
OGQyNjUxMTg2NjE1ZGEyODJhZDczNDA1Nzc4OTEyY2ZmNjBiOGE=
|
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/CHANGES.md
ADDED
data/Gemfile
CHANGED
@@ -13,6 +13,7 @@ Enumerating extends Enumerable with "lazy" versions of various common operations
|
|
13
13
|
* `#mapping` is an alias for `#collecting` (like `Enumerable#map`)
|
14
14
|
* `#uniqing` discards duplicates (like `Enumerable#uniq`)
|
15
15
|
* `#taking`, `#taking_while`, `#dropping` and `#dropping_while` also do what you expect
|
16
|
+
* '#[]' gets the nth element of a collection
|
16
17
|
|
17
18
|
We say the "...ing" variants are "lazy", because they defer per-element processing until the result is used. They return Enumerable "result proxy" objects, rather than Arrays, and only perform the actual filtering (or transformation) as the result proxy is enumerated.
|
18
19
|
|
@@ -60,7 +61,22 @@ By combining two or more of the lazy operations provided by Enumerating, you can
|
|
60
61
|
# resolve
|
61
62
|
companies.to_a #=> ["Disney"]
|
62
63
|
|
63
|
-
Because
|
64
|
+
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.
|
65
|
+
|
66
|
+
Multi-threaded processing
|
67
|
+
-------------------------
|
68
|
+
|
69
|
+
`Enumerable#threading` is a multi-threaded version of `Enumerable#collecting`, allowing multiple Ruby Thread's to be used to process elements of an collection. It requires a numeric argument specifying the maximum number of Threads to use.
|
70
|
+
|
71
|
+
start = Time.now
|
72
|
+
[5,6,7,8].threading(4) do |delay|
|
73
|
+
sleep(delay)
|
74
|
+
end.to_a
|
75
|
+
(Time.now - start).to_i #=> 8
|
76
|
+
|
77
|
+
Outputs will be yielded in the expected order, making it a drop-in replacement for `#collecting`.
|
78
|
+
|
79
|
+
Unlike some other "parallel map" implementations, the output of `#threading` is lazy, though it does need to pre-fetch elements from the source collection as required to start Threads.
|
64
80
|
|
65
81
|
Lazy combination of Enumerables
|
66
82
|
-------------------------------
|
@@ -78,7 +94,7 @@ Enumerating also provides some interesting ways to combine several Enumerable co
|
|
78
94
|
array1 = [1,3,6]
|
79
95
|
array2 = [2,4,7]
|
80
96
|
Enumerating.concatenating(array1, array2)
|
81
|
-
|
97
|
+
# generates: [1,3,6,2,4,7]
|
82
98
|
|
83
99
|
`Enumerating.merging` merges multiple collections, preserving sort-order. The inputs are assumed to be sorted already.
|
84
100
|
|
@@ -2,9 +2,7 @@ require 'benchmark'
|
|
2
2
|
|
3
3
|
$: << File.expand_path("../../lib", __FILE__)
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
if RUBY19
|
5
|
+
if defined?(Fiber)
|
8
6
|
require "lazing"
|
9
7
|
module Enumerable
|
10
8
|
alias :lazing_select :selecting
|
@@ -52,20 +50,26 @@ def benchmark(description, control_result = nil)
|
|
52
50
|
result
|
53
51
|
end
|
54
52
|
|
55
|
-
@control = benchmark "conventional" do
|
53
|
+
@control = benchmark "conventional (eager)" do
|
56
54
|
array.select { |x| x.even? }.collect { |x| x*x }
|
57
55
|
end
|
58
56
|
|
59
|
-
benchmark "enumerating", @control do
|
57
|
+
benchmark "enumerating", @control do
|
60
58
|
array.selecting { |x| x.even? }.collecting { |x| x*x }
|
61
59
|
end
|
62
60
|
|
63
|
-
if
|
61
|
+
if defined?(Fiber)
|
64
62
|
benchmark "lazing", @control do
|
65
63
|
array.lazing_select { |x| x.even? }.lazing_collect { |x| x*x }
|
66
64
|
end
|
67
65
|
end
|
68
66
|
|
67
|
+
if array.respond_to?(:lazy)
|
68
|
+
benchmark "ruby2 Enumerable#lazy", @control do
|
69
|
+
array.lazy.select { |x| x.even? }.lazy.collect { |x| x*x }
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
69
73
|
benchmark "facets Enumerable#defer", @control do
|
70
74
|
array.defer.select { |x| x.even? }.collect { |x| x*x }
|
71
75
|
end
|
data/lib/enumerating.rb
CHANGED
@@ -0,0 +1,41 @@
|
|
1
|
+
module Enumerating
|
2
|
+
|
3
|
+
class Prefetcher
|
4
|
+
|
5
|
+
include Enumerable
|
6
|
+
|
7
|
+
def initialize(source, buffer_size)
|
8
|
+
@source = source.to_enum
|
9
|
+
@buffer_size = buffer_size
|
10
|
+
end
|
11
|
+
|
12
|
+
def each(&block)
|
13
|
+
return @source.each(&block) if @buffer_size <= 0
|
14
|
+
buffered_elements = []
|
15
|
+
i = 0
|
16
|
+
@source.each do |element|
|
17
|
+
slot = i % @buffer_size
|
18
|
+
if i >= @buffer_size
|
19
|
+
yield buffered_elements[slot]
|
20
|
+
end
|
21
|
+
buffered_elements[slot] = element
|
22
|
+
i += 1
|
23
|
+
end
|
24
|
+
buffered_elements.size.times do
|
25
|
+
slot = i % buffered_elements.size
|
26
|
+
yield buffered_elements[slot]
|
27
|
+
i += 1
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
module Enumerable
|
36
|
+
|
37
|
+
def prefetching(size)
|
38
|
+
Enumerating::Prefetcher.new(self, size)
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'enumerating/prefetching'
|
2
|
+
|
3
|
+
module Enumerating
|
4
|
+
|
5
|
+
class ThreadStarter
|
6
|
+
|
7
|
+
include Enumerable
|
8
|
+
|
9
|
+
def initialize(source, block)
|
10
|
+
@source = source
|
11
|
+
@block = block
|
12
|
+
end
|
13
|
+
|
14
|
+
def each
|
15
|
+
@source.each do |source_item|
|
16
|
+
thread = Thread.new do
|
17
|
+
@block.call(source_item)
|
18
|
+
end
|
19
|
+
yield thread
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
class ThreadJoiner
|
26
|
+
|
27
|
+
include Enumerable
|
28
|
+
|
29
|
+
def initialize(threads)
|
30
|
+
@threads = threads
|
31
|
+
end
|
32
|
+
|
33
|
+
def each
|
34
|
+
@threads.each do |thread|
|
35
|
+
thread.join
|
36
|
+
yield thread.value
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
module Enumerable
|
45
|
+
|
46
|
+
def threading(max_threads, &block)
|
47
|
+
threads = Enumerating::ThreadStarter.new(self, block)
|
48
|
+
Enumerating::ThreadJoiner.new(threads.prefetching(max_threads - 1))
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
data/lib/enumerating/version.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require "spec_helper"
|
2
2
|
|
3
3
|
module Enumerable
|
4
|
-
|
4
|
+
|
5
5
|
unless method_defined?(:first)
|
6
6
|
def first
|
7
7
|
each do |first_item|
|
@@ -9,7 +9,7 @@ module Enumerable
|
|
9
9
|
end
|
10
10
|
end
|
11
11
|
end
|
12
|
-
|
12
|
+
|
13
13
|
end
|
14
14
|
|
15
15
|
describe Enumerable do
|
@@ -70,61 +70,77 @@ describe Enumerable do
|
|
70
70
|
end
|
71
71
|
|
72
72
|
end
|
73
|
-
|
73
|
+
|
74
74
|
describe "#taking" do
|
75
|
-
|
75
|
+
|
76
76
|
it "includes the specified number" do
|
77
77
|
@array = [1,2,3,4]
|
78
78
|
@array.taking(3).to_a.should == [1,2,3]
|
79
79
|
end
|
80
|
-
|
80
|
+
|
81
81
|
it "is lazy" do
|
82
82
|
[1,2].with_time_bomb.taking(2).to_a.should == [1,2]
|
83
83
|
end
|
84
|
-
|
84
|
+
|
85
85
|
it "copes with 0" do
|
86
86
|
[].with_time_bomb.taking(0).to_a.should == []
|
87
87
|
end
|
88
|
-
|
88
|
+
|
89
89
|
end
|
90
|
-
|
90
|
+
|
91
91
|
describe "#taking_while" do
|
92
|
-
|
92
|
+
|
93
93
|
it "takes elements as long as the predicate is true" do
|
94
94
|
@array = [2,4,6,3]
|
95
95
|
@array.taking_while(&:even?).to_a.should == [2,4,6]
|
96
96
|
end
|
97
|
-
|
97
|
+
|
98
98
|
it "is lazy" do
|
99
99
|
[2,3].with_time_bomb.taking_while(&:even?).to_a.should == [2]
|
100
100
|
end
|
101
|
-
|
101
|
+
|
102
102
|
end
|
103
|
-
|
103
|
+
|
104
104
|
describe "#dropping" do
|
105
|
-
|
105
|
+
|
106
106
|
it "excludes the specified number" do
|
107
107
|
@array = [1,2,3,4]
|
108
108
|
@array.dropping(2).to_a.should == [3,4]
|
109
109
|
end
|
110
|
-
|
110
|
+
|
111
111
|
it "is lazy" do
|
112
112
|
[1,2,3,4].with_time_bomb.dropping(2).taking(1).to_a.should == [3]
|
113
113
|
end
|
114
|
-
|
114
|
+
|
115
115
|
end
|
116
|
-
|
116
|
+
|
117
117
|
describe "#dropping_while" do
|
118
|
-
|
118
|
+
|
119
119
|
it "drops elements as long as the predicate is true" do
|
120
120
|
@array = [2,4,6,3,4]
|
121
121
|
@array.dropping_while(&:even?).to_a.should == [3,4]
|
122
122
|
end
|
123
|
-
|
123
|
+
|
124
124
|
it "is lazy" do
|
125
125
|
[2,3].with_time_bomb.dropping_while(&:even?).taking(1).to_a.should == [3]
|
126
126
|
end
|
127
|
-
|
127
|
+
|
128
|
+
end
|
129
|
+
|
130
|
+
describe "#[]" do
|
131
|
+
|
132
|
+
before do
|
133
|
+
@evens = [1,2,3,4,5].collecting { |x| x * 2 }
|
134
|
+
end
|
135
|
+
|
136
|
+
it "finds the specified element" do
|
137
|
+
@evens[2].should == 6
|
138
|
+
end
|
139
|
+
|
140
|
+
it "is lazy" do
|
141
|
+
@evens.with_time_bomb[4].should == 10
|
142
|
+
end
|
143
|
+
|
128
144
|
end
|
129
145
|
|
130
146
|
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Enumerable do
|
4
|
+
|
5
|
+
Counter = Class.new do
|
6
|
+
|
7
|
+
include Enumerable
|
8
|
+
|
9
|
+
def initialize(source)
|
10
|
+
@source = source
|
11
|
+
@number_yielded = 0
|
12
|
+
end
|
13
|
+
|
14
|
+
def each
|
15
|
+
@source.each do |item|
|
16
|
+
@number_yielded += 1
|
17
|
+
yield item
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_reader :number_yielded
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
describe "#prefetching" do
|
26
|
+
|
27
|
+
let(:source) { [1, 2, 3, 4, nil, false, 7] }
|
28
|
+
|
29
|
+
it "yields all items" do
|
30
|
+
source.prefetching(2).to_a.should eq(source)
|
31
|
+
source.prefetching(3).to_a.should eq(source)
|
32
|
+
end
|
33
|
+
|
34
|
+
it "is stateless" do
|
35
|
+
source.prefetching(2).first.should eq(source.first)
|
36
|
+
source.prefetching(2).first.should eq(source.first)
|
37
|
+
end
|
38
|
+
|
39
|
+
it "is lazy" do
|
40
|
+
source.with_time_bomb.prefetching(2).first.should eq(source.first)
|
41
|
+
end
|
42
|
+
|
43
|
+
it "pre-computes the specified number of elements" do
|
44
|
+
counter = Counter.new(source)
|
45
|
+
counter.prefetching(2).take(1)
|
46
|
+
counter.number_yielded.should eq(3)
|
47
|
+
end
|
48
|
+
|
49
|
+
context "with a buffer size of zero" do
|
50
|
+
|
51
|
+
it "does not pre-fetch anything" do
|
52
|
+
counter = Counter.new(source)
|
53
|
+
counter.prefetching(0).take(1)
|
54
|
+
counter.number_yielded.should eq(1)
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
context "with a buffer bigger than the source Enumerable" do
|
60
|
+
|
61
|
+
it "yields all items" do
|
62
|
+
source.prefetching(20).to_a.should eq(source)
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Enumerable do
|
4
|
+
|
5
|
+
describe "#threading" do
|
6
|
+
|
7
|
+
it "acts like #collect" do
|
8
|
+
[1,2,3].threading(5) { |x| x * 2 }.to_a.should == [2,4,6]
|
9
|
+
end
|
10
|
+
|
11
|
+
it "runs things in separate threads" do
|
12
|
+
[1,2,3].threading(5) { Thread.current.object_id }.to_a.uniq.size.should eq(3)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "is lazy" do
|
16
|
+
[1,2,3].with_time_bomb.threading(2) { |x| x * 2 }.first.should == 2
|
17
|
+
end
|
18
|
+
|
19
|
+
it "runs the specified number of threads in parallel" do
|
20
|
+
delays = [0.01, 0.01, 0.01]
|
21
|
+
start = Time.now
|
22
|
+
delays.threading(2) do |delay|
|
23
|
+
sleep(delay)
|
24
|
+
end.to_a
|
25
|
+
(Time.now - start).round(2).should eq(0.01 * 2)
|
26
|
+
end
|
27
|
+
|
28
|
+
it "acts as a sliding window" do
|
29
|
+
delays = [0.05, 0.04, 0.03, 0.02, 0.01]
|
30
|
+
start = Time.now
|
31
|
+
elapsed_times = delays.threading(3) do |delay|
|
32
|
+
sleep(delay)
|
33
|
+
(Time.now - start).round(2)
|
34
|
+
end
|
35
|
+
elapsed_times.to_a.should eq([0.05, 0.04, 0.03, 0.07, 0.06])
|
36
|
+
end
|
37
|
+
|
38
|
+
it "surfaces exceptions" do
|
39
|
+
lambda do
|
40
|
+
[1,2,3].threading(5) { raise "hell" }.to_a
|
41
|
+
end.should raise_error(RuntimeError, "hell")
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,15 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: enumerating
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
5
|
-
prerelease:
|
4
|
+
version: 1.2.0
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Mike Williams
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date:
|
11
|
+
date: 2013-04-15 00:00:00.000000000 Z
|
13
12
|
dependencies: []
|
14
13
|
description: Enumerating extends Enumerable with "lazy" versions of various operations,
|
15
14
|
allowing streamed processing of large (or even infinite) collections. Even in Ruby
|
@@ -20,8 +19,10 @@ extensions: []
|
|
20
19
|
extra_rdoc_files: []
|
21
20
|
files:
|
22
21
|
- .gitignore
|
22
|
+
- .rspec
|
23
|
+
- CHANGES.md
|
23
24
|
- Gemfile
|
24
|
-
- README.
|
25
|
+
- README.md
|
25
26
|
- Rakefile
|
26
27
|
- benchmarks/pipeline_bench.rb
|
27
28
|
- enumerating.gemspec
|
@@ -30,46 +31,45 @@ files:
|
|
30
31
|
- lib/enumerating/filtering.rb
|
31
32
|
- lib/enumerating/merging.rb
|
32
33
|
- lib/enumerating/mixing.rb
|
34
|
+
- lib/enumerating/prefetching.rb
|
35
|
+
- lib/enumerating/threading.rb
|
33
36
|
- lib/enumerating/version.rb
|
34
37
|
- lib/enumerating/zipping.rb
|
35
38
|
- spec/enumerating/concatenating_spec.rb
|
36
39
|
- spec/enumerating/filtering_spec.rb
|
37
40
|
- spec/enumerating/merging_spec.rb
|
41
|
+
- spec/enumerating/prefetching_spec.rb
|
42
|
+
- spec/enumerating/threading_spec.rb
|
38
43
|
- spec/enumerating/zipping_spec.rb
|
39
44
|
- spec/spec_helper.rb
|
40
45
|
homepage: http://github.com/mdub/enumerating
|
41
46
|
licenses: []
|
47
|
+
metadata: {}
|
42
48
|
post_install_message:
|
43
49
|
rdoc_options: []
|
44
50
|
require_paths:
|
45
51
|
- lib
|
46
52
|
required_ruby_version: !ruby/object:Gem::Requirement
|
47
|
-
none: false
|
48
53
|
requirements:
|
49
54
|
- - ! '>='
|
50
55
|
- !ruby/object:Gem::Version
|
51
56
|
version: '0'
|
52
|
-
segments:
|
53
|
-
- 0
|
54
|
-
hash: -2370472515613642162
|
55
57
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
56
|
-
none: false
|
57
58
|
requirements:
|
58
59
|
- - ! '>='
|
59
60
|
- !ruby/object:Gem::Version
|
60
61
|
version: '0'
|
61
|
-
segments:
|
62
|
-
- 0
|
63
|
-
hash: -2370472515613642162
|
64
62
|
requirements: []
|
65
63
|
rubyforge_project:
|
66
|
-
rubygems_version:
|
64
|
+
rubygems_version: 2.0.0
|
67
65
|
signing_key:
|
68
|
-
specification_version:
|
66
|
+
specification_version: 4
|
69
67
|
summary: Lazy filtering/transforming of Enumerable collections
|
70
68
|
test_files:
|
71
69
|
- spec/enumerating/concatenating_spec.rb
|
72
70
|
- spec/enumerating/filtering_spec.rb
|
73
71
|
- spec/enumerating/merging_spec.rb
|
72
|
+
- spec/enumerating/prefetching_spec.rb
|
73
|
+
- spec/enumerating/threading_spec.rb
|
74
74
|
- spec/enumerating/zipping_spec.rb
|
75
75
|
- spec/spec_helper.rb
|