idempotent_enumerable 0.0.1
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 +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
|
+
[](http://badge.fury.io/rb/idempotent_enumerable)
|
4
|
+
[](https://travis-ci.org/zverok/idempotent_enumerable)
|
5
|
+
[](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: []
|