idempotent_enumerable 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.rubocop_todo.yml +1 -0
- data/README.md +217 -0
- data/benchmarks/set.rb +17 -0
- data/idempotent_enumerable.gemspec +39 -0
- data/lib/idempotent_enumerable.rb +142 -0
- data/lib/idempotent_enumerable/configurator.rb +34 -0
- metadata +190 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 5ac991903381b9f945b1f4cc0d2336efc0523b2d
|
4
|
+
data.tar.gz: 963db8eb4ae066178ce0b22df3b9fbf1315549db
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 97fa573e2be5ae08a6837e988f75adc737fbcbd436223b37581dfdce045062312d9b30590a300e9dbaa4b4147bef6935181ed77e3e9c4cc913e0d4fd64d28f8a
|
7
|
+
data.tar.gz: ef45aa843263e4232d3f09b6b89970cf971e607d06262d2ec6a4094692238792de99e06b37d9ed2e0cd660426098db546f6dbfa0421ab79e4d9e9c1e193e1c3e
|
data/.rubocop_todo.yml
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
|
data/README.md
ADDED
@@ -0,0 +1,217 @@
|
|
1
|
+
# IdempotentEnumerable
|
2
|
+
|
3
|
+
[![Gem Version](https://badge.fury.io/rb/idempotent_enumerable.svg)](http://badge.fury.io/rb/idempotent_enumerable)
|
4
|
+
[![Build Status](https://travis-ci.org/zverok/idempotent_enumerable.svg?branch=master)](https://travis-ci.org/zverok/idempotent_enumerable)
|
5
|
+
[![Coverage Status](https://coveralls.io/repos/zverok/idempotent_enumerable/badge.svg?branch=master)](https://coveralls.io/r/zverok/idempotent_enumerable?branch=master)
|
6
|
+
|
7
|
+
|
8
|
+
`IdempotentEnumerable` is like Ruby core's `Enumerable` but tries to preserve the class of the
|
9
|
+
collection it included in, where reasonable.
|
10
|
+
|
11
|
+
## Features/Showcase
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
require 'set'
|
15
|
+
|
16
|
+
s = Set.new(1..5)
|
17
|
+
# => #<Set: {1, 2, 3, 4, 5}>
|
18
|
+
s.reject(&:odd?)
|
19
|
+
# => [2, 4] -- FFFUUUU
|
20
|
+
|
21
|
+
require 'idempotent_enumerable'
|
22
|
+
Set.include IdempotentEnumerable
|
23
|
+
|
24
|
+
s.reject(&:odd?)
|
25
|
+
# => #<Set: {2, 4}> -- Nice!
|
26
|
+
```
|
27
|
+
|
28
|
+
`IdempotentEnumerable` relies on fact your `each` method returns an instance of `Enumerator` (or
|
29
|
+
other `Enumerable` object) when called without block. Which, honestly, it should do anyways.
|
30
|
+
|
31
|
+
To construct back an instance of original class, `IdempotentEnumerable` relies on the fact
|
32
|
+
`OriginalClass.new(array)` call will work. But, if your class provides another way for construction
|
33
|
+
from array, you can still use the module:
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
h = {a: 1, b: 2, c: 3}
|
37
|
+
# => {:a=>1, :b=>2, :c=>3}
|
38
|
+
h.first(2)
|
39
|
+
# => [[:a, 1], [:b, 2]]
|
40
|
+
|
41
|
+
Hash.include IdempotentEnumerable
|
42
|
+
|
43
|
+
# To make hash from array of pairs, one should use `Hash[array]` notation.
|
44
|
+
Hash.idempotent_enumerable.constructor = :[]
|
45
|
+
|
46
|
+
h.first(2)
|
47
|
+
# => {:a=>1, :b=>2}
|
48
|
+
```
|
49
|
+
|
50
|
+
`IdempotentEnumerable` also supports complicated collections, with `each` accepting additional
|
51
|
+
arguments, out of the box ([daru](https://github.com/SciRuby/daru) used as an example):
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
require 'daru'
|
55
|
+
|
56
|
+
Daru::DataFrame.include IdempotentEnumerable
|
57
|
+
|
58
|
+
df = Daru::DataFrame.new([[1,2,3], [4,5,6], [7,8,9]])
|
59
|
+
# #<Daru::DataFrame(3x3)>
|
60
|
+
# 0 1 2
|
61
|
+
# 0 1 4 7
|
62
|
+
# 1 2 5 8
|
63
|
+
# 2 3 6 9
|
64
|
+
|
65
|
+
# :column argument would be passed to DataFrame#each, so we are selecting columns
|
66
|
+
df.select(:column) { |col| col.sum > 6 }
|
67
|
+
# #<Daru::DataFrame(3x2)>
|
68
|
+
# 0 1
|
69
|
+
# 0 4 7
|
70
|
+
# 1 5 8
|
71
|
+
# 2 6 9
|
72
|
+
```
|
73
|
+
|
74
|
+
## Reasons
|
75
|
+
|
76
|
+
`IdempotentEnumerable` can be used as:
|
77
|
+
|
78
|
+
* soft patch to existing Ruby collections (like `Set` or `Hash`);
|
79
|
+
* custom reimplementations of generic collections (some `FasterArray`);
|
80
|
+
* custom specialized collection, like [Nokogiri::XML::NodeSet](http://www.rubydoc.info/github/sparklemotion/nokogiri/Nokogiri/XML/NodeSet),
|
81
|
+
which quacks like `Array`, but also provides XML/CSS navigation methods. Unfortunately, if you'll
|
82
|
+
do something like `doc.search('a').reject { |a| a.text.include?('Google') }`, you'll receive regular
|
83
|
+
`Array` that haven't any useful `#at`/`#search` methods anymore.
|
84
|
+
|
85
|
+
## Installation and usage
|
86
|
+
|
87
|
+
`gem install idempotent_enumerable` or `gem 'idempotent_enumerable'` in your `Gemfile`.
|
88
|
+
|
89
|
+
Then follow examples in this README.
|
90
|
+
|
91
|
+
## List of methods redefined
|
92
|
+
|
93
|
+
### Methods that return single collection
|
94
|
+
|
95
|
+
* [drop](https://ruby-doc.org/core-2.4.2/Enumerable.html#method-i-drop);
|
96
|
+
* [drop_while](https://ruby-doc.org/core-2.4.2/Enumerable.html#method-i-drop_while);
|
97
|
+
* [first](https://ruby-doc.org/core-2.4.2/Enumerable.html#method-i-first) (when used with argument);
|
98
|
+
* [grep](https://ruby-doc.org/core-2.4.2/Enumerable.html#method-i-grep);
|
99
|
+
* [grep_v](https://ruby-doc.org/core-2.4.2/Enumerable.html#method-i-grep_v) (RUBY_VERSION >= 2.3);
|
100
|
+
* [max](https://ruby-doc.org/core-2.4.2/Enumerable.html#method-i-max) (when used with argument, RUBY_VERSION >= 2.2);
|
101
|
+
* [max_by](https://ruby-doc.org/core-2.4.2/Enumerable.html#method-i-max_by) (when used with argument, RUBY_VERSION >= 2.2);
|
102
|
+
* [min](https://ruby-doc.org/core-2.4.2/Enumerable.html#method-i-min) (when used with argument, RUBY_VERSION >= 2.2);
|
103
|
+
* [min_by](https://ruby-doc.org/core-2.4.2/Enumerable.html#method-i-min_by) (when used with argument, RUBY_VERSION >= 2.2);
|
104
|
+
* [reduce](https://ruby-doc.org/core-2.4.2/Enumerable.html#method-i-reduce);
|
105
|
+
* [reject](https://ruby-doc.org/core-2.4.2/Enumerable.html#method-i-reject);
|
106
|
+
* [select](https://ruby-doc.org/core-2.4.2/Enumerable.html#method-i-select);
|
107
|
+
* [sort](https://ruby-doc.org/core-2.4.2/Enumerable.html#method-i-sort);
|
108
|
+
* [sort_by](https://ruby-doc.org/core-2.4.2/Enumerable.html#method-i-sort_by);
|
109
|
+
* [take](https://ruby-doc.org/core-2.4.2/Enumerable.html#method-i-take);
|
110
|
+
* [take_while](https://ruby-doc.org/core-2.4.2/Enumerable.html#method-i-take_while);
|
111
|
+
* [uniq](https://ruby-doc.org/core-2.4.2/Enumerable.html#method-i-uniq) (RUBY_VERSION >= 2.4).
|
112
|
+
|
113
|
+
### Methods that return (or emit) several collections
|
114
|
+
|
115
|
+
For methods like [partition](https://ruby-doc.org/core-2.4.2/Enumerable.html#method-i-partition) that
|
116
|
+
somehow split an enumerable sequence into several, `IdempotentEnumerable` preserves the type of
|
117
|
+
**internal** sequence. E.g.:
|
118
|
+
|
119
|
+
```ruby
|
120
|
+
Set.include IdempotentEnumerable
|
121
|
+
set = Set.new(1..5)
|
122
|
+
set.partition(&:odd?)
|
123
|
+
# => [#<Set: {1, 3, 5}>, #<Set: {2, 4}>]
|
124
|
+
set.each_slice(3).to_a
|
125
|
+
# => [#<Set: {1, 2, 3}>, #<Set: {4, 5}>]
|
126
|
+
```
|
127
|
+
|
128
|
+
* [chunk](https://ruby-doc.org/core-2.4.2/Enumerable.html#method-i-chunk);
|
129
|
+
* [chunk_while](https://ruby-doc.org/core-2.4.2/Enumerable.html#method-i-chunk_while) (RUBY_VERSION >= 2.3);
|
130
|
+
* [each_cons](https://ruby-doc.org/core-2.4.2/Enumerable.html#method-i-each_cons);
|
131
|
+
* [each_slice](https://ruby-doc.org/core-2.4.2/Enumerable.html#method-i-each_slice);
|
132
|
+
* [group_by](https://ruby-doc.org/core-2.4.2/Enumerable.html#method-i-group_by) (returns hash with
|
133
|
+
keys being group keys and values being original collection type);
|
134
|
+
* [partition](https://ruby-doc.org/core-2.4.2/Enumerable.html#method-i-partition);
|
135
|
+
* [slice_after](https://ruby-doc.org/core-2.4.2/Enumerable.html#method-i-slice_after) (RUBY_VERSION >= 2.2);
|
136
|
+
* [slice_before](https://ruby-doc.org/core-2.4.2/Enumerable.html#method-i-slice_before);
|
137
|
+
* [slice_when](https://ruby-doc.org/core-2.4.2/Enumerable.html#method-i-slice_when) (RUBY_VERSION >= 2.2).
|
138
|
+
|
139
|
+
### Optionally redefined methods
|
140
|
+
|
141
|
+
Generally speaking, `map` and `flat_map` can return collection of anything, probably not coercible
|
142
|
+
to original collection type, so they are **not** redefined by default.
|
143
|
+
|
144
|
+
But they can be redefined with optional `idempotent_enumerable.redefine_map!` call:
|
145
|
+
|
146
|
+
```ruby
|
147
|
+
Set.include IdempotentEnumerable
|
148
|
+
set = Set.new(1..5)
|
149
|
+
set.map(&:to_s)
|
150
|
+
# => ["1", "2", "3", "4", "5"]
|
151
|
+
Set.idempotent_enumerable.redefine_map!
|
152
|
+
set.map(&:to_s)
|
153
|
+
# => #<Set: {"1", "2", "3", "4", "5"}>
|
154
|
+
```
|
155
|
+
|
156
|
+
`redefine_map!` has two options:
|
157
|
+
* `only:` (by default `[:map, :flat_map]`) to specify that you want to redefine only one of those
|
158
|
+
methods;
|
159
|
+
* `all:` to specify which condition all elements of produced collection should satisfy to coerce.
|
160
|
+
|
161
|
+
Example of the latter:
|
162
|
+
|
163
|
+
```ruby
|
164
|
+
Hash.include IdempotentEnumerable
|
165
|
+
Hash.idempotent_enumerable.constructor = :[]
|
166
|
+
# only convert back to hash if `map` have returned array of pairs
|
167
|
+
Hash.idempotent_enumerable.redefine_map! all: ->(e) { e.is_a?(Array) && e.count == 2 }
|
168
|
+
{a: 1, b: 2}.map(&:join)
|
169
|
+
# => ["a1", "b2"] -- no coercion
|
170
|
+
{a: 1, b: 2}.map { |k, v| [k.to_s, v.to_s] }
|
171
|
+
# => {"a"=>"1", "b"=>"2"} -- coercion
|
172
|
+
```
|
173
|
+
|
174
|
+
## Performance penalty
|
175
|
+
|
176
|
+
...is, of course, present, yet not that awful (depends on your standards).
|
177
|
+
|
178
|
+
```ruby
|
179
|
+
require 'benchmark/ips'
|
180
|
+
|
181
|
+
set1 = Set.new((1..100))
|
182
|
+
|
183
|
+
class SetI < Set
|
184
|
+
include IdempotentEnumerable
|
185
|
+
end
|
186
|
+
set2 = SetI.new((1..100))
|
187
|
+
|
188
|
+
Benchmark.ips do |x|
|
189
|
+
x.report('Enumerable') { set1.reject(&:odd?) }
|
190
|
+
x.report('IdempotentEnumerable') { set2.reject(&:odd?) }
|
191
|
+
|
192
|
+
x.compare!
|
193
|
+
end
|
194
|
+
```
|
195
|
+
|
196
|
+
Output:
|
197
|
+
|
198
|
+
```
|
199
|
+
Warming up --------------------------------------
|
200
|
+
Enumerable 10.681k i/100ms
|
201
|
+
IdempotentEnumerable 4.035k i/100ms
|
202
|
+
Calculating -------------------------------------
|
203
|
+
Enumerable 112.134k (± 3.5%) i/s - 566.093k in 5.055148s
|
204
|
+
IdempotentEnumerable 42.197k (± 4.1%) i/s - 213.855k in 5.078339s
|
205
|
+
|
206
|
+
Comparison:
|
207
|
+
Enumerable: 112134.2 i/s
|
208
|
+
IdempotentEnumerable: 42196.6 i/s - 2.66x slower
|
209
|
+
```
|
210
|
+
|
211
|
+
## Author
|
212
|
+
|
213
|
+
[Victor Shepelev](http://zverok.github.io/)
|
214
|
+
|
215
|
+
## License
|
216
|
+
|
217
|
+
MIT
|
data/benchmarks/set.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
require 'idempotent_enumerable'
|
3
|
+
require 'set'
|
4
|
+
require 'benchmark/ips'
|
5
|
+
|
6
|
+
set1 = Set.new((1..100))
|
7
|
+
class SetI < Set
|
8
|
+
include IdempotentEnumerable
|
9
|
+
end
|
10
|
+
set2 = SetI.new((1..100))
|
11
|
+
|
12
|
+
Benchmark.ips do |x|
|
13
|
+
x.report('Enumerable') { set1.reject(&:odd?) }
|
14
|
+
x.report('IdempotentEnumerable') { set2.reject(&:odd?) }
|
15
|
+
|
16
|
+
x.compare!
|
17
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'idempotent_enumerable'
|
3
|
+
s.version = '0.0.1'
|
4
|
+
s.authors = ['Victor Shepelev']
|
5
|
+
s.email = 'zverok.offline@gmail.com'
|
6
|
+
s.homepage = 'https://github.com/zverok/idempotent_enumerable'
|
7
|
+
|
8
|
+
s.summary = 'Like Enumerable but preserves original collection class'
|
9
|
+
s.description = <<-EOF
|
10
|
+
IdempotentEnumerable is like Enumerable, but tries to preserve original collection type when possible.
|
11
|
+
EOF
|
12
|
+
s.licenses = ['MIT']
|
13
|
+
|
14
|
+
s.required_ruby_version = '>= 2.1.0'
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split($RS).reject do |file|
|
17
|
+
file =~ /^(?:
|
18
|
+
spec\/.*
|
19
|
+
|Gemfile
|
20
|
+
|Rakefile
|
21
|
+
|\.rspec
|
22
|
+
|\.gitignore
|
23
|
+
|\.rubocop.yml
|
24
|
+
|\.travis.yml
|
25
|
+
)$/x
|
26
|
+
end
|
27
|
+
s.require_paths = ["lib"]
|
28
|
+
|
29
|
+
s.add_development_dependency 'rubocop', '>= 0.50'
|
30
|
+
s.add_development_dependency 'rspec', '>= 3'
|
31
|
+
s.add_development_dependency 'rubocop-rspec', '>= 1.17.1'
|
32
|
+
s.add_development_dependency 'rspec-its', '~> 1'
|
33
|
+
s.add_development_dependency 'saharspec', '~> 0.0.2'
|
34
|
+
s.add_development_dependency 'rake'
|
35
|
+
s.add_development_dependency 'rubygems-tasks'
|
36
|
+
s.add_development_dependency 'yard'
|
37
|
+
s.add_development_dependency 'benchmark-ips'
|
38
|
+
s.add_development_dependency 'coveralls'
|
39
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
module IdempotentEnumerable
|
2
|
+
include Enumerable
|
3
|
+
|
4
|
+
def self.included(klass)
|
5
|
+
def klass.idempotent_enumerable
|
6
|
+
@idempotent_enumerable ||= Configurator.new(self)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
%i[drop_while reject select sort_by take_while].each do |method|
|
11
|
+
define_method(method) do |*arg, &block|
|
12
|
+
return to_enum(method, *arg) unless block
|
13
|
+
idempotently_construct(each(*arg).send(method, &block))
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
alias find_all select
|
18
|
+
|
19
|
+
def chunk(*arg, &block)
|
20
|
+
# FIXME: should return enumerator
|
21
|
+
return to_enum(:chunk, *arg) unless block
|
22
|
+
each(*arg).chunk(&block).map { |key, group| [key, idempotently_construct(group)] }
|
23
|
+
end
|
24
|
+
|
25
|
+
if RUBY_VERSION >= '2.3.0'
|
26
|
+
def chunk_while(*arg, &block)
|
27
|
+
idempotent_enumerator(each(*arg).chunk_while(&block))
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def drop(num, *arg)
|
32
|
+
idempotently_construct(each(*arg).drop(num))
|
33
|
+
end
|
34
|
+
|
35
|
+
def each_cons(num, *arg)
|
36
|
+
return to_enum(:each_cons, num, *arg) unless block_given?
|
37
|
+
each(*arg).each_cons(num) { |slice| yield(idempotently_construct(slice)) }
|
38
|
+
end
|
39
|
+
|
40
|
+
def each_slice(num, *arg)
|
41
|
+
return to_enum(:each_slice, num, *arg) unless block_given?
|
42
|
+
each(*arg).each_slice(num) { |slice| yield(idempotently_construct(slice)) }
|
43
|
+
end
|
44
|
+
|
45
|
+
def first(*arg)
|
46
|
+
arg, num = idempotent_split_arg(arg)
|
47
|
+
num ? idempotently_construct(each(*arg).first(*num)) : each(*arg).first
|
48
|
+
end
|
49
|
+
|
50
|
+
def grep(*arg, pattern, &block)
|
51
|
+
idempotently_construct(each(*arg).grep(pattern, &block))
|
52
|
+
end
|
53
|
+
|
54
|
+
if RUBY_VERSION >= '2.3.0'
|
55
|
+
def grep_v(pattern, *arg, &block)
|
56
|
+
idempotently_construct(each(*arg).grep_v(pattern, &block))
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def group_by(*arg, &block)
|
61
|
+
each(*arg).group_by(&block).map { |key, val| [key, idempotently_construct(val)] }.to_h
|
62
|
+
end
|
63
|
+
|
64
|
+
def min(*arg)
|
65
|
+
arg, num = idempotent_split_arg(arg)
|
66
|
+
num ? idempotently_construct(each(*arg).min(*num)) : each(*arg).min
|
67
|
+
end
|
68
|
+
|
69
|
+
def min_by(*arg, &block)
|
70
|
+
return to_enum(:min_by, *arg) unless block
|
71
|
+
arg, num = idempotent_split_arg(arg)
|
72
|
+
num ? idempotently_construct(each(*arg).min_by(*num, &block)) : each(*arg).min_by(&block)
|
73
|
+
end
|
74
|
+
|
75
|
+
def max(*arg)
|
76
|
+
arg, num = idempotent_split_arg(arg)
|
77
|
+
num ? idempotently_construct(each(*arg).max(*num)) : each(*arg).max
|
78
|
+
end
|
79
|
+
|
80
|
+
def max_by(*arg, &block)
|
81
|
+
return to_enum(:max_by, *arg) unless block
|
82
|
+
arg, num = idempotent_split_arg(arg)
|
83
|
+
num ? idempotently_construct(each(*arg).max_by(*num, &block)) : each(*arg).max_by(&block)
|
84
|
+
end
|
85
|
+
|
86
|
+
def partition(*arg, &block)
|
87
|
+
return to_enum(:partition, *arg) unless block
|
88
|
+
each(*arg).partition(&block).map(&method(:idempotently_construct))
|
89
|
+
end
|
90
|
+
|
91
|
+
def slice_before(pattern = nil, &block)
|
92
|
+
idempotent_enumerator(pattern ? each.slice_before(pattern) : each.slice_before(&block))
|
93
|
+
end
|
94
|
+
|
95
|
+
if RUBY_VERSION >= '2.2'
|
96
|
+
def slice_after(pattern = nil, &block)
|
97
|
+
idempotent_enumerator(pattern ? each.slice_after(pattern) : each.slice_after(&block))
|
98
|
+
end
|
99
|
+
|
100
|
+
def slice_when(*arg, &block)
|
101
|
+
idempotent_enumerator(each(*arg).slice_when(&block))
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def sort(*arg, &block)
|
106
|
+
idempotently_construct(each(*arg).sort(&block))
|
107
|
+
end
|
108
|
+
|
109
|
+
def take(num, *arg)
|
110
|
+
idempotently_construct(each(*arg).take(num))
|
111
|
+
end
|
112
|
+
|
113
|
+
if RUBY_VERSION >= '2.4.0'
|
114
|
+
def uniq(*arg, &block)
|
115
|
+
idempotently_construct(each(*arg).uniq(&block))
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
private
|
120
|
+
|
121
|
+
def idempotently_construct(array)
|
122
|
+
self.class.send(self.class.idempotent_enumerable.constructor, array)
|
123
|
+
end
|
124
|
+
|
125
|
+
def idempotent_enumerator(enumerator)
|
126
|
+
Enumerator.new { |y| enumerator.each { |chunk| y << idempotently_construct(chunk) } }
|
127
|
+
end
|
128
|
+
|
129
|
+
# For methods like min(), with optional argument (count of min elements), AND complex collections
|
130
|
+
# that have args for their #each method, we need to split args to "args to #each" and
|
131
|
+
# "args to #min".
|
132
|
+
#
|
133
|
+
# Doesn't work if #each has variable number of args, unfortunately.
|
134
|
+
#
|
135
|
+
def idempotent_split_arg(arg)
|
136
|
+
arity = method(:each).arity
|
137
|
+
return [arg] if arity < 0 || arg.count <= arity
|
138
|
+
[arg[0...arity], arg[arity..-1]]
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
require_relative 'idempotent_enumerable/configurator'
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module IdempotentEnumerable
|
2
|
+
class Configurator
|
3
|
+
attr_writer :constructor
|
4
|
+
|
5
|
+
def initialize(host)
|
6
|
+
@host = host
|
7
|
+
end
|
8
|
+
|
9
|
+
def constructor
|
10
|
+
@constructor || :new
|
11
|
+
end
|
12
|
+
|
13
|
+
REDEFINEABLE = %i[map flat_map collect collect_concat].freeze
|
14
|
+
|
15
|
+
def redefine_map!(only: REDEFINEABLE, all: nil)
|
16
|
+
(Array(only) & REDEFINEABLE).each { |method| redefine(method, all) }
|
17
|
+
self
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def redefine(method, all)
|
23
|
+
@host.send(:define_method, method) do |*arg, &block|
|
24
|
+
return to_enum(method) unless block
|
25
|
+
res = each(*arg).send(method, &block)
|
26
|
+
if !all || res.all?(&all)
|
27
|
+
idempotently_construct(res)
|
28
|
+
else
|
29
|
+
res
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
metadata
ADDED
@@ -0,0 +1,190 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: idempotent_enumerable
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Victor Shepelev
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-10-16 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rubocop
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.50'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.50'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '3'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '3'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rubocop-rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 1.17.1
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 1.17.1
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec-its
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: saharspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 0.0.2
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 0.0.2
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rake
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rubygems-tasks
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: yard
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: benchmark-ips
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: coveralls
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
153
|
+
description: " IdempotentEnumerable is like Enumerable, but tries to preserve original
|
154
|
+
collection type when possible.\n"
|
155
|
+
email: zverok.offline@gmail.com
|
156
|
+
executables: []
|
157
|
+
extensions: []
|
158
|
+
extra_rdoc_files: []
|
159
|
+
files:
|
160
|
+
- ".rubocop_todo.yml"
|
161
|
+
- README.md
|
162
|
+
- benchmarks/set.rb
|
163
|
+
- idempotent_enumerable.gemspec
|
164
|
+
- lib/idempotent_enumerable.rb
|
165
|
+
- lib/idempotent_enumerable/configurator.rb
|
166
|
+
homepage: https://github.com/zverok/idempotent_enumerable
|
167
|
+
licenses:
|
168
|
+
- MIT
|
169
|
+
metadata: {}
|
170
|
+
post_install_message:
|
171
|
+
rdoc_options: []
|
172
|
+
require_paths:
|
173
|
+
- lib
|
174
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
175
|
+
requirements:
|
176
|
+
- - ">="
|
177
|
+
- !ruby/object:Gem::Version
|
178
|
+
version: 2.1.0
|
179
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
180
|
+
requirements:
|
181
|
+
- - ">="
|
182
|
+
- !ruby/object:Gem::Version
|
183
|
+
version: '0'
|
184
|
+
requirements: []
|
185
|
+
rubyforge_project:
|
186
|
+
rubygems_version: 2.6.10
|
187
|
+
signing_key:
|
188
|
+
specification_version: 4
|
189
|
+
summary: Like Enumerable but preserves original collection class
|
190
|
+
test_files: []
|