range_extd 1.1.1 → 2.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.
- checksums.yaml +4 -4
- data/ChangeLog +16 -0
- data/Makefile +7 -3
- data/News +4 -0
- data/README.en.rdoc +1146 -270
- data/README.ja.rdoc +1146 -270
- data/Rakefile +11 -5
- data/lib/range_extd/infinity.rb +426 -0
- data/lib/range_extd/load_all.rb +19 -0
- data/lib/range_extd/nil_class.rb +41 -0
- data/lib/range_extd/nowhere.rb +135 -0
- data/lib/range_extd/numeric.rb +160 -0
- data/lib/range_extd/object.rb +53 -0
- data/lib/range_extd/range.rb +401 -0
- data/lib/{range_extd/range_extd.rb → range_extd.rb} +387 -633
- data/range_extd.gemspec +50 -0
- data/test/all_required_test.rb +173 -0
- data/test/test_range_extd.rb +482 -148
- data/test/test_range_extd_nowhere.rb +84 -0
- metadata +29 -16
- data/lib/range_extd/infinity/infinity.rb +0 -600
data/README.en.rdoc
CHANGED
@@ -1,135 +1,470 @@
|
|
1
1
|
|
2
2
|
= RangeExtd - Extended Range class with exclude_begin and open-ends
|
3
3
|
|
4
|
+
== Introduction
|
5
|
+
|
4
6
|
This package contains RangeExtd class, the Extended Range class that features:
|
5
7
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
8
|
+
1. includes exclude_begin? (to exclude the "begin" boundary),
|
9
|
+
2. allows open-ended range to the infinity (*not* undefined ends, i.e., +nil+),
|
10
|
+
3. defines NONE and ALL constants,
|
11
|
+
4. the first self-consistent logical range structure in Ruby,
|
12
|
+
5. complete compatibility within the built-in Range.
|
11
13
|
|
12
14
|
With the introduction of the excluded status of begin, in addition
|
13
15
|
to the end as in built-in Range, and open-ended feature,
|
14
16
|
the logical completeness of the 1-dimensional range is realised.
|
15
17
|
|
16
|
-
|
17
|
-
|
18
|
-
{
|
19
|
-
to
|
18
|
+
A major pro of this library is application to logical operations of multiple ranges,
|
19
|
+
most typically Float and/or Rational.
|
20
|
+
{Rangeary}[https://rubygems.org/gems/rangeary] uses this library to
|
21
|
+
fullest to realise the concept of logical range operations. In doing
|
22
|
+
them, the concept of potentially open-ended Ranges with potential
|
23
|
+
exclusions of begin and end is essential. For example, the negation
|
24
|
+
of Range +(?a..?d)+ is a pair of Ranges +(-"Infinity-Character"...3)+ and
|
25
|
+
+(?d(exclusive).."Infinity-Character")+ and its negation is back to
|
26
|
+
the original +(?a..?d)+. Such operations are possible only with this
|
27
|
+
class +RangeExtd+
|
28
|
+
|
29
|
+
Rangeary: {https://rubygems.org/gems/rangeary}
|
30
|
+
|
31
|
+
The built-in Range class is very useful, and has given Ruby users
|
32
|
+
a power to make easy coding. Yet, the lack of definition of
|
33
|
+
exclusive begin boundary is a nuisance in some cases.
|
34
|
+
|
35
|
+
Having said that, there is a definite and understandable reason;
|
36
|
+
Range in Ruby is not limited at all to Numeric (or strictly speaking,
|
37
|
+
Real numbers or their representatives). Range with any object that has a method
|
38
|
+
of <tt>succ()</tt> is found to be useful, whereas there is no reverse method for
|
39
|
+
<tt>succ()</tt> in general.
|
40
|
+
In that sense Range is inherently not symmetric. In addition,
|
41
|
+
some regular Range objects are continuous (like Float), while others are discrete
|
42
|
+
(like Integer or String). That may add some confusion to the strict definition.
|
43
|
+
|
44
|
+
To add the feature of the exclusive-begin boundary is in that sense
|
45
|
+
not 100 per cent trivial. The definition I adopt for the behaviour of
|
46
|
+
{RangeExtd} is probably not the only solution. Personally, I am content
|
47
|
+
with it, and I think it achieves the good logical completeness within the frame.
|
48
|
+
|
49
|
+
I hope you find this package to be useful.
|
50
|
+
|
51
|
+
=== Validity of a Range
|
52
|
+
|
53
|
+
Ruby built-in Range is very permissive for the elements (members). For example,
|
54
|
+
+(true...true)+ is a valid Range whatever it means, although its use
|
55
|
+
is highly limited because you cannot iterate over it, that is,
|
56
|
+
methods like +each+ with an associated iterator and +to_a+ would raise an Exception (+TypeError+).
|
20
57
|
|
21
|
-
|
58
|
+
With this library, the validity of a Range is strictly defined.
|
59
|
+
This library adds a few methods, most notably
|
60
|
+
{Range#valid?}, {Range#null?} and {Range#empty?}
|
61
|
+
to Range class, which would take immediate effect in any of its sub-classes.
|
62
|
+
|
63
|
+
As an example, <tt>(3...3).valid?</tt> returns false, because the element 3 is
|
22
64
|
inclusive for the begin boundary, yet exclusive for the end boundary,
|
23
65
|
which are contradictory to each other. With this RangeExtd class,
|
24
|
-
|
66
|
+
the following two are regarded as valid ranges,
|
25
67
|
|
26
|
-
* RangeExtd.new(3, 3, true, true) # => an empty range
|
27
|
-
* RangeExtd.new(3, 3, false, false) # => a single-point range (3..3)
|
68
|
+
* RangeExtd.new(3, 3, true, true) # => an empty range
|
69
|
+
* RangeExtd.new(3, 3, false, false) # => a single-point range (3..3)
|
28
70
|
|
29
|
-
However, as long as
|
71
|
+
However, as long as the use is closed within the built-in Range, nothing has changed,
|
30
72
|
so it is completely compatible with the standard Ruby.
|
31
73
|
|
32
|
-
|
74
|
+
=== Open-ended ranges to infinity
|
75
|
+
|
76
|
+
Ruby 2.6 and 2.7 have introduced endless and beginless Ranges, respectively.
|
77
|
+
The difference between borderless Ranges and open-ended ranges to infinity
|
78
|
+
introduced in this library is subtle and perhaps more
|
79
|
+
conceptual or philosophical than meaningful in the practical term (see Section "Background"
|
80
|
+
for detail). Fear not, though. In practical applications, they are
|
81
|
+
compatible and you do not have to be aware of them. In short, you can
|
82
|
+
mostly use built-in borderless Ranges unless you want otherwise.
|
83
|
+
|
84
|
+
To express open-ended ranges defined in this library, you use either of
|
33
85
|
the two (negative and positive, or former and later) constants
|
34
86
|
defined in the class {RangeExtd::Infinity}
|
35
87
|
|
36
|
-
* RangeExtd::Infinity::NEGATIVE
|
37
|
-
* RangeExtd::Infinity::POSITIVE
|
88
|
+
* {RangeExtd::Infinity::NEGATIVE}
|
89
|
+
* {RangeExtd::Infinity::POSITIVE}
|
38
90
|
|
39
|
-
They are basically the
|
91
|
+
They are basically the objects that *generalize* <tt>Float::INFINITY</tt> to
|
40
92
|
any Comparable object. For example,
|
41
93
|
|
42
94
|
("a"..RangeExtd::Infinity::POSITIVE).each
|
43
95
|
|
44
96
|
gives an infinite iterator with <tt>String#succ</tt>, starting from "a"
|
45
97
|
(therefore, make sure to code so it breaks the iterator at one stage!).
|
98
|
+
In this case it work in an identical way to (a Ruby-2.6 form of)
|
46
99
|
|
100
|
+
("a"..).each
|
47
101
|
|
48
|
-
|
49
|
-
a power to make easy coding. Yet, the lack of definition of
|
50
|
-
exclusive begin boundary is a nuisance in some cases.
|
102
|
+
=== News: Library locations and support of beginless Ranges
|
51
103
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
<tt>succ()</tt> in general.
|
57
|
-
In that sense Range is inherently not symmetric. In addition
|
58
|
-
some regular Range objects are continuous (like Float), while others are discrete
|
59
|
-
(like Integer or String). That may add some confusion to the strict definition.
|
104
|
+
**IMPORTANT**: The paths for the libraries are moved up by one
|
105
|
+
directory in {RangeExtd} Ver.2 from Ver.1 in order that their
|
106
|
+
locations follow the Ruby Gems convention. In short, the standard way
|
107
|
+
to require is +require "range_extd"+, the path of which used to be "range_extd/range_extd"
|
60
108
|
|
61
|
-
|
62
|
-
not 100 per cent trivial. The definition I adopt for the behaviour of
|
63
|
-
RangeExtd is probably not the only solution. Personally, I am content
|
64
|
-
with it, and I think it achieves the good logical completeness within the frame.
|
109
|
+
Version of {RangeExtd} is now 2.0.
|
65
110
|
|
66
|
-
|
111
|
+
Here is a brief summary of other significant changes in recent
|
112
|
+
upgrades. For more extensive information, see History section in this doc.
|
67
113
|
|
68
|
-
|
114
|
+
==== News: Beginless Range supported
|
69
115
|
|
70
|
-
|
71
|
-
introduced in Ruby 2.6. It is released as Version 1.* finally!
|
116
|
+
Ruby 2.7 supports {Beginless range}[https://rubyreferences.github.io/rubychanges/2.7.html#beginless-range].
|
72
117
|
|
73
|
-
|
118
|
+
+RangeExtd+ also supports it now.
|
119
|
+
With this, there are important changes in specification.
|
74
120
|
|
75
|
-
|
76
|
-
(
|
77
|
-
{
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
121
|
+
First, {RangeExtd::NONE} is now in practice
|
122
|
+
+RangeExtd((RangeExtd::Nowhere::NOWHERE...RangeExtd::Nowhere::NOWHERE), true)+,
|
123
|
+
that is, both ends are {RangeExtd::Nowhere::NOWHERE} and both ends ({RangeExtd#begin} and
|
124
|
+
{RangeExtd#end}) are exclusive.
|
125
|
+
In the previous versions, both ends of {RangeExtd::NONE} used to have
|
126
|
+
+nil+. Range +(nil..nil)+ did not use to be allowed in Ruby-2.6 or earlier,
|
127
|
+
and hence it was unique to {RangeExtd::NONE}, conveniently.
|
128
|
+
However, Ruby-2.7 and later now accepts nil to nil Range. Then,
|
129
|
+
+(nil..nil)+ is perfectly valid and it has in practice
|
130
|
+
no difference from +RangeExtd((nil...nil), true)+, which used to be
|
131
|
+
the form of {RangeExtd::NONE}, despite the fact
|
132
|
+
the former is a completely different object that is close to {RangeExtd::ALL} except
|
133
|
+
for the exclusion flags. That is why this change is required.
|
134
|
+
|
135
|
+
Having said that, this change is majorly conceptual and there is almost no
|
136
|
+
change from users' point of view. The begin and end value for {RangeExtd::NONE},
|
137
|
+
{RangeExtd::Nowhere::NOWHERE}, behaves almost exactly like {NilClass}
|
138
|
+
(see Description below for detail). Given that the value was literally +nil+ in {RangeExtd}
|
139
|
+
Ver.1 and earlier, the use of {RangeExtd::Nowhere::NOWHERE} in
|
140
|
+
{RangeExtd::NONE} in Ver.2 should not demand any changes in the existing code
|
141
|
+
that use {RangeExtd::NONE}. The recommended way to check whether an
|
142
|
+
object is {RangeExtd::NONE} or not with use of {RangeExtd#is_none?} (as
|
143
|
+
it always has been) (do not be confused with +Enumerable#none?+). Or, in most practical cases, {Range#null?} is
|
144
|
+
likely to be what a user wants (n.b., a potential caveat is
|
145
|
+
+(true..true).null?+ returns +false+; see subsection "RangeExtd Class"
|
146
|
+
in "Description" in this doc for detail).
|
84
147
|
|
85
|
-
|
148
|
+
Second, +RangeExtd.valid?(nil..)+ now returns +true+, which used to be +false+, and it
|
149
|
+
is equal to {RangeExtd::ALL}.
|
150
|
+
|
151
|
+
For example, +"abc"[nil..]+ is a perfectly valid Ruby
|
152
|
+
expression in Ruby-2.7 and later, though it used to be invalid or even
|
153
|
+
SyntaxError in earlier versions of Ruby. Hence it would be strange if +RangeExtd+ considered it invalid.
|
154
|
+
|
155
|
+
Note that +RangeExtd.valid?(true..)+ still returns +false+.
|
156
|
+
|
157
|
+
Other major changes in specification include:
|
158
|
+
|
159
|
+
* +RangeExtd::Infinity#succ+ is now undefined, in line with Float.
|
160
|
+
* Extensions for +Object+ and +Numeric+ are now not in default and optional.
|
161
|
+
* +RangeExtd#eql?+ follows the Ruby default behaviour (comparison based on [#hash]), eliminating special cases in comparison with {RangeExtd::NONE}.
|
162
|
+
* Fixed a bug where +RangeExtd#min_by+ (and +max_by+ and +minmax_by+) did not work correctly.
|
163
|
+
|
164
|
+
==== News: Endless Range supported
|
165
|
+
|
166
|
+
Now, as of 2019 October, this fully supports {Endless Range}[https://rubyreferences.github.io/rubychanges/2.6.html#endless-range-1]
|
167
|
+
introduced in Ruby 2.6. It is released as Version 1.* finally!
|
86
168
|
|
87
169
|
==== NOTE: Relationship with Rangesmaller
|
88
170
|
|
89
171
|
This package RangeExtd supersedes the obsolete {Rangesmaller}[https://rubygems.org/gems/rangesmaller] package and class,
|
90
|
-
with the added open-ended feature
|
172
|
+
with the added open-ended feature and different interfaces in
|
91
173
|
creating a new instance.
|
92
|
-
https://rubygems.org/gems/rangesmaller
|
174
|
+
{https://rubygems.org/gems/rangesmaller}
|
175
|
+
|
176
|
+
|
177
|
+
== Background
|
178
|
+
|
179
|
+
=== Endless and Beginless Ranges
|
180
|
+
|
181
|
+
{Endless Range}[https://rubyreferences.github.io/rubychanges/2.6.html#endless-range-1] and
|
182
|
+
{Beginless Range}[https://rubyreferences.github.io/rubychanges/2.7.html#beginless-range]
|
183
|
+
were introduced in Ruby 2.6 and 2.7, respectively, released in Decembers 2018
|
184
|
+
and 2019.
|
185
|
+
|
186
|
+
Thanks to them, the Ruby built-in +Range+ has achieved part of the
|
187
|
+
functionalities and concept of what +RangeExtd+ provides.
|
188
|
+
However, there are still some differences, some of which are clear but
|
189
|
+
some are more obscured.
|
190
|
+
|
191
|
+
The most clear advantage of this library is the support for +exclude_begin?+ (to exclude the "begin" boundary).
|
192
|
+
|
193
|
+
As for the boundary-less Ranges, this library offers, in a word,
|
194
|
+
the abstraction of open-ended range (to the infinity).
|
195
|
+
A major conceptual difference is that Ruby's built-in beginless/endless +Range+
|
196
|
+
provides *undefined* boundaries, whereas +RangeExtd+ does *infinite* boundaries.
|
197
|
+
|
198
|
+
The difference is subtle but significant.
|
199
|
+
Let us take a look at some examples of the standard Ruby, particularly
|
200
|
+
Numeric, because Ruby Ranges for Numeric practically provide both
|
201
|
+
functionalities. These examples highlight the difference:
|
202
|
+
|
203
|
+
"abcdef"[..2] # => "abc"
|
204
|
+
"abcdef"[..2.0] # => "abc"
|
205
|
+
"abcdef"[(-Float::INFINITY)..2] # raise (RangeError)
|
206
|
+
"abcdef"[(-1)..2] # => ""
|
207
|
+
"abcdef"[(-6)..2] # => "abc"
|
208
|
+
"abcdef"[(-7)..2] # => nil
|
209
|
+
(-Float::INFINITY..5).first(1) # raise: can't iterate from Float (TypeError)
|
210
|
+
(-Float::INFINITY..5).first # => -Infinity
|
211
|
+
(-Float::INFINITY..5).begin # => -Infinity
|
212
|
+
(..5).first # raise: cannot get the first element of beginless range (RangeError)
|
213
|
+
(..5).begin # => nil
|
214
|
+
|
215
|
+
The first (and second) examples use a beginless Range, where the begin
|
216
|
+
value is *undefined*. Then, String class *interprets* the "begin value"
|
217
|
+
as 0. By contrast, the third example raises an Exception; it is
|
218
|
+
understandable because the begin value is defined but infinitely negative.
|
219
|
+
Indeed, a negative value for the index for a String has a special
|
220
|
+
meaning as demonstrated in the 4th to 6th examples.
|
221
|
+
|
222
|
+
The last five examples are interesting. +Range#begin+ simply returns
|
223
|
+
the begin-boundary value always. +Range#first+ returns the first
|
224
|
+
"element" when no argument is given;
|
225
|
+
+(Float::INFINITY..5)+ has the first element and so it is returned. A beginless
|
226
|
+
{Range} is a different story; it does not have a defined first element
|
227
|
+
and hence +Range#first+ raises +RangeError+. By contrast,
|
228
|
+
When an argument +n+ is given to +Range#first+, an Array of
|
229
|
+
+n+ elements should be returned. Since counting from any Float is undefined, the Range
|
230
|
+
from the negative infinity raises +TypeError+. It makes sense?
|
231
|
+
|
232
|
+
By the way, I note that +(5..8.6).last(2)+ is valid and returns +[7, 8]+ and +(2.2..8.6).size+
|
233
|
+
is also valid, to add confusion.
|
234
|
+
|
235
|
+
Another point is, although the infinity has a clear mathematical
|
236
|
+
definition, not all Ranges accept it. Let us consider your own
|
237
|
+
subset class where each instance has only a single lower-case alphabet
|
238
|
+
character and where a Range of instances can be defined in the same
|
239
|
+
way as the String class. Then, the minimum begin value and maximum end
|
240
|
+
value the Range can have are "+a+" and "+z+", respectively. In this
|
241
|
+
case, what would the positive (or negative) infinities mean? Perhaps,
|
242
|
+
in the strictest term, the infinities for the Range should be invalid?
|
243
|
+
Or, the positive infinity should be interpreted as the index for "+z+"
|
244
|
+
in this case, or else?
|
245
|
+
|
246
|
+
Conceptually, the inclusive interpretation is more convenient.
|
247
|
+
Indeed, {Rangeary}[https://rubygems.org/gems/rangeary] uses
|
248
|
+
+RangeExtd+ in such a way, that is,
|
249
|
+
the negation of the Range is actively used for logical operations of
|
250
|
+
Arrays of Ranges. However, you cannot force each application to
|
251
|
+
accept the definition.
|
252
|
+
|
253
|
+
In summary, *undefined* boundaries are undefined by definition and
|
254
|
+
their interpretations are up to each application, whereas positive or
|
255
|
+
negative infinities may have clear definitions although more
|
256
|
+
flexible interpretations may be preferred in practical applications.
|
257
|
+
|
258
|
+
Given all these, {RangeExtd::Infinity::NEGATIVE} and {RangeExtd::Infinity::POSITIVE}
|
259
|
+
in this library behaves like +nil+, though it is possible for users to
|
260
|
+
distinguish them.
|
261
|
+
|
262
|
+
|
263
|
+
=== Behaviours of endless and beginless Ranges
|
264
|
+
|
265
|
+
The behaviours of the built-in Endless/Beginless Range can be a little confusing.
|
266
|
+
In addition, it seems there are bugs for +Range#size+
|
267
|
+
({Bug #18983}[https://bugs.ruby-lang.org/issues/18983] and
|
268
|
+
{Bug #18993}[https://bugs.ruby-lang.org/issues/18993])
|
269
|
+
or at least points that contradict the specification described in the
|
270
|
+
official doc, which adds confusion.
|
271
|
+
|
272
|
+
In the Ruby implementation, the begin and end values of a beginless and endless Ranges
|
273
|
+
are both interpreted as +nil+. In Ruby, +nil == nil+ is true and
|
274
|
+
therefore
|
275
|
+
|
276
|
+
(?a..).end == (5..).end
|
277
|
+
|
278
|
+
is also +true+, whereas
|
279
|
+
|
280
|
+
(?a..).end == (5..Float::INFINITY).end
|
281
|
+
|
282
|
+
is +false+. Below is a more extended set of examples.
|
283
|
+
|
284
|
+
(-Float::INFINITY..Float::INFINITY).size # => Infinity
|
285
|
+
( Float::INFINITY..Float::INFINITY).size # raises FloatDomainError
|
286
|
+
num1 = (5..Float::INFINITY)
|
287
|
+
num2 = (5..)
|
288
|
+
num1.end != num2.end # => true
|
289
|
+
num1.size # => Infinity
|
290
|
+
num2.size # => Infinity
|
291
|
+
|
292
|
+
str1 = (?a..)
|
293
|
+
str1.end != num1.end # => true
|
294
|
+
str1.end == num2.end # => true (because both are nil)
|
295
|
+
str1.size # => nil (because Range#size is defined for Numeric only)
|
296
|
+
(..?z).size # => Infinity (contradicting the specification?)
|
297
|
+
|
298
|
+
(..3).to_s => "..3"
|
299
|
+
(3..).to_s => "3.."
|
300
|
+
(3..nil).to_s => "3.."
|
301
|
+
(nil..3).to_s => "..3"
|
93
302
|
|
303
|
+
(nil..) == (..nil) # => true
|
304
|
+
(nil..) != (...nil) # => true (because exclude_end? differ)
|
305
|
+
"abcdef"[..nil] # => "abcdef" (i.e., it is interpreted as (0..IntegerInfinity)
|
306
|
+
# (n.b., nil.to_i==0; Integer(nil) #=> TypeError))
|
307
|
+
"abcdef"[..?a] # raise: no implicit conversion of String into Integer (TypeError)
|
308
|
+
"abcdef"[0..100] # => "abcdef"
|
309
|
+
"abcdef"[-100..100] # => nil
|
310
|
+
|
311
|
+
(..nil).size # => Float::INFINITY
|
312
|
+
|
313
|
+
(..nil).begin # => nil
|
314
|
+
(..nil).first # raise: cannot get the first element of beginless range (RangeError)
|
315
|
+
(..nil).last # raise: cannot get the last element of endless range (RangeError)
|
316
|
+
(..nil).end # => nil
|
317
|
+
|
318
|
+
(..nil).cover? 5 # => true
|
319
|
+
(..nil).cover? ?a # => true
|
320
|
+
(..nil).cover? [?a] # => true
|
321
|
+
(..nil).cover? nil # => true
|
322
|
+
|
323
|
+
For Integer,
|
324
|
+
|
325
|
+
num1 = (5..Float::INFINITY)
|
326
|
+
num2 = (5..)
|
327
|
+
num1.end != num2.end # => true (because (Float::INFINITY != nil))
|
328
|
+
num1.size # => Float::INFINITY
|
329
|
+
num2.size # => Float::INFINITY
|
330
|
+
|
331
|
+
(3...) == (3...nil) # => true
|
332
|
+
(3..) != (3...nil) # => true (because exclude_end? differ)
|
333
|
+
|
334
|
+
(3..).size # => Float::INFINITY
|
335
|
+
(..3).begin # => nil
|
336
|
+
(..3).first # raise: cannot get the first element of beginless range (RangeError)
|
337
|
+
(3..).last # raise: cannot get the last element of endless range (RangeError)
|
338
|
+
(3..).end # => nil
|
339
|
+
(..3).each{} # raise: `each': can't iterate from NilClass (TypeError)
|
340
|
+
(..3).to_a # raise: `each': can't iterate from NilClass (TypeError)
|
341
|
+
(3..).to_a # raise: `to_a': cannot convert endless range to an array (RangeError)
|
342
|
+
(3..Float::INFINITY).to_a # Infinite loop!
|
343
|
+
|
344
|
+
(-Float::INFINITY..4).first # => -Float::INFINITY
|
345
|
+
(4..Float::INFINITY).last # => Float::INFINITY
|
346
|
+
(-Float::INFINITY..4).first(2) # raise: can't iterate from Float (TypeError)
|
347
|
+
(4..Float::INFINITY).last(2) # Infinite loop!
|
348
|
+
|
349
|
+
For String (or any user-defined class?),
|
350
|
+
|
351
|
+
(?a..).end == (5..).end # => true (because both are nil)
|
352
|
+
(?a..).end != (5..Float::INFINITY).end # => true
|
353
|
+
(..?a).begin == (..5).begin # => true (because both are nil)
|
354
|
+
(..?a).begin != ((-Float::INFINITY)..5).begin # => true
|
355
|
+
(..?a).size # => Float::INFINITY
|
356
|
+
(?a..).size # => nil
|
357
|
+
|
358
|
+
(..?a).begin # => nil
|
359
|
+
(..?a).first # raise: cannot get the first element of beginless range (RangeError)
|
360
|
+
(?a..).last # raise: cannot get the last element of endless range (RangeError)
|
361
|
+
(?a..).end # => nil
|
362
|
+
(..?a).each{} # raise: `each': can't iterate from NilClass (TypeError)
|
363
|
+
(..?a).to_a # raise: `each': can't iterate from NilClass (TypeError)
|
364
|
+
(?a..).to_a # raise: `to_a': cannot convert endless range to an array (RangeError)
|
365
|
+
(?a..Float::INFINITY).to_a # raise: bad value for range (ArgumentError) # b/c it is not String!
|
366
|
+
|
367
|
+
=== Comment on Range#size
|
368
|
+
|
369
|
+
The behaviour of +Range#size+ is highly confusing.
|
370
|
+
According to {Official doc}[https://ruby-doc.org/core-3.1.2/Range.html#method-i-size],
|
371
|
+
|
372
|
+
Returns the count of elements in self if both begin and end values are numeric;
|
373
|
+
otherwise, returns nil
|
374
|
+
|
375
|
+
But actually Ruby does not necessarily behaves in this way (see examples above).
|
376
|
+
In addition, the meaning of "elements" in the doc for general Numeric is
|
377
|
+
ambiguous. The following demonstrates it (reported as {Bug #18993}[https://bugs.ruby-lang.org/issues/18993]):
|
378
|
+
|
379
|
+
(5.quo(3)...5).size # => 3
|
380
|
+
(5.quo(3).to_f...5).size # => 4
|
381
|
+
(5.quo(3)..5).size # => 4
|
382
|
+
(5.quo(3).to_f..5).size # => 4
|
383
|
+
|
384
|
+
=== Comment on Range#count
|
385
|
+
|
386
|
+
The behaviour of +Range#count+ is mostly understandable, but those of
|
387
|
+
borderless or with infinities are not trivial.
|
388
|
+
|
389
|
+
(5..).count # => Float::INFINITY
|
390
|
+
(..5).count # => Float::INFINITY
|
391
|
+
(..nil).count # => Float::INFINITY
|
392
|
+
(-Float::INFINITY..nil) # => Float::INFINITY
|
393
|
+
(-Float::INFINITY..Float::INFINITY).count # raises (TypeError) "can't iterate from Float"
|
394
|
+
(..5).count(4) # raises (TypeError)
|
395
|
+
(..5).count{|i| i<3} # raises (TypeError)
|
396
|
+
(1..).count(4) # infinite loop!
|
397
|
+
(1..).count{|i| i<3} # infinite loop!
|
398
|
+
|
399
|
+
Basically, in some limited cases, the method returns Infinity, which
|
400
|
+
are special cases.
|
401
|
+
|
402
|
+
Given these, +RangeExtd::ALL.count+ returns +Float::INFINITY+ as
|
403
|
+
another special case.
|
94
404
|
|
95
405
|
== Install
|
96
406
|
|
97
407
|
gem install range_extd
|
98
408
|
|
99
|
-
|
409
|
+
installs several files including
|
100
410
|
|
101
|
-
range_extd
|
102
|
-
range_extd/infinity
|
411
|
+
range_extd.rb
|
412
|
+
range_extd/infinity.rb
|
103
413
|
|
104
|
-
|
414
|
+
in one of your <tt>$LOAD_PATH</tt>
|
105
415
|
|
106
|
-
Alternatively get it from
|
416
|
+
Alternatively get it from {http://rubygems.org/gems/range_extd}
|
107
417
|
|
108
|
-
|
418
|
+
Or, if you manually install it, place all the Ruby files under +lib/+
|
419
|
+
directory under one of your +RUBYLIB+ directory paths, preserving the
|
420
|
+
directory structure. Note that +range_extd.rb+ must be directly under
|
421
|
+
the library directory.
|
109
422
|
|
110
423
|
Then all you need to do is
|
111
424
|
|
112
|
-
require
|
425
|
+
require "range_extd/load_all"
|
426
|
+
|
427
|
+
Or, if you only want minimum functions of this library, you can instead
|
113
428
|
|
114
|
-
|
429
|
+
require "range_extd"
|
115
430
|
|
116
|
-
|
431
|
+
Basically, "+range_extd/load_all.rb+" is a wrapper Ruby file, which requires the following files:
|
117
432
|
|
118
|
-
|
433
|
+
require "range_extd"
|
434
|
+
require "range_extd/numeric"
|
435
|
+
require "range_extd/object"
|
436
|
+
require "range_extd/infinity"
|
437
|
+
require "range_extd/nowhere"
|
438
|
+
require "range_extd/range"
|
439
|
+
require "range_extd/nil_class"
|
119
440
|
|
120
|
-
|
441
|
+
Among these, the first three files are independent, whereas the last
|
442
|
+
four files are inseparable from the first one and are automatically
|
443
|
+
require-d from the first one.
|
121
444
|
|
122
|
-
|
445
|
+
The second and third files are a set of utility libraries; if your code
|
446
|
+
requires them, some methods are added or some existing methods are slightly altered in the
|
447
|
+
existing Ruby built-in classes: +Object+ and +Numeric+
|
448
|
+
(including +Float+ and +Integer+).
|
449
|
+
How they are modified are backward-compatible; simply a few
|
450
|
+
new features are added. Their use is highly recommended; otherwise,
|
451
|
+
the use of this library would be very limited. For example,
|
452
|
+
the comparison operator +<=>+ would not be commutative without them,
|
453
|
+
which might result in some nasty surprises. For detail, refer to the
|
454
|
+
individual references.
|
123
455
|
|
124
456
|
Have fun!
|
125
457
|
|
126
458
|
|
127
459
|
== Simple Examples
|
128
460
|
|
461
|
+
In the following, I assume all the files are required.
|
462
|
+
|
129
463
|
=== How to create a RangeExtd instance
|
130
464
|
|
131
465
|
Here are some simple examples.
|
132
466
|
|
467
|
+
require "range_extd/load_all"
|
133
468
|
r = RangeExtd(?a...?d, true) # => a<...d
|
134
469
|
r.exclude_begin? # => true
|
135
470
|
r.to_a # => ["b", "c"]
|
@@ -141,20 +476,21 @@ Here are some simple examples.
|
|
141
476
|
(RangeExtd::Infinity::NEGATIVE..RangeExtd::Infinity::POSITIVE) \
|
142
477
|
== RangeExtd::ALL # => true
|
143
478
|
|
144
|
-
|
479
|
+
+RangeExtd+ provides three forms for initialization (hint: the first
|
480
|
+
form is probably the handiest with least typing and is the easiest to remember):
|
145
481
|
|
146
|
-
RangeExtd(range, [exclude_begin=false, [exclude_end=false]]
|
147
|
-
RangeExtd(obj_begin, obj_end, [exclude_begin=false, [exclude_end=false]]
|
148
|
-
RangeExtd(obj_begin, string_form, obj_end, [exclude_begin=false, [exclude_end=false]]
|
482
|
+
RangeExtd(range, [exclude_begin=false, [exclude_end=false]])
|
483
|
+
RangeExtd(obj_begin, obj_end, [exclude_begin=false, [exclude_end=false]])
|
484
|
+
RangeExtd(obj_begin, string_form, obj_end, [exclude_begin=false, [exclude_end=false]])
|
149
485
|
|
150
|
-
The two parameters in the brackets specify the respective
|
151
|
-
or included if false (Default). If they contradict
|
152
|
-
parameter of the range (Range or RangeExtd),
|
153
|
-
|
154
|
-
and <tt>:exclude_end</tt>, which have the highest priority
|
155
|
-
The <tt>string_form</tt> in the third form is like ".." and "<..."
|
156
|
-
|
157
|
-
the most visibly
|
486
|
+
The two parameters in the square-brackets specify the respective boundaries to be excluded if true,
|
487
|
+
or included if false (Default). If they contradict the first
|
488
|
+
parameter of the range (+Range+ or +RangeExtd+), the latter two parameters have priorities.
|
489
|
+
Alternatively, you can specify the same parameters as the options <tt>:exclude_begin</tt>
|
490
|
+
and <tt>:exclude_end</tt>, which have the highest priority if specified.
|
491
|
+
The <tt>string_form</tt> in the third form is like ".." (including both ends) and "<..." (excluding both ends),
|
492
|
+
set by users (see {RangeExtd.middle_strings=}() for detail), and is arguably
|
493
|
+
the most visibly-recognisable way to specify any range with <tt>exclude_begin=true</tt>.
|
158
494
|
|
159
495
|
<tt>RangeExtd.new()</tt> is the same thing.
|
160
496
|
For more detail and examples, see {RangeExtd.initialize}.
|
@@ -162,102 +498,106 @@ For more detail and examples, see {RangeExtd.initialize}.
|
|
162
498
|
|
163
499
|
=== Slightly more advanced uses
|
164
500
|
|
165
|
-
(
|
501
|
+
RangeExtd((0..), true).each do |i|
|
166
502
|
print i
|
167
503
|
break if i >= 9
|
168
|
-
end # => self
|
169
|
-
|
504
|
+
end # => self; "123456789" => STDOUT
|
505
|
+
# *NOT* "012..."
|
506
|
+
(nil..nil).valid? # => true
|
170
507
|
(1...1).valid? # => false
|
171
508
|
(1...1).null? # => true
|
172
509
|
RangeExtd.valid?(1...1) # => false
|
173
510
|
RangeExtd(1, 1, true, true).valid? # => true
|
174
511
|
RangeExtd(1, 1, true, true).empty? # => true
|
175
512
|
RangeExtd(?a, ?b, true, true).to_a? # => []
|
176
|
-
RangeExtd(?a, ?b, true, true).
|
513
|
+
RangeExtd(?a, ?b, true, true).null? # => true (empty? is same in this case)
|
177
514
|
RangeExtd(?a, ?e, true, true).to_a? # => ["b", "c", "d"]
|
178
|
-
RangeExtd(?a, ?e, true, true).
|
515
|
+
RangeExtd(?a, ?e, true, true).null? # => false
|
179
516
|
RangeExtd::NONE.is_none? # => true
|
517
|
+
RangeExtd(1...1, true) == RangeExtd::NONE # => true
|
180
518
|
RangeExtd::ALL.is_all? # => true
|
519
|
+
(nil..nil).is_all? # => false
|
520
|
+
(-Float::INFINITY..Float::INFINITY).is_all? # => false
|
521
|
+
(nil..nil).equiv_all? # => true
|
522
|
+
(-Float::INFINITY..Float::INFINITY).equiv_all? # => true
|
181
523
|
(3...7).equiv?(3..6) # => true
|
524
|
+
(nil..nil).equiv?(RangeExtd::ALL) # => true
|
182
525
|
|
183
|
-
All the methods that are in the built-in Range can be used
|
526
|
+
All the methods that are in the built-in Range can be used in
|
527
|
+
{RangeExtd}, which is a child class of {Range}.
|
184
528
|
|
185
529
|
|
186
530
|
== Description
|
187
531
|
|
188
|
-
Once the file +range_extd
|
532
|
+
Once the file +range_extd.rb+ is required, the three classes are defined:
|
189
533
|
|
190
534
|
* RangeExtd
|
191
535
|
* RangeExtd::Infinity
|
536
|
+
* RangeExtd::Nowhere
|
192
537
|
|
193
|
-
Also, several methods are added or altered in Range class.
|
194
|
-
All the changes made in
|
538
|
+
Also, several methods are added or altered in {Range} class and {NilClass}.
|
539
|
+
All the changes made in them are backward-compatible with the original.
|
540
|
+
|
541
|
+
Note that whereas the changes in {Range} could be in principle separable
|
542
|
+
from {RangeExtd}, if no one would likely want to use them separately, those
|
543
|
+
in {NilClass} are unavoidable. Without them, {RangeExtd::NONE} could
|
544
|
+
not be defined, for +ArgumentError+ (bad value for range) would be
|
545
|
+
raised in the initialization due to the way Ruby built-in Range is
|
546
|
+
implemented.
|
547
|
+
|
548
|
+
See {discussion at Stackoverflow}[https://stackoverflow.com/a/14449380/3577922].
|
195
549
|
|
196
550
|
=== RangeExtd::Infinity Class
|
197
551
|
|
198
|
-
Class {RangeExtd::Infinity} has
|
552
|
+
Class {RangeExtd::Infinity} has only two constant instances.
|
199
553
|
|
200
554
|
* RangeExtd::Infinity::NEGATIVE
|
201
555
|
* RangeExtd::Infinity::POSITIVE
|
202
556
|
|
203
|
-
They are the objects that
|
557
|
+
They are the objects that generalize the concept of
|
204
558
|
<tt>Float::INFINITY</tt>
|
205
|
-
to any Comparable objects. The methods <tt><=></tt>
|
559
|
+
to any Comparable objects. The methods <tt><=></tt> are defined.
|
206
560
|
|
207
|
-
You can use them the same as other objects, such as,
|
561
|
+
You can use them in the same way as other objects, such as,
|
208
562
|
|
209
|
-
(
|
563
|
+
(RangeExtd::Infinity::NEGATIVE.."k")
|
210
564
|
|
211
|
-
However
|
212
|
-
the use out of Range-
|
565
|
+
However, since they do not have any other methods,
|
566
|
+
the use of them out of Range or its sub-classes is probably meaningless.
|
213
567
|
|
214
|
-
Note for any Numeric object,
|
568
|
+
Note for any Numeric object, you probably would like to use <tt>Float::INFINITY</tt> instead in principle.
|
215
569
|
|
216
570
|
Any objects in any user-defined Comparable class are commutatively comparable with
|
217
571
|
those two constants, as long as the cmp method of the class is written
|
218
|
-
|
572
|
+
in the *standard* way, that is, delegating the cmp method to the
|
573
|
+
parent class, ultimately +Object+, when they encounter an object of a
|
574
|
+
class they don't know.
|
219
575
|
|
220
|
-
For more detail, see
|
221
|
-
|
222
|
-
|
223
|
-
**Note1:** +RangeExtd::Infinity::POSITIVE+ is practically the same as
|
224
|
-
{Endless Range}[https://rubyreferences.github.io/rubychanges/2.6.html#endless-range-1]
|
225
|
-
introduced in Ruby 2.6 released in 2018 December!! In other words,
|
226
|
-
the official Ruby has finally implement a part of this library!
|
227
|
-
However, +RangeExtd::Infinity::NEGATIVE+ is not yet implemented in the
|
228
|
-
official Ruby Range (it has no "boundless begin"), and hence this library still has some use, which
|
229
|
-
supplements the mathematical incompleteness of the standard Range in
|
230
|
-
the official Ruby.
|
576
|
+
For more detail, see the document at {RubyGems webpage}[http://rubygems.org/gems/range_extd],
|
577
|
+
which is generated from the source-code annotation with YARD.
|
231
578
|
|
232
|
-
|
233
|
-
As of Ver.1.1, the +RangeExtd::Infinity+ class instances are not
|
234
|
-
comparable with +Float::INFINITY+; for example,
|
579
|
+
=== RangeExtd::Nowhere Class
|
235
580
|
|
236
|
-
|
581
|
+
Class {RangeExtd::Nowhere} is a Singleton class, which mimics
|
582
|
+
{NilClass}. The sole instance is available as
|
237
583
|
|
238
|
-
|
239
|
-
hence they should not be *equal*. See the reference of
|
240
|
-
{RangeExtd::Infinity} for detail. Note, the behaviour of Endless Range from Ruby 2.6
|
241
|
-
may feel a little odd, as follows:
|
584
|
+
* RangeExtd::Nowhere::NOWHERE
|
242
585
|
|
243
|
-
|
244
|
-
|
245
|
-
num1.end != rnum2.end # => true
|
246
|
-
num1.size # => Infinity
|
247
|
-
num2.size # => Infinity
|
586
|
+
This instance returns, for example, true for +nil?+ and the same
|
587
|
+
object-ID for +object_id+ as +nil+ and equal (+==+) to nil. It is used to constitute {RangeExtd::NONE}.
|
248
588
|
|
249
|
-
|
250
|
-
str1.end == num2.end # => true (because both are nil)
|
251
|
-
str1.size # => nil
|
589
|
+
It is not, however, recognised as the false value in conditional statements.
|
252
590
|
|
591
|
+
Also, a Range containing {RangeExtd::Nowhere::NOWHERE} is *not* "valid"
|
592
|
+
as a Range (see below), except for {RangeExtd::NONE}.
|
253
593
|
|
254
594
|
=== RangeExtd Class
|
255
595
|
|
256
|
-
RangeExtd objects are immutable, the same as Range.
|
257
|
-
Hence once an instance
|
596
|
+
{RangeExtd} objects are immutable, the same as {Range}.
|
597
|
+
Hence once an instance has been created, it would not change.
|
258
598
|
|
259
599
|
How to create an instance is explained above (in the Examples
|
260
|
-
sections). Any attempt to try to create an instance that is not
|
600
|
+
sections). Any attempt to try to create an instance of {RangeExtd} that is not
|
261
601
|
"valid" as a range (see below) raises an exception (<tt>ArgumentError</tt>), and fails.
|
262
602
|
|
263
603
|
There are two constants defined in this class:
|
@@ -266,7 +606,7 @@ There are two constants defined in this class:
|
|
266
606
|
* RangeExtd::ALL
|
267
607
|
|
268
608
|
The former represents the empty range and the latter does the range
|
269
|
-
covers everything, namely open-ended for the both negative and
|
609
|
+
that covers everything, namely open-ended for the both negative and
|
270
610
|
positive directions.
|
271
611
|
|
272
612
|
In addition to all the standard methods of {Range}, the following
|
@@ -278,7 +618,7 @@ See the document of each method for detail (some are defined only in
|
|
278
618
|
* <tt>valid?</tt>
|
279
619
|
* <tt>empty?</tt>
|
280
620
|
* <tt>null?</tt>
|
281
|
-
* <tt>is_none?</tt>
|
621
|
+
* <tt>is_none?</tt>
|
282
622
|
* <tt>is_all?</tt>
|
283
623
|
* <tt>equiv?</tt>
|
284
624
|
|
@@ -289,65 +629,93 @@ to the instance method <tt>valid?</tt>:
|
|
289
629
|
* <tt>RangeExtd.middle_strings=(ary)</tt>
|
290
630
|
* <tt>RangeExtd.middle_strings</tt>
|
291
631
|
|
632
|
+
==== Details about validity, emptiness, and nullness
|
633
|
+
|
292
634
|
What is valid (<tt>#valid?</tt> => true) as a range is defined as follows.
|
293
635
|
|
294
636
|
1. Both <tt>begin</tt> and <tt>end</tt> elements must be Comparable to each other,
|
295
637
|
and the comparison results must be consistent between the two.
|
296
|
-
The
|
297
|
-
|
298
|
-
|
638
|
+
The three exceptions are {RangeExtd::NONE} and Beginless and Endless Ranges
|
639
|
+
(introduced in Ruby 2.7 and 2.6, respectively),
|
640
|
+
which are all valid. Accordingly, +(nil..nil)+ is
|
641
|
+
valid in {RangeExtd} Ver.2.0+ (nb., it used to raise Exception in Ruby 1.8).
|
642
|
+
2. Except for {RangeExtd::NONE} and Beginless Range, the object of +Range#begin+ must have the method +<=+.
|
643
|
+
Therefore, some Endless Ranges (Ruby 2.6 and later) like +(true..)+ are *not* valid.
|
644
|
+
Note even "+true+" has the method +<=>+ and hence checking +<=+ is essential.
|
645
|
+
3. Similarly, except for {RangeExtd::NONE} and Endless Range, +Range#end+ must have the method +<=+.
|
646
|
+
Therefore, some Beginless Ranges (Ruby 2.7 and later) like +(..true)+ are *not* valid.
|
647
|
+
4. *begin* must be smaller than or equal (<tt>==</tt>) to *end*,
|
299
648
|
that is, <tt>(begin <=> end)</tt> must be either -1 or 0.
|
300
|
-
|
301
|
-
the exclude status of the both ends must agree
|
302
|
-
|
649
|
+
5. If *begin* is equal to *end*, namely, <tt>(begin <=> end) == 0</tt>,
|
650
|
+
the exclude status of the both ends must agree, except for the cases
|
651
|
+
where both +begin+ and +end+ are +nil+ (beginless and endless Range).
|
652
|
+
In other words, if the +begin+ is excluded, +end+ must be also excluded,
|
303
653
|
and vice versa.
|
304
|
-
For example,
|
654
|
+
For example, +(1...1)+ is NOT valid for this reason,
|
305
655
|
because any built-in Range object has the exclude status
|
306
|
-
of false (namely, inclusive) for
|
656
|
+
of +false+ (namely, inclusive) for +begin+, whereas
|
657
|
+
+RangeExtd(1...1, true)+ is valid and equal (<tt>==</tt>) to {RangeExtd::NONE}.
|
658
|
+
6. Range containing {RangeExtd::Nowhere::NOWHERE} except for
|
659
|
+
{RangeExtd::NONE} is *not* valid.
|
307
660
|
|
308
|
-
For more detail and examples see the documents of
|
661
|
+
For more detail and examples, see the documents of
|
309
662
|
{RangeExtd.valid?} and {Range#valid?}
|
310
663
|
|
311
|
-
The definition of what is empty (
|
664
|
+
The definition of what is empty ({Range#empty?} == +true+) as a range is as follows;
|
312
665
|
|
313
666
|
1. the range must be valid: <tt>valid?</tt> => true
|
314
|
-
2. if the range id discrete, that is, begin has
|
667
|
+
2. if the range id discrete, that is, +begin+ has the
|
315
668
|
<tt>succ</tt> method, there must be no member within the range
|
316
669
|
(which means the begin must be excluded, too):
|
317
670
|
<tt>to_a.empty?</tt> => true
|
318
|
-
3. if the range is continuous, that is, begin does not have
|
319
|
-
<tt>succ</tt> method, begin and end must be equal
|
320
|
-
(<tt>(begin <=> end)</tt>
|
671
|
+
3. if the range is continuous, that is, begin does not have the
|
672
|
+
<tt>succ</tt> method, +begin+ and +end+ must be equal
|
673
|
+
(<tt>(begin <=> end) == 0</tt>) and both the boundaries must
|
321
674
|
be excluded: <tt>(exclude_begin? && exclude_end?)</tt> => true.
|
322
675
|
|
323
676
|
Note that ranges with equal <tt>begin</tt> and <tt>end</tt> with inconsistent two
|
324
677
|
exclude status are not valid, as mentioned in the previous paragraph.
|
325
678
|
The built-in Range always has the begin-exclude status of
|
326
|
-
<tt>false</tt>. For that reason, no
|
327
|
-
|
679
|
+
<tt>false</tt>. For that reason, no instances of built-in Range
|
680
|
+
have the status of <tt>empty?</tt> of <tt>true</tt>.
|
328
681
|
|
329
682
|
For more detail and examples see the documents of
|
330
683
|
{Range#empty?}
|
331
684
|
|
332
685
|
Finally, {Range#null?} is equivalent to "either empty or not valid".
|
333
686
|
Therefore, for RangeExtd objects <tt>null?</tt> is equivalent to
|
334
|
-
<tt>empty?</tt>.
|
687
|
+
<tt>empty?</tt>. In most practical cases, {Range#null?} will be
|
688
|
+
perhaps more useful than {Range#empty?}.
|
335
689
|
|
336
690
|
In comparison (<tt><=></tt>) between a RangeExtd and another RangeExtd or Range
|
337
|
-
object,
|
691
|
+
object, these definitions are taken into account.
|
338
692
|
Some of them are shown in the above Examples section.
|
339
|
-
For more detail, see {Range
|
340
|
-
well as <tt>#eql?</tt>.
|
693
|
+
For more detail, see {Range#<=>} and {RangeExtd#<=>}.
|
341
694
|
|
342
695
|
Note that as long as the operation is within Range objects, the
|
343
696
|
behaviour is identical to the standard Ruby -- it is completely
|
344
|
-
compatible. Therefore, requiring this library
|
697
|
+
backward-compatible. Therefore, requiring this library should not affect any
|
345
698
|
existing code in principle.
|
346
699
|
|
700
|
+
==== equality
|
701
|
+
|
702
|
+
The method +eql?+ checks the equality of the hash values according to
|
703
|
+
Ruby's specification and hence every parameter must agree. By
|
704
|
+
contrast, <tt>==</tt> makes a more rough comparison and if the two
|
705
|
+
objects are broadly the same, returns +true+.
|
706
|
+
|
707
|
+
RaE(0...0, true) == RaE(?a...?a, true) # => false
|
708
|
+
RaE(0...1, true) == RaE(5...6, true) # => true
|
347
709
|
|
348
710
|
== Known bugs
|
349
711
|
|
350
|
-
*
|
712
|
+
* Although {RangeExtd::Nowhere::NOWHERE} cannot be used in the context of
|
713
|
+
{RangeExtd} (because it is not {Range#valid?}), users could still
|
714
|
+
use it within just the built-in Range framework.
|
715
|
+
Perhaps, {RangeExtd::Nowhere::NOWHERE} should be redefined as a
|
716
|
+
non-nil object?
|
717
|
+
* This library of Version 2+ does not work in Ruby 2.6 or earlier.
|
718
|
+
* This library of Version 1 does not work in Ruby 1.8 or earlier.
|
351
719
|
For Ruby 1.9.3 it is probably all right, though I have never tested it.
|
352
720
|
* Some unusual (rare) boundary conditions are found to vary from
|
353
721
|
version to version in Ruby, such as an implementation of +Hash#=>+.
|
@@ -358,24 +726,88 @@ existing code in principle.
|
|
358
726
|
* {RangeExtd#hash} method does not theoretically guarantee to return a unique
|
359
727
|
number for a {RangeExtd} object, though to encounter a hash number that is
|
360
728
|
used elsewhere is extremely unlikely to happen in reality.
|
729
|
+
* +RangeExtd::NONE.inspect+ and +RangeExtd::NONE.to_s+ return "Null<...Null", but it is
|
730
|
+
displayed as "nil...nil" in Ruby +irb+ and hence it is not easily
|
731
|
+
recognizable in +irb+.
|
361
732
|
|
362
733
|
Extensive tests have been performed, as included in the package.
|
363
734
|
|
364
735
|
== ToDo
|
365
736
|
|
366
|
-
|
737
|
+
* If {RangeExtd::Infinity::POSITIVE} (and NEGATIVE) behaves like
|
738
|
+
+nil+ (in the same way as {RangeExtd::Nowhere::NOWHERE}, it may be useful.
|
739
|
+
However, a range containing such objects would not work with String
|
740
|
+
like <tt>"abcde"[my_nil..]</tt>, for it seems the String class makes
|
741
|
+
a pretty rigorous check about +nil+. So, I guess the practical
|
742
|
+
applicability would not be improved so much, as far as the built-in
|
743
|
+
Ruby classes are concerned.
|
744
|
+
* A method like "+similar+" may be useful. For example,
|
745
|
+
+(-Float::INFINITY..Float::INFINITY)+ and +(-Float::INFINITYnil...Float::INFINITY)+
|
746
|
+
have no mathematical difference, because excluding an infinity is
|
747
|
+
meaningless. Indeed it makes no difference in the results of operations with
|
748
|
+
non-infinite Range/Rangeary.
|
367
749
|
|
368
750
|
== History memo
|
369
751
|
|
370
752
|
* <tt>((?a..?z) === "cc")</tt> would give false with Ruby 2.6.x or earlier, but true if later.
|
753
|
+
* <tt>(Float::INFINITY..Float::INFINITY).size</tt> used to return 0
|
754
|
+
(in Ruby-2.1 at least) but raises +FloatDomainError: NaN+ as of
|
755
|
+
Ruby-2.6 and later, including Ruby 3. I do not know in which version
|
756
|
+
the behaviour changed.
|
757
|
+
|
758
|
+
|
759
|
+
=== RangeExtd Ver.2
|
760
|
+
|
761
|
+
* The paths for the libraries are moved up by one directory in {RangeExtd} Ver.2 from Ver.1 in order that their locations follow the Ruby Gems convention.
|
762
|
+
* Compatible with Beginless Range introduced in Ruby-2.7.
|
763
|
+
* +RangeExtd::Infinity#succ+ is now undefined, in line with Float.
|
764
|
+
* Extensions for +Object+ and +Numeric+ are not in default anymore and are optional.
|
765
|
+
* +RangeExtd#eql?+ follows the Ruby default behaviour (comparison based on [#hash]), eliminating special cases in comparison with {RangeExtd::NONE}.
|
766
|
+
* Fixed a bug where +RangeExtd#min_by+ (and +max_by+ and +minmax_by+) did not work correctly.
|
767
|
+
|
768
|
+
=== RangeExtd Ver.1.1
|
769
|
+
|
770
|
+
As of Ver.1.1, the +RangeExtd::Infinity+ class instances are not
|
771
|
+
comparable with +Float::INFINITY+; for example,
|
772
|
+
|
773
|
+
RangeExtd::Infinity::POSITIVE != Float::INFINITY # => true
|
774
|
+
|
775
|
+
Conceptionally, the former is a generalized object of the latter and
|
776
|
+
hence they should not be *equal*. See the reference of
|
777
|
+
{RangeExtd::Infinity} for detail. Note, the behaviour of Endless Range from Ruby 2.6
|
778
|
+
may feel a little odd, as follows:
|
779
|
+
|
780
|
+
num1 = (5..Float::INFINITY)
|
781
|
+
num2 = (5..)
|
782
|
+
num1.end != num2.end # => true
|
783
|
+
num1.size # => Infinity
|
784
|
+
num2.size # => Infinity
|
785
|
+
|
786
|
+
str1 = (?a..)
|
787
|
+
str1.end == num2.end # => true (because both are nil)
|
788
|
+
str1.size # => nil
|
789
|
+
|
790
|
+
=== RangeExtd Ver.1.0
|
791
|
+
|
792
|
+
+RangeExtd::Infinity::POSITIVE+ is practically the same as
|
793
|
+
{Endless Range}[https://rubyreferences.github.io/rubychanges/2.6.html#endless-range-1]
|
794
|
+
introduced in Ruby 2.6 released in 2018 December!! In other words,
|
795
|
+
the official Ruby has finally implement a part of this library!
|
796
|
+
However, +RangeExtd::Infinity::NEGATIVE+ was not yet implemented (at
|
797
|
+
the time) in the
|
798
|
+
official Ruby Range (it has no "boundless begin").
|
799
|
+
|
371
800
|
|
372
801
|
== Final notes
|
373
802
|
|
374
803
|
All the behaviours within RangeExtd (not Range), such as
|
375
804
|
any comparison between two RangeExtd, should be (or hopefully?)
|
376
805
|
natural for you. At least it is well-defined and self-consistent, as
|
377
|
-
the logical structure of the ranges is now complete with RangeExtd.
|
378
|
-
|
806
|
+
the logical structure of the ranges is now complete with {RangeExtd}.
|
807
|
+
|
808
|
+
In this section in the earlier versions, I wrote:
|
809
|
+
|
810
|
+
> Note that some behaviours for open-ended or begin-excluded ranges may
|
379
811
|
give you a little shock at first. For example, the method
|
380
812
|
<tt>member?(obj)</tt> for an open-ended range for the negative direction with
|
381
813
|
discrete elements returns <tt>nil</tt>. That is because no meaningful method
|
@@ -384,11 +816,15 @@ theoretically impossible in general to check whether the given obj is a member o
|
|
384
816
|
the range or not. You may find it to be weird, but that just means
|
385
817
|
the concept of the infinity is unfamiliar to us mortals!
|
386
818
|
|
387
|
-
|
388
|
-
|
819
|
+
Now, interestingly, the introduction of "beginless Range" in Ruby
|
820
|
+
means every Ruby programmer must be familiar with the concept!
|
821
|
+
I would call it a progress.
|
822
|
+
|
823
|
+
Still, comparisons between RangeExtd and Range may give you
|
824
|
+
occasional surprises. This is because some of the accepted
|
389
825
|
ranges by built-in Range class are no longer valid in this framework with the
|
390
826
|
inclusion of exclude-status of the begin boundary, as explained.
|
391
|
-
Hopefully you will feel it
|
827
|
+
Hopefully you will feel it natural as you get accustomed to it.
|
392
828
|
And I bet once you have got accustomed to it, you will never want to
|
393
829
|
go back to the messy world of logical incompleteness, that is, the
|
394
830
|
current behaviour of Range!
|
@@ -396,9 +832,6 @@ current behaviour of Range!
|
|
396
832
|
Enjoy.
|
397
833
|
|
398
834
|
|
399
|
-
|
400
|
-
== Miscellaneous
|
401
|
-
|
402
835
|
== Copyright etc
|
403
836
|
|
404
837
|
Author:: Masa Sakano < info a_t wisebabel dot com >
|
@@ -410,130 +843,459 @@ Versions:: The versions of this package follow Semantic Versioning (2.0.0) http:
|
|
410
843
|
|
411
844
|
= RangeExtd - 拡張Rangeクラス - exclude_begin と無限大に開いた範囲と
|
412
845
|
|
846
|
+
== はじめに
|
847
|
+
|
413
848
|
このパッケージは、Range を拡張した RangeExtd クラスを定義しています。
|
414
849
|
以下の特徴を持ちます。
|
415
850
|
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
851
|
+
1. メソッド exclude_begin? の導入 (レンジの始点を除外できる),
|
852
|
+
2. (無限大に)開いたレンジ(+nil+のように*未定義*のレンジではない)
|
853
|
+
3. NONE (空レンジ) と ALL (全範囲レンジ)定数の導入
|
854
|
+
4. Rubyで初めて自己論理的に完結したレンジ構造の達成
|
855
|
+
5. 組込Rangeとの完全後方互換性
|
421
856
|
|
422
857
|
組込Rangeにある exclude_end に加えて、exclude_beginを導入したこと、及
|
423
858
|
び無限大へ開いた範囲を許可したことで、一次元上の範囲の論理的完全性を実
|
424
859
|
現しました。
|
425
860
|
|
426
|
-
|
427
|
-
|
428
|
-
|
861
|
+
このライブラリの最大の利点は、FloatやRationalのような数で応用場面の多
|
862
|
+
い複数レンジの論理演算が可能になったことです。
|
863
|
+
Gem {Rangeary}[https://rubygems.org/gems/rangeary] は本ライブラリをフ
|
864
|
+
ルに用いて、レンジの論理演算の概念を実現しています。そのためには、無限
|
865
|
+
に開いた可能性がありまた始端と終端のいずれもが除外されている可能性がある
|
866
|
+
レンジの概念が不可欠でした。たとえば、
|
867
|
+
レンジ +(?a..?d)+ の否定(あるいは補集合)が2つのレンジ +(-"Infinity-Character"...3)+ と
|
868
|
+
+(?d(exclusive).."Infinity-Character")+ であり、その否定が元の
|
869
|
+
+(?a..?d)+ になります。このような演算は、本 +RangeExtd+ クラスを用いる
|
870
|
+
ことで初めて可能になります。
|
871
|
+
|
872
|
+
Rangeary: {https://rubygems.org/gems/rangeary}
|
873
|
+
|
874
|
+
組込 Rangeは大変有用なクラスであり、Rubyユーザーに容易なプログラミングを可能にす
|
875
|
+
るツールでした。しかし、始点を除外することができないのが玉に瑕でありました。
|
876
|
+
|
877
|
+
ただし、それにはれっきとした理由があることは分かります。Rubyの Rangeは、Numeric
|
878
|
+
(厳密にはその実数を表現したもの)だけに限ったものではありません。 <tt>succ()</tt> メソッ
|
879
|
+
ドを持つオブジェクトによる Rangeは極めて有用です。一方、<tt>succ()</tt> の逆に相
|
880
|
+
当するメソッドは一般的には定義されていません。そういう意味で、Rangeは本質的に非
|
881
|
+
対称です。加えて、よく使われる Rangeオブジェクトのうちあるもの(たとえば Float)は
|
882
|
+
連続的なのに対し、そうでないものも普通です(たとえば Integer や String)。この状況
|
883
|
+
が厳密な定義をする時の混乱に拍車をかけています。
|
884
|
+
|
885
|
+
ここで始点を除外可能としたことは、そういう意味で、道筋が100パーセント明らかなも
|
886
|
+
のではありませんでした。ここで私が採用した {RangeExtd}クラスの定義は、おそらく、考え
|
887
|
+
られる唯一のものではないでしょう。とはいえ、個人的には満足のいくものに仕上がりま
|
888
|
+
したし、このレンジという枠内での論理的完全性をうまく達成できたと思います。
|
889
|
+
|
890
|
+
このクラスが少なからぬ人に有用なものであることを願ってここにリリースします。
|
891
|
+
|
892
|
+
=== Rangeの正当性
|
893
|
+
|
894
|
+
Rubyの組込みRangeは、メンバーに許されるものに対してとても慣用です。た
|
895
|
+
とえば、+(true...true)+ は、それが何を意味するのかはともかく、完全に正
|
896
|
+
当なRangeです。もっとも、イテレーターを伴う+each+や+to_a+ といったメソッ
|
897
|
+
ドを使おうとすると例外(+TypeError+)が発生ますし、利用価値はごく限られ
|
898
|
+
るでしょうが。
|
899
|
+
|
900
|
+
本ライブラリにより、Rangeの「正当性」が厳密に定義されます。本ライブラ
|
901
|
+
リは、Rangeクラスに{Range#valid?}, {Range#null?} and {Range#empty?}を
|
902
|
+
はじめとするいつくつかのメソッドを追加します。それらはもちろん、すべて
|
903
|
+
の子クラスにも継承されます。
|
904
|
+
|
905
|
+
一例として、 <tt>(3...3).valid?</tt> は偽(false)を返します。なぜならば、
|
906
|
+
要素3は始端では含まれるのに終端では除外されているため、相互に矛盾してい
|
907
|
+
るからです。このRangeExtd クラスでは、次の2つが正当なレンジと見做され
|
908
|
+
ます。
|
429
909
|
|
430
|
-
|
431
|
-
|
432
|
-
盾しているためです。ここで導入する RangeExtdクラスにおいては、以下のよ
|
433
|
-
うにこれが有効なレンジとして定義できます。
|
910
|
+
* RangeExtd.new(3, 3, true, true) # => an empty range
|
911
|
+
* RangeExtd.new(3, 3, false, false) # => a single-point range (3..3)
|
434
912
|
|
435
|
-
|
436
|
-
|
913
|
+
ただし、もし組込みRangeに閉じて使う限りは、何も変わりません。つまり、
|
914
|
+
標準Rubyと完全に互換性を保っています。
|
437
915
|
|
438
|
-
|
439
|
-
ん。つまり、標準の Rubyとの完全な後方互換性を実現しています。
|
916
|
+
=== 無限に開いたレンジ
|
440
917
|
|
441
|
-
|
918
|
+
Ruby 2.6 と 2.7 でそれぞれ終端および始端のないRangeが導入されました。
|
919
|
+
これら境界のないRangeと本ライブラリの無限に開いたRangeとの違いは
|
920
|
+
少々難解で、現実の場面で実用的というよりは、概念的哲学的なものといって
|
921
|
+
いいでしょう(詳しくは「背景」の章を参照)。しかし、気にすることはありま
|
922
|
+
せん。実用という意味では、両者は互換であり、それほど気を遣うことはあり
|
923
|
+
ません。端的には、特に不満がない限りは、組込みの境界のないRangeを使え
|
924
|
+
ばよいでしょう。
|
925
|
+
|
926
|
+
無限に開いたレンジを表すのは以下のようにします。{RangeExtd::Infinity}クラスで
|
442
927
|
定義されている二つの定数(無限大または無現小、あるいは無限前と無限後)の
|
443
928
|
いずれかを用います。
|
444
929
|
|
445
930
|
* RangeExtd::Infinity::NEGATIVE
|
446
931
|
* RangeExtd::Infinity::POSITIVE
|
447
932
|
|
448
|
-
これらは基本的に <tt>Float::INFINITY</tt> を全ての Comparable
|
449
|
-
|
933
|
+
これらは基本的に <tt>Float::INFINITY</tt> を全ての Comparable
|
934
|
+
であるオブジェクトに*一般化*したものです。たとえば、
|
450
935
|
|
451
936
|
("a"..RangeExtd::Infinity::POSITIVE).each
|
452
937
|
|
453
938
|
は、"a"から始まる <tt>String#succ</tt> を使った無限のイテレーターを与えます
|
454
939
|
(だから、どこかで必ず breakするようにコードを書きましょう!)。
|
940
|
+
この例の場合、Ruby-2.6以上の以下とまったく同じように動きます。
|
455
941
|
|
456
|
-
|
457
|
-
るツールでした。しかし、始点を除外することができないのが玉に瑕でありました。
|
942
|
+
("a"..).each
|
458
943
|
|
459
|
-
|
460
|
-
(厳密にはその実数を表現したもの)だけに限ったものではありません。 <tt>succ()</tt> メソッ
|
461
|
-
ドを持つオブジェクトによる Rangeは極めて有用です。一方、<tt>succ()</tt> の逆に相
|
462
|
-
当するメソッドは一般的には定義されていません。そういう意味で、Rangeは本質的に非
|
463
|
-
対称です。加えて、よく使われる Rangeオブジェクトのうちあるもの(たとえば Float)は
|
464
|
-
連続的なのに対し、そうでないものも普通です(たとえば Integer や String)。この状況
|
465
|
-
が厳密な定義をする時の混乱に拍車をかけています。
|
944
|
+
=== News: Libraryの場所他
|
466
945
|
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
946
|
+
**重要**: ライブラリのパスが{RangeExtd} Ver.1 から Ver.2 で、
|
947
|
+
ディレクトリの階層一つ上がりました。これは、Ruby Gemの慣用にそうように
|
948
|
+
するためです。端的には、標準的方法は、+require "range_extd"+ です。
|
949
|
+
以前のパスは、"range_extd/range_extd" でした。
|
471
950
|
|
472
|
-
|
951
|
+
それに伴い、{RangeExtd} のバージョンを2.0にあげました。
|
473
952
|
|
953
|
+
以下が、その主な変更点です。詳しくは、「履歴メモ」章を参照ください。
|
474
954
|
|
475
|
-
|
955
|
+
==== News: Beginless Range サポートしました
|
476
956
|
|
477
|
-
|
478
|
-
|
957
|
+
Ruby 2.7 で始端のない {Beginless range}[https://rubyreferences.github.io/rubychanges/2.7.html#beginless-range]
|
958
|
+
がサポートされました。
|
959
|
+
+RangeExtd+ も今やサポートします。この影響で、仕様に重要な変更があります。
|
960
|
+
|
961
|
+
まず、{RangeExtd::NONE} は、事実上
|
962
|
+
+RangeExtd((RangeExtd::Nowhere::NOWHERE...RangeExtd::Nowhere::NOWHERE), true)+
|
963
|
+
になりました。すなわち、両端が
|
964
|
+
{RangeExtd::Nowhere::NOWHERE} であり、
|
965
|
+
両端({RangeExtd#begin} と {RangeExtd#end})とも除外されています。
|
966
|
+
以前のバージョンでは、{RangeExtd::NONE} の両端は +nil+ でした。
|
967
|
+
Range +(nil..nil)+ は、Ruby-2.6 およびそれ以前ではそもそも許されていな
|
968
|
+
くて、そのために{RangeExtd::NONE}に独特な表記として幸便だったものです。
|
969
|
+
しかし、Ruby-2.7 以降では nil から nil のRangeが許容されます。つまり、
|
970
|
+
+(nil..nil)+ は完全に正当であり、それは {RangeExtd::NONE} を表していた
|
971
|
+
+RangeExtd((nil...nil), true)+ と事実上同じになってしまいます。前者は、
|
972
|
+
後者とはまったく異なるオブジェクトであり、(除外フラグをのぞけば)むしろ
|
973
|
+
{RangeExtd::ALL} に極めて近いにも拘らずです。だから、変更が必要になったのです。
|
974
|
+
|
975
|
+
もっとも、この変更は概念的なものであり、ユーザー視点ではほぼ変更は見えません。
|
976
|
+
{RangeExtd::NONE}の始端と終端である {RangeExtd::Nowhere::NOWHERE} は、
|
977
|
+
{NilClass} とほぼまったく同じように振る舞います(以下の「詳説」章を参照)。
|
978
|
+
{RangeExtd} Ver.1以前でその値はまさに +nil+ だったことを考えれば、
|
979
|
+
Ver.2で{RangeExtd::Nowhere::NOWHERE} が{RangeExtd::NONE}
|
980
|
+
に使われるようになったと言っても、今まで動いていたコードには何の変更も必要ないはずです。
|
981
|
+
オブジェクトが{RangeExtd::NONE} かどうかをチェックする推奨方法は、
|
982
|
+
今までもずっとそうだったように、{RangeExtd#is_none?} です
|
983
|
+
(+Enumerable#none?+とは異なるのでご注意)。実用的には、
|
984
|
+
{Range#null?} がユーザーが希望する挙動であることが大半でしょう
|
985
|
+
(注: +(true..true).null?+ は偽(+false+)を返すことに注意。
|
986
|
+
本マニュアルの「詳説」章の「RangeExtd Class」を参照)。
|
987
|
+
|
988
|
+
次に、+RangeExtd.valid?(nil..)+ は、真(+true+)を返すようになりました。
|
989
|
+
以前は、偽を返していました。そしてそれは {RangeExtd::ALL} に等しいです。
|
990
|
+
|
991
|
+
たとえば、+"abc"[nil..]+ は以前のバージョンでは不正もしくは文法エラー
|
992
|
+
さえ出ていましたが、Ruby-2.7以降では完全に正当なRuby表現です。
|
993
|
+
したがって、もし仮に+RangeExtd+ がそれらを正当でないと見做したならば、
|
994
|
+
不自然に受け取られるでしょう。
|
479
995
|
|
480
|
-
|
996
|
+
+RangeExtd.valid?(true..)+ は、依然 +false+ を返します。
|
481
997
|
|
482
|
-
|
483
|
-
{Rangeary}[https://rubygems.org/gems/rangeary] は、本ライブラリを使い
|
484
|
-
切っています。Rangeを実現するためには、始端と終端との両方で開いた可能
|
485
|
-
性があるRangeを扱うことが必須だからです。例えば、
|
486
|
-
Range +(?a..?d)+ の否定は、複数Range +(-"Infinity(文字)"...3)+ と
|
487
|
-
+(?d(始端除外).."Infinity(文字)")+ であり、その否定は、元の +(?a..?d)+
|
488
|
-
です。このような演算は、+RangeExtd+ があって初めて可能になります。
|
998
|
+
他の大きな変更には以下があります。
|
489
999
|
|
490
|
-
|
1000
|
+
* +RangeExtd::Infinity#succ+ はFloatクラスに合わせて未定義になりました。
|
1001
|
+
* +Object+ と +Numeric+ クラスの拡張はデフォルトではなく、オプション(ユーザーの選択)となりました。
|
1002
|
+
* +RangeExtd#eql?+ はRubyの標準([#hash]値を比較)にそうようにし、今まであった{RangeExtd::NONE}との特別な比較ルーチンを削除しました。
|
1003
|
+
* +RangeExtd#min_by+ (+max_by+ と +minmax_by+) のバグを修正しました。
|
1004
|
+
|
1005
|
+
==== News: Endless Range サポートしました
|
1006
|
+
|
1007
|
+
2019年10月より、本パッケージは、Ruby 2.6 で導入された {Endless Range}[https://rubyreferences.github.io/rubychanges/2.6.html#endless-range-1]
|
1008
|
+
(終端のない Range)を正式サポートしました。よって、Version 1.0 をリリースしました!
|
1009
|
+
|
1010
|
+
Ruby 2.7 では、{Beginless range}[https://rubyreferences.github.io/rubychanges/2.7.html#beginless-range] が導入されました.
|
491
1011
|
|
492
1012
|
==== 注: Rangesmallerとの関係
|
493
1013
|
|
494
1014
|
このパッケージは、(今やサポートされていない) {Rangesmaller}[https://rubygems.org/gems/rangesmaller] パッケージ及びクラスを
|
495
1015
|
後継するものです。同クラスの機能に、無限に開いた範囲を許す機能が加わり、また、オ
|
496
1016
|
ブジェクト生成時のインターフェースが変更されています。
|
497
|
-
https://rubygems.org/gems/rangesmaller
|
1017
|
+
{https://rubygems.org/gems/rangesmaller}
|
1018
|
+
|
1019
|
+
|
1020
|
+
== 背景
|
1021
|
+
|
1022
|
+
=== Endless Range と Beginless Range
|
1023
|
+
|
1024
|
+
{Endless Range}[https://rubyreferences.github.io/rubychanges/2.6.html#endless-range-1]
|
1025
|
+
(終端のないRange)と
|
1026
|
+
{Beginless Range}[https://rubyreferences.github.io/rubychanges/2.7.html#beginless-range]
|
1027
|
+
(始端のないRange)はそれぞれ
|
1028
|
+
2018年12月および2019年12月リリースの Ruby 2.6 と 2.7 で導入されました。
|
1029
|
+
|
1030
|
+
そのおかげで、Rubyの組込み+Range+ は、+RangeExtd+ が提供していた機能の
|
1031
|
+
いくつかを持つようになりました。
|
1032
|
+
ただし、今でも、明快なものも微妙なものも含めていくつかの違いがあります。
|
1033
|
+
|
1034
|
+
本ライブラリのはっきりとした利点は、+exclude_begin?+ (つまり始端を除外する)機能です。
|
1035
|
+
|
1036
|
+
境界のないRangeについては、本ライブラリが提供するものは、一言で言えば、
|
1037
|
+
抽象的な意味で無限に開いたレンジです。
|
1038
|
+
概念的な主な違いは、Rubyの組込み+Range+ は*未定義*の境界を表すのに対し、
|
1039
|
+
+RangeExtd+ は*無限に開いた*境界を表します。
|
1040
|
+
|
1041
|
+
この違いは微妙ながら、はっきりとした意味があります。
|
1042
|
+
以下に、標準Rubyの特にNumericのRangeを例示します。というのも、Numeric
|
1043
|
+
はこの両方を提供しているために、違いがわかりやすいのです。
|
1044
|
+
|
1045
|
+
"abcdef"[..2] # => "abc"
|
1046
|
+
"abcdef"[..2.0] # => "abc"
|
1047
|
+
"abcdef"[(-Float::INFINITY)..2] # raise (RangeError)
|
1048
|
+
"abcdef"[(-1)..2] # => ""
|
1049
|
+
"abcdef"[(-6)..2] # => "abc"
|
1050
|
+
"abcdef"[(-7)..2] # => nil
|
1051
|
+
(-Float::INFINITY..5).first(1) # raise: can't iterate from Float (TypeError)
|
1052
|
+
(-Float::INFINITY..5).first # => -Infinity
|
1053
|
+
(-Float::INFINITY..5).begin # => -Infinity
|
1054
|
+
(..5).first # raise: cannot get the first element of beginless range (RangeError)
|
1055
|
+
(..5).begin # => nil
|
1056
|
+
|
1057
|
+
最初、そして2番目の式に出てくるのが始端のないRangeで、始端が未定義です。
|
1058
|
+
Stringクラスは、その「始点の値」を0だと*解釈*しています。
|
1059
|
+
対照的に、3番目の式では、例外が発生しています。この仕様は、
|
1060
|
+
始点の値が定義されていて、でも負の無限大だから、と考えれば、理解できます。
|
1061
|
+
実際、Stringの場合、負の数の添字は、(4番目、6番目の例にあるように)
|
1062
|
+
特別な意味を持っていますからね。
|
1063
|
+
|
1064
|
+
最後の5つの例は、興味深いです。
|
1065
|
+
+Range#begin+ は単純に始点の値を返します。
|
1066
|
+
+Range#first+ は引数が与えられなければ、最初の「要素」を返します。
|
1067
|
+
+(Float::INFINITY..5)+ には最初の要素があるため、それが返されます。
|
1068
|
+
しかしbeginless {Range} では話が異なります。定義された最初の要素がないため、
|
1069
|
+
+Range#first+ は、+RangeError+ 例外を発生させます。対照的に、
|
1070
|
+
引数 +n+ が +Range#first+ に与えられた時は、+n+個の要素を持つ配列
|
1071
|
+
(Array)が返されなければなりません。Floatから値を数えるのは未定義であるため、
|
1072
|
+
負の無限大からのRangeの場合は、+TypeError+ 例外が発生します。
|
1073
|
+
筋が通っていると思いませんか?
|
1074
|
+
|
1075
|
+
ところで、補足すると、+(5..8.6).last(2)+ は正当であって配列 +[7, 8]+
|
1076
|
+
を返します。また、+(2.2..8.6).size+ も(なぜか?)正当です。混乱しますね……。
|
1077
|
+
|
1078
|
+
別のポイントとして、無限大には明快な数学的定義がありますが、
|
1079
|
+
すべてのRangeがそれを認めるわけではありません。たとえば、
|
1080
|
+
自作クラスで、アルファベット小文字1文字だけを持ち、
|
1081
|
+
Stringクラスと同様にRangeが定義できる例を考えてみます。
|
1082
|
+
すると、最小の始端と最大の終端は、それぞれ"+a+"と"+z+"です。
|
1083
|
+
この場合、「無限大」(あるいは無限小)とは何を意味するでしょうか?
|
1084
|
+
厳密な意味では、それは不正とすべきでしょうか。あるいは、
|
1085
|
+
この場合の無限大は"+z+"を表す数と解釈すべきでしょうか?
|
1086
|
+
それとも別のなにか?
|
1087
|
+
|
1088
|
+
概念としては、包括的な解釈のほうが便利です。実際、
|
1089
|
+
{Rangeary}[https://rubygems.org/gems/rangeary] ライブラリは、
|
1090
|
+
+RangeExtd+ をそのように利用しています。すなわち、
|
1091
|
+
Range の「否定」(補集合)を積極的に用いて、複数Ranges
|
1092
|
+
の論理演算を実現しています。しかし、
|
1093
|
+
すべてのアプリケーションにそう解釈することを強要することはできません。
|
1094
|
+
|
1095
|
+
まとめると、*未定義*境界は定義上未定義であり、その解釈はアプリケーション任せになるのに対し、
|
1096
|
+
正負の無限大境界は明快な定義はあるかも知れないけれど、実際の応用では柔軟な解釈が望ましい場合もあるかもしれない、
|
1097
|
+
というところです。
|
1098
|
+
|
1099
|
+
これらを考慮し、本ライブラリの
|
1100
|
+
{RangeExtd::Infinity::NEGATIVE} と {RangeExtd::Infinity::POSITIVE}
|
1101
|
+
とは、事実上 +nil+ のように振る舞うようにデザインされています。ただし、
|
1102
|
+
ユーザーが別扱いすることは可能です。
|
1103
|
+
|
1104
|
+
=== endless and beginless Rangesの振舞い
|
1105
|
+
|
1106
|
+
組込み Endless/Beginless Range の振舞いは幾分混乱するところがあります。
|
1107
|
+
加えて、+Range#size+にはバグが複数あるようです
|
1108
|
+
({Bug #18983}[https://bugs.ruby-lang.org/issues/18983] と
|
1109
|
+
{Bug #18993}[https://bugs.ruby-lang.org/issues/18993])。
|
1110
|
+
少なくとも、公式マニュアルに記載されている仕様とは矛盾する振舞いがあり、
|
1111
|
+
混乱に拍車をかけます。
|
1112
|
+
|
1113
|
+
Rubyの実装では、beginless/endless Rangesの始端と終端の値は、
|
1114
|
+
+nil+ と解釈されます。 Rubyでは +nil == nil+ が真であるために、
|
1115
|
+
|
1116
|
+
(?a..).end == (5..).end
|
1117
|
+
|
1118
|
+
も真です。一方、
|
1119
|
+
|
1120
|
+
(?a..).end == (5..Float::INFINITY).end
|
1121
|
+
|
1122
|
+
は偽(+false+)です。以下が幅広い例です。
|
1123
|
+
|
1124
|
+
(-Float::INFINITY..Float::INFINITY).size # => Infinity
|
1125
|
+
( Float::INFINITY..Float::INFINITY).size # raises FloatDomainError
|
1126
|
+
num1 = (5..Float::INFINITY)
|
1127
|
+
num2 = (5..)
|
1128
|
+
num1.end != num2.end # => true
|
1129
|
+
num1.size # => Infinity
|
1130
|
+
num2.size # => Infinity
|
1131
|
+
|
1132
|
+
str1 = (?a..)
|
1133
|
+
str1.end != num1.end # => true
|
1134
|
+
str1.end == num2.end # => true (because both are nil)
|
1135
|
+
str1.size # => nil (because Range#size is defined for Numeric only)
|
1136
|
+
(..?z).size # => Infinity (contradicting the specificatin?)
|
1137
|
+
|
1138
|
+
(..3).to_s => "..3"
|
1139
|
+
(3..).to_s => "3.."
|
1140
|
+
(3..nil).to_s => "3.."
|
1141
|
+
(nil..3).to_s => "..3"
|
1142
|
+
|
1143
|
+
(nil..) == (..nil) # => true
|
1144
|
+
(nil..) != (...nil) # => true (because exclude_end? differ)
|
1145
|
+
"abcdef"[..nil] # => "abcdef" (i.e., it is interpreted as (0..IntegerInfinity)
|
1146
|
+
# (n.b., nil.to_i==0; Integer(nil) #=> TypeError))
|
1147
|
+
"abcdef"[..?a] # raise: no implicit conversion of String into Integer (TypeError)
|
1148
|
+
"abcdef"[0..100] # => "abcdef"
|
1149
|
+
"abcdef"[-100..100] # => nil
|
1150
|
+
|
1151
|
+
(..nil).size # => Float::INFINITY
|
1152
|
+
|
1153
|
+
(..nil).begin # => nil
|
1154
|
+
(..nil).first # raise: cannot get the first element of beginless range (RangeError)
|
1155
|
+
(..nil).last # raise: cannot get the last element of endless range (RangeError)
|
1156
|
+
(..nil).end # => nil
|
1157
|
+
|
1158
|
+
(..nil).cover? 5 # => true
|
1159
|
+
(..nil).cover? ?a # => true
|
1160
|
+
(..nil).cover? [?a] # => true
|
1161
|
+
(..nil).cover? nil # => true
|
1162
|
+
|
1163
|
+
Integerクラスならば、
|
1164
|
+
|
1165
|
+
num1 = (5..Float::INFINITY)
|
1166
|
+
num2 = (5..)
|
1167
|
+
num1.end != num2.end # => true (because (Float::INFINITY != nil))
|
1168
|
+
num1.size # => Float::INFINITY
|
1169
|
+
num2.size # => Float::INFINITY
|
1170
|
+
|
1171
|
+
(3...) == (3...nil) # => true
|
1172
|
+
(3..) != (3...nil) # => true (because exclude_end? differ)
|
1173
|
+
|
1174
|
+
(3..).size # => Float::INFINITY
|
1175
|
+
(..3).begin # => nil
|
1176
|
+
(..3).first # raise: cannot get the first element of beginless range (RangeError)
|
1177
|
+
(3..).last # raise: cannot get the last element of endless range (RangeError)
|
1178
|
+
(3..).end # => nil
|
1179
|
+
(..3).each{} # raise: `each': can't iterate from NilClass (TypeError)
|
1180
|
+
(..3).to_a # raise: `each': can't iterate from NilClass (TypeError)
|
1181
|
+
(3..).to_a # raise: `to_a': cannot convert endless range to an array (RangeError)
|
1182
|
+
(3..Float::INFINITY).to_a # Infinite loop!
|
1183
|
+
|
1184
|
+
(-Float::INFINITY..4).first # => -Float::INFINITY
|
1185
|
+
(4..Float::INFINITY).last # => Float::INFINITY
|
1186
|
+
(-Float::INFINITY..4).first(2) # raise: can't iterate from Float (TypeError)
|
1187
|
+
(4..Float::INFINITY).last(2) # Infinite loop!
|
1188
|
+
|
1189
|
+
Stringクラス(あるいはユーザー定義クラス?)ならば、
|
1190
|
+
|
1191
|
+
(?a..).end == (5..).end # => true (because both are nil)
|
1192
|
+
(?a..).end != (5..Float::INFINITY).end # => true
|
1193
|
+
(..?a).begin == (..5).begin # => true (because both are nil)
|
1194
|
+
(..?a).begin != ((-Float::INFINITY)..5).begin # => true
|
1195
|
+
(..?a).size # => Float::INFINITY
|
1196
|
+
(?a..).size # => nil
|
1197
|
+
|
1198
|
+
(..?a).begin # => nil
|
1199
|
+
(..?a).first # raise: cannot get the first element of beginless range (RangeError)
|
1200
|
+
(?a..).last # raise: cannot get the last element of endless range (RangeError)
|
1201
|
+
(?a..).end # => nil
|
1202
|
+
(..?a).each{} # raise: `each': can't iterate from NilClass (TypeError)
|
1203
|
+
(..?a).to_a # raise: `each': can't iterate from NilClass (TypeError)
|
1204
|
+
(?a..).to_a # raise: `to_a': cannot convert endless range to an array (RangeError)
|
1205
|
+
(?a..Float::INFINITY).to_a # raise: bad value for range (ArgumentError) # b/c it is not String!
|
1206
|
+
|
1207
|
+
=== Range#size についての注記
|
1208
|
+
|
1209
|
+
+Range#size+ の振舞いはとてもわかりにくいです。
|
1210
|
+
{公式マニュアル}[https://ruby-doc.org/core-3.1.2/Range.html#method-i-size] によれば、
|
1211
|
+
|
1212
|
+
Returns the count of elements in self if both begin and end values are numeric;
|
1213
|
+
otherwise, returns nil
|
1214
|
+
|
1215
|
+
しかし、実際のRubyの挙動は必ずしもこの通りではありません(上述の例参照)。
|
1216
|
+
加えて、一般のNumeric に対して"elements"が一体何かは不明瞭です。
|
1217
|
+
だから、Stringならば必ずnilが買える
|
1218
|
+
以下が一例です({Bug #18993}[https://bugs.ruby-lang.org/issues/18993] として報告済):
|
1219
|
+
|
1220
|
+
(5.quo(3)...5).size # => 3
|
1221
|
+
(5.quo(3).to_f...5).size # => 4
|
1222
|
+
(5.quo(3)..5).size # => 4
|
1223
|
+
(5.quo(3).to_f..5).size # => 4
|
1224
|
+
|
1225
|
+
=== Range#count についての注記
|
1226
|
+
|
1227
|
+
+Range#count+ の振舞いの大半は理解できます。しかし、
|
1228
|
+
境界のないものや無限大関係は自明ではありません。
|
1229
|
+
|
1230
|
+
(5..).count # => Float::INFINITY
|
1231
|
+
(..5).count # => Float::INFINITY
|
1232
|
+
(..nil).count # => Float::INFINITY
|
1233
|
+
(-Float::INFINITY..nil) # => Float::INFINITY
|
1234
|
+
(-Float::INFINITY..Float::INFINITY).count # raises (TypeError) "can't iterate from Float"
|
1235
|
+
(..5).count(4) # raises (TypeError)
|
1236
|
+
(..5).count{|i| i<3} # raises (TypeError)
|
1237
|
+
(1..).count(4) # infinite loop!
|
1238
|
+
(1..).count{|i| i<3} # infinite loop!
|
1239
|
+
|
1240
|
+
端的には、一部の特別なケースについては、同メソッドは Infinity (無限大)を返します。
|
1241
|
+
|
1242
|
+
これを考慮して本ライブラリの+RangeExtd::ALL.count+ は、特別なケースとして、
|
1243
|
+
returns +Float::INFINITY+ を返します。
|
1244
|
+
|
498
1245
|
|
499
1246
|
== インストール
|
500
1247
|
|
501
1248
|
gem install range_extd
|
502
1249
|
|
503
|
-
|
1250
|
+
により、
|
504
1251
|
|
505
|
-
range_extd
|
506
|
-
range_extd/infinity
|
1252
|
+
range_extd.rb
|
1253
|
+
range_extd/infinity.rb
|
507
1254
|
|
508
|
-
|
1255
|
+
をはじめとした数個のファイルが<tt>$LOAD_PATH</tt> の一カ所にインストールされるはずです。
|
509
1256
|
|
510
|
-
|
511
|
-
|
512
|
-
http://rubygems.org/gems/range_extd
|
1257
|
+
あるいは、パッケージを{http://rubygems.org/gems/range_extd}から入手できます。
|
513
1258
|
|
514
1259
|
後は、Ruby のコード(又は irb)から
|
515
1260
|
|
516
|
-
require
|
517
|
-
|
518
|
-
とするだけです。もしくは、特に手でインストールした場合は、
|
1261
|
+
require "range_extd/load_all"
|
519
1262
|
|
520
|
-
|
1263
|
+
とするだけです。もしくは、本ライブラリのの最小限セットだけ使う場合は、
|
521
1264
|
|
522
|
-
|
1265
|
+
require "range_extd"
|
523
1266
|
|
524
|
-
|
1267
|
+
でもいいです。
|
1268
|
+
端的には "+range_extd/load_all.rb+" は、ラッパーであり、以下のファイルを読み込みます:
|
525
1269
|
|
526
|
-
|
1270
|
+
require "range_extd"
|
1271
|
+
require "range_extd/numeric"
|
1272
|
+
require "range_extd/object"
|
1273
|
+
require "range_extd/infinity"
|
1274
|
+
require "range_extd/nowhere"
|
1275
|
+
require "range_extd/range"
|
1276
|
+
require "range_extd/nil_class"
|
527
1277
|
|
528
|
-
|
1278
|
+
このうち、最初の3つは独立で、下の4つは一番上のファイルと必ず一緒に使われるもので、最初のファイルを読めば自動的に読み込まれます。
|
529
1279
|
|
1280
|
+
2番目と3番目のファイルは、ユーティリティライブラリです。読み込めば、
|
1281
|
+
Ruby組込みクラスの +Object+ と +Numeric+ (+Float+ と +Integer+を含む)
|
1282
|
+
にいくつかのメソッドが追加されたり機能が追加されます。
|
1283
|
+
追加された機能はすべて後方互換であり、単に既存のクラスに機能を追加するだけです。
|
1284
|
+
これらの読み込みを強く推奨します。もし読み込まない場合は、本ライブラリ
|
1285
|
+
のパワーがごく限られてしまいます。たとえば、比較演算子+<=>+
|
1286
|
+
が可換でないため、驚くような挙動になることがあるでしょう。
|
1287
|
+
具体的な追加機能はそれぞれのマニュアルを参照ください。
|
530
1288
|
|
531
1289
|
== 単純な使用例
|
532
1290
|
|
1291
|
+
以下の例では、ライブラリのすべてのファイルが読み込まれている(require)
|
1292
|
+
と仮定します。
|
1293
|
+
|
533
1294
|
=== RangeExtd インスタンスを作成する方法
|
534
1295
|
|
535
1296
|
以下に幾つかの基本的な使用例を列挙します。
|
536
1297
|
|
1298
|
+
require "range_extd/load_all"
|
537
1299
|
r = RangeExtd(?a...?d, true) # => a<...d
|
538
1300
|
r.exclude_begin? # => true
|
539
1301
|
r.to_a # => ["b", "c"]
|
@@ -545,7 +1307,8 @@ https://rubygems.org/gems/rangesmaller
|
|
545
1307
|
(RangeExtd::Infinity::NEGATIVE..RangeExtd::Infinity::POSITIVE) \
|
546
1308
|
== RangeExtd::ALL # => true
|
547
1309
|
|
548
|
-
|
1310
|
+
+RangeExtd+ のインスタンスを作成する方法が3通りあります(おそらく
|
1311
|
+
最初のやり方が最も単純でタイプ量が少なく、かつ覚えやすいでしょう)。
|
549
1312
|
|
550
1313
|
RangeExtd(range, [exclude_begin=false, [exclude_end=false]], opts)
|
551
1314
|
RangeExtd(obj_begin, obj_end, [exclude_begin=false, [exclude_end=false]], opts)
|
@@ -553,7 +1316,7 @@ https://rubygems.org/gems/rangesmaller
|
|
553
1316
|
|
554
1317
|
大括弧の中の二つのパラメーターが、それぞれ始点と終点とを除外する(true)、または含む
|
555
1318
|
(false)を指示します。もし、その二つのパラメーターが最初のパラメーターのレンジ
|
556
|
-
(Range
|
1319
|
+
(+Range+ または +RangeExtd+) と矛盾する場合は、ここで与えた二つのパラメーターが優先され
|
557
1320
|
ます。同じパラメーターをオプションHash
|
558
1321
|
(<tt>:exclude_begin</tt> と <tt>:exclude_end</tt>)で指定することもできて、
|
559
1322
|
もし指定されればそれらが最高の優先度を持ちます。
|
@@ -567,36 +1330,53 @@ https://rubygems.org/gems/rangesmaller
|
|
567
1330
|
|
568
1331
|
=== 少し上級編
|
569
1332
|
|
570
|
-
(
|
1333
|
+
RangeExtd((0..), true).each do |i|
|
571
1334
|
print i
|
572
1335
|
break if i >= 9
|
573
|
-
end # => self
|
574
|
-
|
1336
|
+
end # => self; "123456789" => STDOUT
|
1337
|
+
# *NOT* "012..."
|
1338
|
+
(nil..nil).valid? # => true
|
575
1339
|
(1...1).valid? # => false
|
576
1340
|
(1...1).null? # => true
|
577
1341
|
RangeExtd.valid?(1...1) # => false
|
578
1342
|
RangeExtd(1, 1, true, true).valid? # => true
|
579
1343
|
RangeExtd(1, 1, true, true).empty? # => true
|
580
1344
|
RangeExtd(?a, ?b, true, true).to_a? # => []
|
581
|
-
RangeExtd(?a, ?b, true, true).
|
1345
|
+
RangeExtd(?a, ?b, true, true).null? # => true (empty? is same in this case)
|
582
1346
|
RangeExtd(?a, ?e, true, true).to_a? # => ["b", "c", "d"]
|
583
|
-
RangeExtd(?a, ?e, true, true).
|
1347
|
+
RangeExtd(?a, ?e, true, true).null? # => false
|
584
1348
|
RangeExtd::NONE.is_none? # => true
|
1349
|
+
RangeExtd(1...1, true) == RangeExtd::NONE # => true
|
585
1350
|
RangeExtd::ALL.is_all? # => true
|
1351
|
+
(nil..nil).is_all? # => false
|
1352
|
+
(-Float::INFINITY..Float::INFINITY).is_all? # => false
|
1353
|
+
(nil..nil).equiv_all? # => true
|
1354
|
+
(-Float::INFINITY..Float::INFINITY).equiv_all? # => true
|
586
1355
|
(3...7).equiv?(3..6) # => true
|
1356
|
+
(nil..nil).equiv?(RangeExtd::ALL) # => true
|
587
1357
|
|
588
|
-
組込Range
|
1358
|
+
組込Rangeに含まれる全てのメソッドが、(子クラスである){RangeExtd}で使用可能です。
|
589
1359
|
|
590
1360
|
|
591
1361
|
== 詳説
|
592
1362
|
|
593
|
-
ファイル +range_extd
|
1363
|
+
ファイル +range_extd.rb+ が読まれた段階で、次の3つのクラスが定義されます。
|
594
1364
|
|
595
1365
|
* RangeExtd
|
596
1366
|
* RangeExtd::Infinity
|
1367
|
+
* RangeExtd::Nowhere
|
1368
|
+
|
1369
|
+
加えて、{Range} クラスと {NilClass}に数個のメソッドが追加また改訂されます。
|
1370
|
+
これらに加えられる改訂は、全て後方互換性を保っています。
|
597
1371
|
|
598
|
-
|
599
|
-
|
1372
|
+
この時、{Range} の改訂は、原理的には{RangeExtd}と分離可能だと思います
|
1373
|
+
(分離したい人がいるとは思えませんが!)が、{NilClass} の方は不可避です。
|
1374
|
+
というのも、それなしには{RangeExtd::NONE}が定義不可能だからです。
|
1375
|
+
具体的には、初期化の時に+ArgumentError+ (bad value for range)
|
1376
|
+
の例外が出てしまいます。Rubyの組込みのRangeの仕様のためです。
|
1377
|
+
|
1378
|
+
{Stackoverflow上の議論}[https://stackoverflow.com/a/14449380/3577922]
|
1379
|
+
を参考にあげておきます。
|
600
1380
|
|
601
1381
|
=== RangeExtd::Infinity クラス
|
602
1382
|
|
@@ -606,42 +1386,54 @@ https://rubygems.org/gems/rangesmaller
|
|
606
1386
|
* RangeExtd::Infinity::POSITIVE
|
607
1387
|
|
608
1388
|
これらは、 <tt>Float::INFINITY</tt> を全ての Comparable なオブジェクトに一般化し
|
609
|
-
たものです。メソッド <tt><=></tt
|
1389
|
+
たものです。メソッド <tt><=></tt>が定義されています。
|
610
1390
|
|
611
1391
|
これらは、他のオブジェクトと同様に普通に使用可能です。たとえば、
|
612
|
-
|
1392
|
+
|
1393
|
+
(RangeExtd::Infinity::NEGATIVE.."k")
|
1394
|
+
|
613
1395
|
とはいえ、他には何もメソッドを持っていないため、 Range型のクラスの中以外での使用
|
614
1396
|
はおそらく意味がないでしょう。
|
615
1397
|
|
616
1398
|
なお、Numericのオブジェクトに対しては、原則として <tt>Float::INFINITY</tt> の方
|
617
1399
|
を使って下さい。
|
618
1400
|
|
619
|
-
ユーザー定義のどの Comparable
|
620
|
-
|
621
|
-
|
1401
|
+
ユーザー定義のどの Comparable なクラスに属するどのオブジェクトも、比較
|
1402
|
+
演算子が*標準的な方法で*実装されているという条件付きで、これら二定数と
|
1403
|
+
可換的に比較可能です。「標準的」とは自分の知らないオブジェクトと比較す
|
1404
|
+
る際には、上位クラス、究極的には+Object+クラスに判断を委譲する、という
|
1405
|
+
意味です。
|
622
1406
|
|
623
1407
|
さらに詳しくは、マニュアルを参照して下さい(YARD または RDoc形式で書かれた文書が
|
624
1408
|
コード内部に埋込まれていますし、{RubyGemsのウェブサイト}[http://rubygems.org/gems/range_extd]でも閲覧できます。
|
625
1409
|
|
626
|
-
|
627
|
-
|
628
|
-
{
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
|
1410
|
+
=== RangeExtd::Nowhere クラス
|
1411
|
+
|
1412
|
+
{RangeExtd::Nowhere} は{NilClass}のように振舞うシングルトンクラスです。
|
1413
|
+
唯一のインスタンスが
|
1414
|
+
|
1415
|
+
* RangeExtd::Nowhere::NOWHERE
|
1416
|
+
|
1417
|
+
として定義されています。このインスタンスは、たとえば +nil?+ に真を返し、
|
1418
|
+
また+nil+ と同じ object-ID を +object_id+ で返し、nil と等しい(+==+)
|
1419
|
+
です。これは、{RangeExtd::NONE} を構成するために使われます。
|
633
1420
|
|
1421
|
+
なお、Rubyの条件文では、このインスタンスは真(true)であり、偽(false)
|
1422
|
+
ではありません。
|
1423
|
+
|
1424
|
+
また、{RangeExtd::NONE} を除き、{RangeExtd::Nowhere::NOWHERE} を含む
|
1425
|
+
Range は、"valid"では*ない*と判断されます(後述)。
|
634
1426
|
|
635
1427
|
=== RangeExtd クラス
|
636
1428
|
|
637
|
-
RangeExtd のインスタンスは、 Range
|
638
|
-
|
1429
|
+
{RangeExtd} のインスタンスは、 {Range}と同じくイミュータブルです。だから、一度
|
1430
|
+
インスタンスが生成されると、変化しません。
|
639
1431
|
|
640
|
-
インスタンスの生成方法は上述の通りです(「使用例」の章)。レンジとして"valid"(後述)
|
641
|
-
|
1432
|
+
インスタンスの生成方法は上述の通りです(「使用例」の章)。レンジとして"valid"(後述)と
|
1433
|
+
見なされない{RangeExtd} インスタンスを生成しようとすると、例外(<tt>ArgumentError</tt>)が発生し、
|
642
1434
|
失敗します。
|
643
1435
|
|
644
|
-
|
1436
|
+
このクラスには、2つの定数が定義されています。
|
645
1437
|
|
646
1438
|
* RangeExtd::NONE
|
647
1439
|
* RangeExtd::ALL
|
@@ -656,7 +1448,7 @@ RangeExtd のインスタンスは、 Rangeと同じくイミュータブルで
|
|
656
1448
|
* <tt>valid?</tt>
|
657
1449
|
* <tt>empty?</tt>
|
658
1450
|
* <tt>null?</tt>
|
659
|
-
* <tt>is_none?</tt>
|
1451
|
+
* <tt>is_none?</tt>
|
660
1452
|
* <tt>is_all?</tt>
|
661
1453
|
* <tt>equiv?</tt>
|
662
1454
|
|
@@ -667,24 +1459,37 @@ RangeExtd のインスタンスは、 Rangeと同じくイミュータブルで
|
|
667
1459
|
* <tt>RangeExtd.middle_strings=(ary)</tt>
|
668
1460
|
* <tt>RangeExtd.middle_strings</tt>
|
669
1461
|
|
670
|
-
|
1462
|
+
==== 正当性、空かどうか、ヌルかどうかについての詳説
|
1463
|
+
|
1464
|
+
何がレンジとして正当または有効 (<tt>#valid?</tt> => true) かの定義は以下です。
|
671
1465
|
|
672
1466
|
1. 始点と終点とが互いに Comparable であり、かつその比較結果に矛盾がないこと。
|
673
|
-
|
674
|
-
|
1467
|
+
この例外が3つあって、{RangeExtd::NONE}、(Ruby-2.7/2.6で導入された)Beginless/Endless Ranges で、
|
1468
|
+
これらはすべて valid です。
|
1469
|
+
たとえば、<tt>(nil..nil)</tt> は{RangeExtd} Ver.2.0+では valid です(参考までに、この例は
|
675
1470
|
Ruby 1.8 では例外を生じていました)。
|
676
|
-
2.
|
1471
|
+
2. {RangeExtd::NONE} と Beginless Rangeを除き +Range#begin+ のオブジェクトはメソッド +<=+
|
1472
|
+
を持たなければなりません。ゆえに、+(true..)+のようなEndless Ranges
|
1473
|
+
(Ruby 2.6以上)はvalidでは*ありません*。
|
1474
|
+
なお、"+true+" もメソッド +<=>+ を持っているため、+<=+ メソッドによる確認が不可欠です。
|
1475
|
+
3. 同様に、{RangeExtd::NONE} と Endless Rangeを除き +Range#end+ のオブジェクトはメソッド +<=+
|
1476
|
+
を持たなければなりません。ゆえに、+(..true)+のようなBeginless Ranges
|
1477
|
+
(Ruby 2.7以上)はvalidでは*ありません*。
|
1478
|
+
4. 始点は終点と等しい(<tt>==</tt>)か小さくなければなりません。すなわち、
|
677
1479
|
<tt>(begin <=> end)</tt> は、-1 または 0 を返すこと。
|
678
|
-
|
1480
|
+
5. もし始点と終点とが等しい時、すなわち <tt>(begin <=> end) == 0</tt>ならば、
|
679
1481
|
端を除外するかどうかのフラグは両端で一致していなければなりません。
|
680
1482
|
すなわち、もし始点が除外ならば、終点も除外されていなくてはならず、逆も真です。
|
681
1483
|
その一例として、 <tt>(1...1)</tt> は、"valid" では「ありません」。なぜならば
|
682
1484
|
組込レンジでは、始点を常に含むからです。
|
1485
|
+
+RangeExtd(1...1, true)+ は validで、{RangeExtd::NONE}と等しい(<tt>==</tt>)です。
|
1486
|
+
6. {RangeExtd::NONE} 以外で{RangeExtd::Nowhere::NOWHERE} を含むRange
|
1487
|
+
は、validでは*ありません*。
|
683
1488
|
|
684
1489
|
さらなる詳細は {RangeExtd.valid?} と {Range#valid?} のマニュアルを
|
685
1490
|
参照して下さい。
|
686
1491
|
|
687
|
-
何がレンジとして空(
|
1492
|
+
何がレンジとして空({Range#empty?} == +true+)かの定義は以下の通りです。
|
688
1493
|
|
689
1494
|
1. レンジは、valid であること: <tt>valid?</tt> => true
|
690
1495
|
2. もしレンジの要素が離散的であれば、すなわち始点の要素がメソッド <tt>succ</tt>
|
@@ -705,21 +1510,35 @@ RangeExtd のインスタンスは、 Rangeと同じくイミュータブルで
|
|
705
1510
|
|
706
1511
|
最後、 {Range#null?} は、「<tt>empty?</tt> または "valid"でない」ことに等
|
707
1512
|
価です。従って、 RangeExtd オブジェクトにとっては、<tt>null?</tt> は
|
708
|
-
<tt>empty?</tt>
|
1513
|
+
<tt>empty?</tt> に等価です。実用的には、ほとんどのケースにおいて、
|
1514
|
+
{Range#null?} の方が、{Range#empty?}よりも有用でしょう。
|
709
1515
|
|
710
1516
|
RangeExtd と別の RangeExtd または Rangeの比較 (<tt><=></tt>) においては、これら
|
711
1517
|
の定義が考慮されます。そのうちの幾つかは、上の「使用例」の項に示されています。
|
712
|
-
さらなる詳細は {Range
|
713
|
-
<tt>#eql?</tt> のマニュアルを参照して下さい。
|
1518
|
+
さらなる詳細は {Range#<=>}、{RangeExtd#<=>} のマニュアルを参照して下さい。
|
714
1519
|
|
715
1520
|
なお、処理が Rangeオブジェクト内部で閉じている限り、その振舞いは標準 Rubyと同一
|
716
1521
|
で、互換性を保っています。したがって、このライブラリを読込むことで既存のコードに
|
717
1522
|
影響を与えることは原理的にないはずです。
|
718
1523
|
|
1524
|
+
==== 等価性
|
1525
|
+
|
1526
|
+
メソッド +eql?+ は、Ruby標準ではハッシュ値を比較して等価性を判断するため、
|
1527
|
+
基本的にオブジェクトのすべてのパラメーターが一致する必要があります。
|
1528
|
+
一方、 <tt>==</tt> はもっと大雑把な比較を行います。以下が一例。
|
1529
|
+
|
1530
|
+
RaE(0...0, true) == RaE(?a...?a, true) # => false
|
1531
|
+
RaE(0...1, true) == RaE(5...6, true) # => true
|
1532
|
+
|
719
1533
|
|
720
1534
|
== 既知のバグ
|
721
1535
|
|
722
|
-
*
|
1536
|
+
* {RangeExtd::Nowhere::NOWHERE} は、{RangeExtd} の文脈では使えません
|
1537
|
+
(なぜならば{Range#valid?}が偽を返す)が、ユーザーは、Ruby組込み
|
1538
|
+
{Range}の枠組み内だけで用いることは以前可能です。
|
1539
|
+
{RangeExtd::Nowhere::NOWHERE} をnil以外の値として再定義した方が良いかも?
|
1540
|
+
* このライブラリ Version 2+ は Ruby 2.6 およびそれ以前のバージョンでは動作しません。
|
1541
|
+
* このライブラリ Version 1は Ruby 1.8 およびそれ以前のバージョンでは動作しません。
|
723
1542
|
Ruby 1.9.3 ではおそらく大丈夫でしょうが、私は試したことがありません。
|
724
1543
|
* いくつかの極めて稀な境界条件に於ける挙動は、Rubyのバージョンごとにあ
|
725
1544
|
る程度変化しています。例えば、Float::INFINITY 同士の比較などの挙動が
|
@@ -729,35 +1548,94 @@ RangeExtd と別の RangeExtd または Rangeの比較 (<tt><=></tt>) におい
|
|
729
1548
|
* {RangeExtd#hash} メソッドは、ある RangeExtdオブジェに対して常に唯一で排他的な
|
730
1549
|
数値を返すことが理論保証はされていません。ただし、現実的にそれが破られることは、まず
|
731
1550
|
ありません。
|
1551
|
+
* +RangeExtd::NONE.inspect+ と +RangeExtd::NONE.to_s+ はいずれも "Null<...Null"
|
1552
|
+
を返すのだが、Ruby +irb+ では "nil...nil" と表示されてしまうために、
|
1553
|
+
とても紛らわしい……。
|
732
1554
|
|
733
1555
|
パッケージに含まれている通り、網羅的なテストが実行されています。
|
734
1556
|
|
735
1557
|
|
736
1558
|
== 開発項目
|
737
1559
|
|
738
|
-
|
1560
|
+
* もし {RangeExtd::Infinity::POSITIVE} (と NEGATIVE) が
|
1561
|
+
({RangeExtd::Nowhere::NOWHERE}が振舞うように)+nil+のように振る舞えば、
|
1562
|
+
便利かも知れない。ただし、そのようなオブジェクトを含むRangeは、
|
1563
|
+
Stringクラスに対してはたとえば<tt>"abcde"[my_nil..]</tt>などで、
|
1564
|
+
同じようには動かない。Stringクラスは、+nil+について何か厳密なチェックを行っている
|
1565
|
+
のだろう。だから、仮にそうデザインし直しても、Ruby組込みクラスとの
|
1566
|
+
相性という意味では、使い勝手がずっと向上するということにはなりそうもない。
|
1567
|
+
* "+similar+" というようなメソッドを定義すれば有用かもしれない。たとえば、
|
1568
|
+
+(-Float::INFINITY..Float::INFINITY)+ と +(-Float::INFINITYnil...Float::INFINITY)+
|
1569
|
+
とは、無限大(無限小)を除外することが無意味であるから、数学的に完全に同一である。
|
1570
|
+
実際、これらと無限大を含まないRange/Rangearyとの演算の結果には何も影響を
|
1571
|
+
及ぼすことがない。
|
739
1572
|
|
740
1573
|
|
741
1574
|
== 履歴メモ
|
742
1575
|
|
743
1576
|
* <tt>((?a..?z) === "cc")</tt> は、Ruby 2.6.x 以前は false を返していたが、2.7 以降は true を返す。
|
1577
|
+
* <tt>(Float::INFINITY..Float::INFINITY).size</tt> は以前は 0を返して
|
1578
|
+
いた(少なくともRuby-2.1)が、少なくともRuby-2.6以降(Ruby 3含む)では、例外 +FloatDomainError: NaN+
|
1579
|
+
を発生する。どのバージョンで変化したのかは私は知らない。
|
1580
|
+
|
1581
|
+
=== RangeExtd Ver.2
|
1582
|
+
|
1583
|
+
* {RangeExtd} Ver.2において、Ver.1から、ライブラリのパスがディレクトリ
|
1584
|
+
の階層一つ上がった。Ruby Gems の慣用にそうため。
|
1585
|
+
* Ruby-2.7で導入されたBeginless Rangeに対応。
|
1586
|
+
* +RangeExtd::Infinity#succ+ は未定義になった。Floatに合わせた。
|
1587
|
+
* +Object+ と +Numeric+ クラスの拡張はデフォルトではなく、オプション化
|
1588
|
+
* +RangeExtd#eql?+ は、Ruby標準(ハッシュ値[#hash]比較)にそうように未定化。{RangeExtd::NONE}を特別扱いすることを廃止。
|
1589
|
+
* +RangeExtd#min_by+ (+max_by+ と +minmax_by+)のバグ修正。
|
1590
|
+
|
1591
|
+
=== RangeExtd Ver.1.1
|
1592
|
+
|
1593
|
+
{RangeExtd} Ver.1.1 の時点で、the +RangeExtd::Infinity+ クラスの
|
1594
|
+
インスタンスは +Float::INFINITY+ とは比較できない。
|
1595
|
+
|
1596
|
+
RangeExtd::Infinity::POSITIVE != Float::INFINITY # => true
|
1597
|
+
|
1598
|
+
概念として、前者は後者よりもさらに一般化された概念であるから、*等しく*
|
1599
|
+
あるべきでない。詳しくは {RangeExtd::Infinity} マニュアル参照。
|
1600
|
+
Ruby 2.6以上のEndless Range の振舞いは、以下のように一部奇妙に感じるところがある。
|
1601
|
+
|
1602
|
+
num1 = (5..Float::INFINITY)
|
1603
|
+
num2 = (5..)
|
1604
|
+
num1.end != num2.end # => true
|
1605
|
+
num1.size # => Infinity
|
1606
|
+
num2.size # => Infinity
|
1607
|
+
|
1608
|
+
str1 = (?a..)
|
1609
|
+
str1.end == num2.end # => true (because both are nil)
|
1610
|
+
str1.size # => nil
|
1611
|
+
|
1612
|
+
=== RangeExtd Ver.1.0
|
1613
|
+
|
1614
|
+
**(注)** +RangeExtd::Infinity::POSITIVE+ は、
|
1615
|
+
2018年12月に公式リリースされたRuby 2.6で導入された
|
1616
|
+
{Endless Range}[https://rubyreferences.github.io/rubychanges/2.6.html#endless-range-1]
|
1617
|
+
(終端のないRange)で実用上同一です!! 言葉を替えれば、公式Rubyがついに本
|
1618
|
+
ライブラリの一部をサポートしました! ただし、公式Rubyには、
|
1619
|
+
+RangeExtd::Infinity::NEGATIVE+ は依然ありません(始端のないRangeがない)。
|
1620
|
+
|
744
1621
|
|
745
1622
|
== 終わりに
|
746
1623
|
|
747
1624
|
RangeExtd内部に閉じた(Rangeでなく)挙動、たとえば RangeExtd同士の比較などは、
|
748
|
-
全てユーザーにとって自然なもののはずです(と期待します?)。少なくとも、RangeExtdに
|
1625
|
+
全てユーザーにとって自然なもののはずです(と期待します?)。少なくとも、{RangeExtd}に
|
749
1626
|
よってレンジの論理構造が完結した今、これはよく定義されかつ自己矛盾が無いものと言
|
750
|
-
|
751
|
-
|
752
|
-
|
753
|
-
|
754
|
-
|
755
|
-
|
756
|
-
|
757
|
-
|
758
|
-
|
759
|
-
|
760
|
-
|
1627
|
+
えましょう。
|
1628
|
+
|
1629
|
+
以前の版のこの章では、以下のように記述していました。
|
1630
|
+
|
1631
|
+
> ただ、端の無限に開いた、あるいは始点が除外されたレンジの挙動には、一瞬ぎょっとするものが無くはないかも知れないことに注意して下さい。たとえば、片端が小さい方向に無限に開いて離散的な要素を持つレンジに対してメソッド<tt>member?(obj)</tt> を実行すると、 <tt>nil</tt>が返ります。これは、無限(小)には実質的な意味を持つ <tt>succ()</tt> メソッドが定義されていないためで、したがって与えられた objがレンジの要素(member)かどうかを調べることが、一般論としては理論的に不可能だからです。これはちょっと不思議に思うかも知れませんが、それはつまり定命の私たちには無限という概念を計り知るのが容易でない、というだけの話でしょう!
|
1632
|
+
|
1633
|
+
ところが今や、Ruby本家に"beginless Range"組込まれたことで、すべての
|
1634
|
+
Rubyプログラマーがこの概念に親しむことになりました。
|
1635
|
+
これは進化と呼びたいです。
|
1636
|
+
|
1637
|
+
とはいえ、RangeExtd と Range との比較は、時には驚きがあるかも知れません。
|
1638
|
+
これは、組込Rangeクラスで許容されているレンジの一部は、始点を除外することを認めた
|
761
1639
|
枠組の中では、前述のように最早有効(valid)と見なされないからです。この枠組に慣れるに
|
762
1640
|
したがって、それらが自然だと思えるようになればいいのですが。保証しますが、一旦こ
|
763
1641
|
れに慣れてしまえば、論理的不完全さ極まる混沌とした世界、つまりは Rangeの現在の挙
|
@@ -766,8 +1644,6 @@ RangeExtd内部に閉じた(Rangeでなく)挙動、たとえば RangeExtd同士
|
|
766
1644
|
お楽しみ下さい。
|
767
1645
|
|
768
1646
|
|
769
|
-
== その他
|
770
|
-
|
771
1647
|
== 著作権他情報
|
772
1648
|
|
773
1649
|
著者:: Masa Sakano < info a_t wisebabel dot com >
|