reductions 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.
@@ -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: