enumerating 1.1.1 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,10 @@
1
+ # Changelog
2
+
3
+ ## 1.2.0 (2013-04-16)
4
+
5
+ * Add `#threading`.
6
+ * Add `#prefetching`.
7
+
8
+ ## Previously
9
+
10
+ * Stuff happened
data/Gemfile CHANGED
@@ -1,4 +1,4 @@
1
- source :rubygems
1
+ source "https://rubygems.org"
2
2
 
3
3
  gemspec
4
4
 
@@ -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 each processing step proceeds in parallel, without creation of intermediate collections (Arrays), you can efficiently operate on large (or even infinite) Enumerable collections.
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
- # generates: [1,3,6,2,4,7]
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
- RUBY19 = RUBY_VERSION =~ /^1\.9/
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 RUBY19
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
@@ -1,2 +1,4 @@
1
1
  require 'enumerating/filtering'
2
2
  require 'enumerating/mixing'
3
+ require 'enumerating/prefetching'
4
+ require 'enumerating/threading'
@@ -107,5 +107,8 @@ module Enumerable
107
107
  end
108
108
  end
109
109
 
110
- end
110
+ def [](n)
111
+ dropping(n).first
112
+ end
111
113
 
114
+ end
@@ -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
@@ -1,3 +1,3 @@
1
1
  module Enumerating
2
- VERSION = "1.1.1".freeze
2
+ VERSION = "1.2.0".freeze
3
3
  end
@@ -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
@@ -26,12 +26,12 @@ class WithTimeBomb
26
26
  end
27
27
 
28
28
  module Enumerable
29
-
29
+
30
30
  # extend an Enumerable to throw an exception after last element
31
31
  def with_time_bomb
32
32
  WithTimeBomb.new(self)
33
33
  end
34
-
34
+
35
35
  end
36
36
 
37
37
  require "enumerating"
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.1.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: 2011-08-25 00:00:00.000000000Z
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.markdown
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: 1.8.6
64
+ rubygems_version: 2.0.0
67
65
  signing_key:
68
- specification_version: 3
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