reductions 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c4dcd97fa68d0d05cac8b5620fd5d534b0467708
4
+ data.tar.gz: 2b05ad9ba6f3068e4d1523ae908f1c76d0ff6670
5
+ SHA512:
6
+ metadata.gz: 92a36ebb66b117705f7b5059398b5b20d0cc38dcc377805a197fe6328805c63780d122860dac17c01bceece4c4317ae8beac8ed91ba31bfa0a277618704dd8f7
7
+ data.tar.gz: af67b82c462547faf25e71feacb48b229909677717fb72c8f2c3cd271f1d9b620ab04140259a59d3e8fd89d07d045d46aee300a93ec16ef9e29832904d623088
@@ -0,0 +1,70 @@
1
+ Reductions
2
+ ==========
3
+
4
+ Reductions is an addition to Ruby's Enumerable module that returns
5
+ an array containing all of the intermediate values that would be
6
+ generated in a call to Enumerable#reduce.
7
+
8
+ Installation
9
+ ============
10
+
11
+ `gem install reductions`
12
+
13
+ Usage
14
+ =====
15
+
16
+ require 'reductions'
17
+
18
+ (5..10).reductions(:+) # => [5, 11, 18, 26, 35, 45]
19
+ (5..10).reductions{|a,b| a + b} # => [5, 11, 18, 26, 35, 45]
20
+
21
+ # (5..10).reduce(:+) would result in 45
22
+
23
+ **If you have monkey-patched Enumerable#reduce** and need to
24
+ ensure #reductions works according to your patched #reduce, pass
25
+ `use_reduce: true` to reductions:
26
+
27
+ (5..10).reductions(:+,use_reduce: true)
28
+ (5..10).reductions(use_reduce: true){|a,b| a + b}
29
+
30
+ Using this flag increases the required execution time. See Benchmarking
31
+ section for performance difference.
32
+
33
+
34
+ Why a gem for this?
35
+ -------------------
36
+
37
+ You _could_ just provide a block to Enumerable#reduce that does the same
38
+ thing:
39
+
40
+ (5..10).reduce([]){|acc,n| acc += acc.last ? [acc.last + n] : [n] }
41
+
42
+ However, a gem is preferable for two key reasons:
43
+
44
+ * It allows for more compact, more understandable code. Compare the above with
45
+ `(5..10).reductions(:+)`.
46
+ * There are significant performance problems with the "provide a block to reduce"
47
+ approach once you get into reductions with tens of thousands of elements. See
48
+ "naïve reductions" in the Benchmarking section.
49
+
50
+
51
+ Benchmarking
52
+ ------------
53
+ The case being benchmarked here is `(1..100000).reductions{|a,b| a + b}`
54
+
55
+ user system total real
56
+ reduce 0.010000 0.000000 0.010000 ( 0.017606)
57
+ reductions 0.040000 0.000000 0.040000 ( 0.036335)
58
+ reductions use_reduce 0.050000 0.000000 0.050000 ( 0.051783)
59
+ naïve reductions 15.190000 0.530000 15.720000 ( 15.699648)
60
+
61
+ * `reduce` is a plain #reduce, included here to give a reference point for performance.
62
+ * `reductions` is the reduction using the exact case shown above.
63
+ Notice the full reduction --which returns an array
64
+ with 100,000 entries-- took only **~2** times as long as a plain reduce!
65
+ * `reductions use_reduce` is the reduction with the `use_reduce` flag set to
66
+ true. This reduction uses #reduce in its implementation.
67
+ * `naïve reductions` is the reduction using reduce as shown in the "Why a gem for this?"
68
+ section. The large number of incremental Array allocations makes
69
+ it _really_ slow, in this case taking more than 400 times as long
70
+ as the `reductions` version.
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'benchmark'
4
+ require 'reductions'
5
+
6
+ # For comparison purposes, monkey-patch in a naive implementation. Ignore all
7
+ # the setup and just look at the two return statements to understand what this
8
+ # is doing.
9
+ module Enumerable
10
+ def naive_reductions(init=nil,sym=nil,&block)
11
+ if sym == nil && block == nil && (init.kind_of?(Symbol) || init.kind_of?(String))
12
+ sym = init
13
+ init = nil
14
+ end
15
+
16
+ raise LocalJumpError.new("no block given") if !sym && !block
17
+
18
+ iv = init ? [init] : []
19
+ if block
20
+ return self.reduce(iv){|acc,n| acc += acc.last ? [yield(acc.last,n)] : [n]}
21
+ else
22
+ return self.reduce(iv){|acc,n| acc += acc.last ? [acc.last.method(sym).call(n)] : [n]}
23
+ end
24
+ end
25
+ end
26
+
27
+
28
+ # And now the benchmarking:
29
+
30
+ br = (1..100000)
31
+ ba = br.to_a
32
+
33
+ Benchmark.bmbm(29) do |b|
34
+ b.report('100K reduce') {ba.reduce{|a,n| a + n}}
35
+ b.report('100K reductions direct access') {ba.reductions{|a,n| a + n}}
36
+ b.report('100K reductions each-style') {br.reductions{|a,n| a + n}}
37
+ b.report('100K reductions use_reduce') {br.reductions(use_reduce: true){|a,n| a + n}}
38
+ b.report('100K naive reductions') {br.naive_reductions{|a,n| a + n}}
39
+ end
@@ -0,0 +1,162 @@
1
+
2
+ module Enumerable
3
+
4
+ # Returns an Array containing all the intermediate values collected when
5
+ # performing +Enumerable#reduce+. It accepts the same parameters and block
6
+ # as +Enumerable#reduce+, with the addition of the named parameter +use_reduce+
7
+ #
8
+ # *WARNING:* If you have monkey-patched +Enumerable#reduce+, you should pass
9
+ # +use\_reduce: true+ in the parameter list.
10
+ #
11
+ # @param [Object] init an initial value for the reduction. Just like the one for reduce....
12
+ #
13
+ # @param [Symbol] sym symbol representing a binary operation. Just like the one for reduce....
14
+ #
15
+ # @param [Bool] use_reduce: (named parameter) uses Enumerable#reduce in the
16
+ # implementation if true. Defaults to false for faster execution.
17
+ #
18
+ # @param [Block] block a block to use instead of a symbol. Just like the one for reduce....
19
+ #
20
+ # @return [Array] the array of intermediate values
21
+ #
22
+ # @example
23
+ # # These attempt to follow the examples used for Enumerable#reduce
24
+ #
25
+ # # Sum some numbers
26
+ # (5..10).reductions(:+) # => [5, 11, 18, 26, 35, 45]
27
+ # # Multiply some numbers
28
+ # (5..10).reductions(1, :*) # => [1, 5, 30, 210, 1680, 15120, 151200]
29
+ # # Same using a block
30
+ # (5..10).reductions(1) { |product, n| product * n } # => [1, 5, 30, 210, 1680, 15120, 151200]
31
+ # # Force the use of Enumerable#reduce
32
+ # (5..10).reductions(1, :*, use_reduce: true) # => [1, 5, 30, 210, 1680, 15120, 151200]
33
+ #
34
+ def reductions(init=nil,sym=nil,use_reduce: false,&block)
35
+
36
+ if sym == nil && block == nil && (init.kind_of?(Symbol) || init.kind_of?(String))
37
+ sym = init
38
+ init = nil
39
+ end
40
+
41
+ raise LocalJumpError.new("no block given") if !sym && !block
42
+
43
+ sz = get_reductions_prealloc_size
44
+
45
+ acc = init ? Array.new(sz + 1) : Array.new(sz)
46
+
47
+ # Use the slower version if requested
48
+ return reductions2(init: init,sym: sym,acc: acc,&block) if use_reduce
49
+
50
+ # Direct access reductions with no initial value are faster than
51
+ # each style reductions with no initial value because the latter
52
+ # are forced to perform a boolean test at each iteration
53
+ direct = self.respond_to?(:[])
54
+
55
+ # presence or absence of init changes how we treat index
56
+ # We use +yield+ rather than +block.call+ wherever possible since yield
57
+ # executes with less overhead.
58
+ if block && !init
59
+ if direct
60
+ acc[0] = self[0]
61
+ sz -= 1
62
+ sz.times do |j|
63
+ acc[j+1] = yield(acc[j],self[j+1])
64
+ end
65
+ else
66
+ self.each_with_index do |k,j|
67
+ if j == 0
68
+ acc[0] = k
69
+ else
70
+ acc[j] = yield(acc[j-1],k)
71
+ end
72
+ end
73
+ end
74
+
75
+ elsif block && init
76
+ acc[0] = init
77
+ self.each_with_index do |k,j|
78
+ acc[j+1] = yield(acc[j],k)
79
+ end
80
+
81
+ elsif !block && !init
82
+ if direct
83
+ acc[0] = self[0]
84
+ sz -= 1
85
+ sz.times do |j|
86
+ acc[j+1] = acc[j].method(sym).call(self[j+1])
87
+ end
88
+ else
89
+ self.each_with_index do |k,j|
90
+ if j == 0
91
+ acc[0] = k
92
+ else
93
+ acc[j] = acc[j-1].method(sym).call(k)
94
+ end
95
+ end
96
+ end
97
+
98
+ elsif !block && init
99
+ acc[0] = init
100
+ self.each_with_index do |k,j|
101
+ acc[j+1] = acc[j].method(sym).call(k)
102
+ end
103
+ end
104
+
105
+ return acc
106
+ end
107
+
108
+
109
+
110
+ private
111
+ # Returns the allocation size we need for the results.
112
+ def get_reductions_prealloc_size
113
+ if self.respond_to?(:size)
114
+ return self.size
115
+ else
116
+ # Works for any Enumerable, but generally slower than #size
117
+ return self.count
118
+ end
119
+ end
120
+
121
+
122
+ private
123
+ # Variant of reductions that uses Enumerable#reduce directly in its
124
+ # implementation, while still getting the advantages of result array
125
+ # pre-allocation. Parameter checking and array allocation occurs in
126
+ # #reductions, which then optionally calls this function.
127
+ def reductions2(init: nil,sym: nil, acc: [],&block)
128
+
129
+ acc[0] = init if init
130
+ idx = init ? 1 : 0
131
+ iv = [idx,acc]
132
+
133
+ if block
134
+ return self.reduce(iv) do |acc,n|
135
+ idx = acc[0]
136
+ v = acc[1]
137
+ if idx == 0
138
+ v[0] = n
139
+ else
140
+ v[idx] = yield(v[idx - 1],n)
141
+ end
142
+ acc[0] = idx + 1
143
+ acc
144
+ end.last
145
+ else
146
+ return self.reduce(iv) do |acc,n|
147
+ idx = acc[0]
148
+ v = acc[1]
149
+ if idx == 0
150
+ v[0] = n
151
+ else
152
+ v[idx] = v[idx - 1].method(sym).call(n)
153
+ end
154
+ acc[0] = idx + 1
155
+ acc
156
+ end.last
157
+ end
158
+ end
159
+
160
+ end
161
+
162
+ (1..5).reductions(:+,use_reduce: true)
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'minitest/autorun'
4
+ require 'reductions'
5
+
6
+ class TestReductions < Minitest::Test
7
+
8
+ def test_simple_block_reduction_esr
9
+ assert_equal [1,3,6,10,15], (1..5).reductions{|a,b| a + b}
10
+ end
11
+
12
+ def test_simple_block_reduction_dar
13
+ assert_equal [1,3,6,10,15], (1..5).to_a.reductions{|a,b| a + b}
14
+ end
15
+
16
+ def test_simple_block_reduction2
17
+ assert_equal [1,3,6,10,15], (1..5).reductions(use_reduce: true){|a,b| a + b}
18
+ end
19
+
20
+ def test_simple_sym_reduction_esr
21
+ assert_equal [1,3,6,10,15], (1..5).reductions(:+)
22
+ end
23
+
24
+ def test_simple_sym_reduction_dar
25
+ assert_equal [1,3,6,10,15], (1..5).to_a.reductions(:+)
26
+ end
27
+
28
+ def test_simple_sym_reduction2
29
+ assert_equal [1,3,6,10,15], (1..5).reductions(:+,use_reduce: true)
30
+ end
31
+
32
+ def test_simple_string_reduction_esr
33
+ assert_equal [1,3,6,10,15], (1..5).reductions('+')
34
+ end
35
+
36
+ def test_simple_string_reduction_dar
37
+ assert_equal [1,3,6,10,15], (1..5).to_a.reductions('+')
38
+ end
39
+
40
+ def test_simple_string_reduction2
41
+ assert_equal [1,3,6,10,15], (1..5).reductions('+',use_reduce: true)
42
+ end
43
+
44
+ def test_init_block_reduction_esr
45
+ assert_equal [2,3,5,8,12,17], (1..5).reductions(2){|a,b| a + b}
46
+ end
47
+
48
+ def test_init_block_reduction_dar
49
+ assert_equal [2,3,5,8,12,17], (1..5).to_a.reductions(2){|a,b| a + b}
50
+ end
51
+
52
+ def test_init_block_reduction2
53
+ assert_equal [2,3,5,8,12,17], (1..5).reductions(2,use_reduce: true){|a,b| a + b}
54
+ end
55
+
56
+ def test_init_sym_reduction_esr
57
+ assert_equal [2,3,5,8,12,17], (1..5).reductions(2,:+)
58
+ end
59
+
60
+ def test_init_sym_reduction_dar
61
+ assert_equal [2,3,5,8,12,17], (1..5).to_a.reductions(2,:+)
62
+ end
63
+
64
+ def test_init_sym_reduction2
65
+ assert_equal [2,3,5,8,12,17], (1..5).reductions(2,:+,use_reduce: true)
66
+ end
67
+
68
+ def test_raises_no_block
69
+ assert_raises(LocalJumpError){ (1..5).reductions }
70
+ end
71
+
72
+ end
metadata ADDED
@@ -0,0 +1,53 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: reductions
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Penn Taylor
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-03-13 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: |2
14
+ Reductions is an addition to Enumerable that returns
15
+ an array containing all of the intermediate values that would
16
+ be generated in a call to Enumerable#reduce.
17
+
18
+ (5..10).reductions(:+) # => [5, 11, 18, 26, 35, 45]
19
+ email: rpenn3@gmail.com
20
+ executables: []
21
+ extensions: []
22
+ extra_rdoc_files: []
23
+ files:
24
+ - README.md
25
+ - benchmarks/run_benchmarks.rb
26
+ - lib/reductions.rb
27
+ - tests/unit_tests.rb
28
+ homepage: https://github.com/penntaylor/reductions
29
+ licenses:
30
+ - MIT
31
+ metadata: {}
32
+ post_install_message:
33
+ rdoc_options: []
34
+ require_paths:
35
+ - lib
36
+ required_ruby_version: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ required_rubygems_version: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ requirements: []
47
+ rubyforge_project:
48
+ rubygems_version: 2.4.6
49
+ signing_key:
50
+ specification_version: 4
51
+ summary: Adds reductions capability to Enumerable
52
+ test_files: []
53
+ has_rdoc: