rangeary 1.0.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 +18 -0
- data/Makefile +2 -1
- data/News +4 -0
- data/README.ja.rdoc +461 -260
- data/lib/rangeary/util/hash_inf.rb +233 -0
- data/lib/rangeary/util.rb +727 -0
- data/lib/rangeary.rb +1420 -0
- data/rangeary.gemspec +51 -0
- data/test/tee_io.rb +111 -0
- data/test/test_rangeary.rb +400 -79
- metadata +25 -19
- data/lib/rangeary/rangeary.rb +0 -1643
data/lib/rangeary.rb
ADDED
@@ -0,0 +1,1420 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'range_extd/load_all'
|
4
|
+
|
5
|
+
## Load required files in this library.
|
6
|
+
req_files = %w(rangeary/util)
|
7
|
+
req_files.each do |req_file|
|
8
|
+
begin
|
9
|
+
require_relative req_file
|
10
|
+
rescue LoadError
|
11
|
+
require req_file
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
if $DEBUG
|
16
|
+
req_files.push 'range_extd/load_all'
|
17
|
+
puts "DEBUG(#{File.basename(__FILE__)}): Library full paths:"
|
18
|
+
req_files.each do |elibbase|
|
19
|
+
ar = $LOADED_FEATURES.grep(/(^|\/)#{Regexp.quote(File.basename(elibbase))}(\.rb)?$/).uniq
|
20
|
+
print elibbase+": " if ar.empty?; p ar
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def Rangeary(*rest, **hs)
|
25
|
+
# Note: This form (Rangeary(...)) is NOT used inside this file for the sake of searchability.
|
26
|
+
# The original "Rangeary.new" is used throughout.
|
27
|
+
Rangeary.new(*rest, **hs)
|
28
|
+
end
|
29
|
+
|
30
|
+
# =Class Rangeary < Array
|
31
|
+
#
|
32
|
+
# Authors:: Masa Sakano
|
33
|
+
# License:: MIT
|
34
|
+
#
|
35
|
+
# ==Summary
|
36
|
+
#
|
37
|
+
# Class to express the multiple ranges.
|
38
|
+
#
|
39
|
+
# The library package +range_extd+ is required.
|
40
|
+
# {https://rubygems.org/gems/range_extd}
|
41
|
+
#
|
42
|
+
# An arbitrary number of {Rangeary}, RangeExtd or Range objects (or its
|
43
|
+
# descendant classes) can be supplied for the constructor {#initialize}.
|
44
|
+
# Then, an operation of disjunction is performed to get the clean Array of
|
45
|
+
# RangeExtd, none of which overlaps with one another.
|
46
|
+
#
|
47
|
+
# Once it is constructed, {Rangeary} objects are immutable. Any subsequent
|
48
|
+
# operation returns a new {Rangeary} object. All but few methods of {Array}
|
49
|
+
# are inherited, though some methods, such as +push+ do not work because of
|
50
|
+
# immutability of {Rangeary} objects.
|
51
|
+
#
|
52
|
+
# == Description
|
53
|
+
#
|
54
|
+
# The infinities are vital in the logical operation of Rangeary.
|
55
|
+
# Without it, negation could not be definied, and other logical
|
56
|
+
# operations are also closely related to it; for example, *subtraction*
|
57
|
+
# is basically a combination of *negation* and *conjunction*.
|
58
|
+
#
|
59
|
+
# To determine what the positive and negative infinities for the given
|
60
|
+
# elements is not a trivial task. In default, +nil+ is used except for
|
61
|
+
# +Numerics+ (Integer, Rational, Float etc), for which +Float::INFINITY+
|
62
|
+
# is used. Note that the default used to be
|
63
|
+
# <tt>RangeExtd::Infinity::POSITIVE</tt> and <tt>RangeExtd::Infinity::NEGATIVE</tt>
|
64
|
+
# defined in {RangeExtd}[http://rubygems.org/gems/range_extd]
|
65
|
+
# up to Rangeary Ver.1, where both beginless and endless Ranges were not
|
66
|
+
# been introduced or supported. Rangeary Ver.2 changes the specification
|
67
|
+
# to be in line with the latest Ruby Range.
|
68
|
+
#
|
69
|
+
# Alternatively, a user can specify their own infinities in
|
70
|
+
# initialization of {Rangeary} with options of +positive:+ and
|
71
|
+
# +negative:+. The boundaries at the opposite polarities usually should
|
72
|
+
# match unless they are comparable or either of them is +nil+.
|
73
|
+
#
|
74
|
+
# Otherwise, ArgumentError might be
|
75
|
+
# issued if they contradict the elements; for example, if a {Rangeary}
|
76
|
+
# instance consists of an array of Integer Ranges (RangeExtd) like (3..8),
|
77
|
+
# and yet if String "abc" is specified as an infinity, it *contradicts*
|
78
|
+
# with the elements in the sense they are not comparable.
|
79
|
+
#
|
80
|
+
# Here are examples of how infinities work with {Rangeary}. In the first
|
81
|
+
# example, infinities are implicitly contained in the specified Range.
|
82
|
+
# Then, the infinities are internally preserved throughout operations.
|
83
|
+
# Note that the first set of 2 operations and the second set of a single operation
|
84
|
+
# means the same.
|
85
|
+
#
|
86
|
+
# r1 = Rangeary(nil..Float::INFINITY).conjunction( RangeExtd::NONE )
|
87
|
+
# # => Rangeary(RangeExtd::NONE)
|
88
|
+
# r2 = r1.negation
|
89
|
+
# # => Rangeary(nil..Float::INFINITY)
|
90
|
+
#
|
91
|
+
# ~(Rangeary(nil..Float::INFINITY) * RangeExtd::NONE)
|
92
|
+
# # => Rangeary(nil..Float::INFINITY)
|
93
|
+
#
|
94
|
+
# In the second example below, a negative infinity of "+d+" is explicitly specified
|
95
|
+
# for a Range of single alphabet String.
|
96
|
+
#
|
97
|
+
# Rangeary("f".."k", negative: "d").negation
|
98
|
+
# # => Rangeary("d"..."f", "k"<..nil)
|
99
|
+
#
|
100
|
+
# where +"k"<..nil+ means a begin-exclude Range or +RangeExtd("k"..nil, true)+.
|
101
|
+
#
|
102
|
+
#
|
103
|
+
# === Algorithm of determining default infinities
|
104
|
+
#
|
105
|
+
# Callers can supply user-defined infinity objects for both or either
|
106
|
+
# positive and negative infinity and in that case they are accepted
|
107
|
+
# as the infinities with the highest priority, though ArgumentError might be
|
108
|
+
# issued if they contradict the elements; for example, if a {Rangeary}
|
109
|
+
# instance consists of an array of Integer Ranges (RangeExtd) like +(3..8)+,
|
110
|
+
# and yet if String "abc" is specified as an infinity, it *contradicts*
|
111
|
+
# the elements in the sense they are not comparable.
|
112
|
+
#
|
113
|
+
# Internally, the {Rangeary} instance has a Hash extended with {Rangeary::Util::HashInf},
|
114
|
+
# which can be obtained with {Rangeary#infinities}.
|
115
|
+
# It has only 2 keys of +:negative+ and +:positive+, the values of which
|
116
|
+
# are the current best-guessed or definite infinities. The Hash also
|
117
|
+
# holds status information for each polarity with 3 levels of
|
118
|
+
#
|
119
|
+
# 1. <tt>false</tt>
|
120
|
+
# 2. <tt>:guessed</tt>
|
121
|
+
# 3. <tt>:definite</tt>
|
122
|
+
#
|
123
|
+
# It is +false+ only when the Rangeary is absolutely void with no
|
124
|
+
# information about the contents: +Rangeary(RangeExtd::NONE)+.
|
125
|
+
#
|
126
|
+
# If the user explicitly specifies a boundary in the optional arguments in
|
127
|
+
# initialization of {Rangeary}, it is accepted in principle with an associated status of <tt>:definite</tt>.
|
128
|
+
#
|
129
|
+
# If the user-specified main arguments in initialization contain
|
130
|
+
# a (potentially multiple) {Rangeary}, their defined infinities are
|
131
|
+
# inherited with their associated statuses.
|
132
|
+
#
|
133
|
+
# Also, user-supplied Range-s or RangeExtd-s to the arguments in
|
134
|
+
# initialization of {Rangeary} always have, except for
|
135
|
+
# +RangeExtd::NONE+, concrete boundary values, which can be +nil+.
|
136
|
+
#
|
137
|
+
# If one of the boundaries of a Range (n.b., it is *not* Rangeary) contains either +nil+ or one of infinite values
|
138
|
+
# (which is checked with +RangeExtd::Infinity.infinite?+, where in practice
|
139
|
+
# a duck-typing check is performed, using the method +infinite?+), then
|
140
|
+
# it is accepted as an infinite value with an associated status of <tt>:definite</tt>.
|
141
|
+
#
|
142
|
+
# Otherwise,
|
143
|
+
#
|
144
|
+
# 1. if a boundary value is a (real-type) Numeric, +Float::INFINITY+ (or
|
145
|
+
# its negative,
|
146
|
+
# 2. or otherwise, +nil+
|
147
|
+
#
|
148
|
+
# is set as an infinity of the boundary with an associated status of +:guessed+.
|
149
|
+
#
|
150
|
+
# Note that the priority used to be different up to Rangeary Ver.1; +nil+ was not used and
|
151
|
+
# instead the +RangeExtd::Infinity+ objects were used. It was because
|
152
|
+
# the beginless Range (and endless Range before Ruby-2.6) has not been defined before
|
153
|
+
# Ruby 2.7. Now they are defined, it is only natural to use +nil+ as the
|
154
|
+
# default infinities in both ends, hence the change in specification in
|
155
|
+
# {Rangeary} Ver.2.
|
156
|
+
#
|
157
|
+
# Usually the arguments given in initialization of a {Rangeary} contain
|
158
|
+
# more than one set of infinities candidate, unless only a single
|
159
|
+
# argument of either Range (or its subclass instance) with no optional
|
160
|
+
# arguments is given. The priority is judged in the following order:
|
161
|
+
#
|
162
|
+
# 1. the optional arguments
|
163
|
+
# 2. an associated status of <tt>:definite</tt>, <tt>:guessed</tt>, and
|
164
|
+
# <tt>false</tt> in this order
|
165
|
+
#
|
166
|
+
# If the associated statuses are equal for two or more inputs, the most
|
167
|
+
# extreme one among them for each polarity is chosen. For example, suppose
|
168
|
+
# two instances of {Rangeary} are given in initialization of another
|
169
|
+
# {Rangeary} and their negative infinities are "+b+" and "+c+". Then,
|
170
|
+
# because of
|
171
|
+
#
|
172
|
+
# "b" < "c"
|
173
|
+
#
|
174
|
+
# the former ("+b+") is adopted as the new negative infinity. Note that
|
175
|
+
# the parameters given in the optional arguments have always higher
|
176
|
+
# priority regardless.
|
177
|
+
#
|
178
|
+
#
|
179
|
+
# The following examples demonstrate the specification.
|
180
|
+
#
|
181
|
+
# Rangeary(7..).negation
|
182
|
+
# # => Rangeary(-Float::INFINITY...7)
|
183
|
+
# Rangeary(7..).negation.negation
|
184
|
+
# # => Rangeary(7..)
|
185
|
+
#
|
186
|
+
# Remember the default infinity for Float is +Float::INFINITY+. In this
|
187
|
+
# case, however, the positive infinity was in practice specified by the
|
188
|
+
# user to be +nil+ in the form of argument of +(7..)+ If you want to
|
189
|
+
# specify the negative infinity instead, you must do it explicitly:
|
190
|
+
#
|
191
|
+
# Rangeary(7.., negative: nil).negation
|
192
|
+
# # => Rangeary(...7)
|
193
|
+
#
|
194
|
+
# Alternatively, you can always use cojunction like (the following two mean the same):
|
195
|
+
#
|
196
|
+
# Rangeary(..nil).conjunction(Rangeary(7..)).negation
|
197
|
+
# # => Rangeary(...7)
|
198
|
+
# ~(Rangeary(..nil) * Rangeary(7..))
|
199
|
+
# # => Rangeary(...7)
|
200
|
+
#
|
201
|
+
# The registered infinities for each instance is obtained (Hash extended with
|
202
|
+
# HashInf), which has
|
203
|
+
# two keys of +:positive+ and +negative+, with the method {#infinities};
|
204
|
+
# for example,
|
205
|
+
#
|
206
|
+
# ran.infinities
|
207
|
+
# # => <Hash(Inf): {:negative=>"a", :positive=>nil},
|
208
|
+
# # status: {:negative=>:definite, :positive=>:guessed}>
|
209
|
+
#
|
210
|
+
# Note that the values of the returned Hash (+HashInf) may be +false+;
|
211
|
+
# if it is not convenient, call it as +#instances(convert: true)+
|
212
|
+
# with which +false+ in the returned value, if there is any, is converted
|
213
|
+
# to +nil+ and the standard Hash as opposed to
|
214
|
+
# Hash extended with {Rangeary::Util::HashInf} is returned:
|
215
|
+
#
|
216
|
+
# ran.infinities(convert: true) # => { :negative => "a"
|
217
|
+
# # :positive => nil, }
|
218
|
+
#
|
219
|
+
class Rangeary < Array
|
220
|
+
undef_method :*, :+, :length, :reverse
|
221
|
+
|
222
|
+
# Use Module both for class and instance methods
|
223
|
+
extend Rangeary::Util
|
224
|
+
include Rangeary::Util
|
225
|
+
|
226
|
+
# # Hash with the keys of :negative and :positive => becomes a method in Ver.1
|
227
|
+
# attr_reader :infinities
|
228
|
+
|
229
|
+
# Constructor
|
230
|
+
#
|
231
|
+
# Arbitrary (positive) number of arguments of {Rangeary} or Range or its subclasses can be given.
|
232
|
+
# +(r1, [r2, ...])+
|
233
|
+
#
|
234
|
+
# === Note for Developers about the infinities
|
235
|
+
#
|
236
|
+
# Instance variable +@infinities+ (Hash extended with {HashInf}) is defined.
|
237
|
+
# Method {#infinities} returns it.
|
238
|
+
#
|
239
|
+
# Basically it has the positive and negative infinities AND their confidence levels.
|
240
|
+
# For example, a Numeric Range can have three or more types of infinities of
|
241
|
+
# +nil+, +Float::INFINITY+, and +RangeExtd::Infinity+ or something a user defines.
|
242
|
+
# Since it is not trivial to determine the infinities, they are usually implicitly
|
243
|
+
# guessed from Range/RangeExtd and in such cases the confidence level is not high.
|
244
|
+
# See {HashInf} for detail.
|
245
|
+
#
|
246
|
+
# In initialization, you can give a Hash+{Rangeary::Util::HashInf} infinities object directly
|
247
|
+
# at the end of the main arguments instead of the two keyword arguments
|
248
|
+
# (the latter of which have a higher priority if both are specified, but you should
|
249
|
+
# avoid such uses). However, in that case, no other information
|
250
|
+
# about the main argument will be considered and so you should use it
|
251
|
+
# with extreme caution, or don't use it unless you know exactly what you are doing.
|
252
|
+
#
|
253
|
+
# @example Detailed example with explicit infinities, negation(~), disjunction(+), conjunction(*), exclusive disjunction(^)
|
254
|
+
#
|
255
|
+
# ran2 = ("f".."k")
|
256
|
+
# rae1 = RangeExtd("k"..nil, true)
|
257
|
+
# r3 = ~(Rangeary(ran2, negative: "d"))
|
258
|
+
# # => Rangeary("d"..."f", rae1) == Rangeary("d"..."f", "k"<..nil)
|
259
|
+
# r3.infinities[:negative] # => "d"
|
260
|
+
# r3*(..nil) # => r3,
|
261
|
+
#
|
262
|
+
# r4 = Rangeary(r3, negative: "a")
|
263
|
+
# Rangeary(r4, negative: "b").infinities[:negative] # => "b"
|
264
|
+
#
|
265
|
+
# r5 = ~Rangeary(r4)
|
266
|
+
# # => Rangeary("a"..."d", "f".."k")
|
267
|
+
#
|
268
|
+
# r6 = r3 + Rangeary("c".."d", negative: "a") # disjunction
|
269
|
+
# # => Rangeary("c"..."f", "k"<..nil)
|
270
|
+
#
|
271
|
+
# r7 = r3 * Rangeary("c".."d", negative: "a") # conjunction
|
272
|
+
# # => Rangeary("d".."d"), r7
|
273
|
+
# r7.infinities[:negative] # => "a"
|
274
|
+
#
|
275
|
+
# r8 = r3 ^ (?e..?h) # exclusive disjunction (XOR)
|
276
|
+
# # => Rangeary("d"..."e", "f".."h", "k"<..nil)
|
277
|
+
#
|
278
|
+
# @param inarall [Rangeary, Range, RangeExtd, Array<Rangeary, Range>] An arbitrary number of either {Rangeary} or Range (or its subclasses, notably RangeExtd).
|
279
|
+
# @option positive [Object] :positive Object for positive infinity. In default Float::INFINITY for comparable Numeric or else nil. You may specify +RangeExtd::Infinity::POSITIVE+ or else.
|
280
|
+
# @option negative [Object] :negative Object for negative infinity. In default -Float::INFINITY for comparable Numeric or else nil. You may specify +RangeExtd::Infinity::NEGATIVE+ or else.
|
281
|
+
def initialize(*inarall, positive: false, negative: false)
|
282
|
+
if inarall.last.respond_to?(:definite?) && inarall.last.respond_to?(:guessed?)
|
283
|
+
flag_skip_adjut_infinity = true
|
284
|
+
@infinities = inarall.pop # inarall is destructively modified.
|
285
|
+
else
|
286
|
+
@infinities = HashInf.construct()
|
287
|
+
end
|
288
|
+
@infinities.merge_hashinf!({ negative: negative, positive: positive }, force: true)
|
289
|
+
|
290
|
+
if inarall.size < 1
|
291
|
+
super [RangeExtd::NONE] # Since Ver.1
|
292
|
+
return
|
293
|
+
# raise ArgumentError, "wrong number of arguments (#{inarall.size} for 1 or more)."
|
294
|
+
end
|
295
|
+
|
296
|
+
# This uses information of {Rangeary#infinities} in the arguments if there ia any.
|
297
|
+
@infinities = _build_infinities(@infinities, self.class.flatten_no_rangeary(*inarall)) if !flag_skip_adjut_infinity # set @infinities (wherever possible)
|
298
|
+
|
299
|
+
# Unfold Rangeary into Array of Arrays and convert Range-s into RangeExtd-s
|
300
|
+
in_ranges = inarall.map{|i|
|
301
|
+
self.class.send(:is_rangeary_type?, i) ? i.to_a : i
|
302
|
+
}.flatten.map{|j|
|
303
|
+
if (defined? j.exclude_begin?)
|
304
|
+
j
|
305
|
+
else
|
306
|
+
begin
|
307
|
+
RangeExtd.new(j)
|
308
|
+
rescue ArgumentError, RangeError # Just to change the error message.
|
309
|
+
raise ArgumentError, "invalid parameter for RangeExtd, hence for Rangeary (#{j.inspect})."
|
310
|
+
end
|
311
|
+
end
|
312
|
+
}
|
313
|
+
|
314
|
+
# Call _merge_overlaps
|
315
|
+
begin
|
316
|
+
arRange = _merge_overlaps( _convert2range(in_ranges) )
|
317
|
+
rescue => err
|
318
|
+
# Trap just to change the type of the exception.
|
319
|
+
raise ArgumentError, err.message, err.backtrace
|
320
|
+
end
|
321
|
+
|
322
|
+
super(arRange)
|
323
|
+
self.freeze
|
324
|
+
_validate_infinities
|
325
|
+
end # def initialize(*inarall, positive: false, negative: false)
|
326
|
+
|
327
|
+
|
328
|
+
# Validates the given infinities
|
329
|
+
#
|
330
|
+
# Negative should be smaller than any and positive be larger.
|
331
|
+
#
|
332
|
+
# If a new infinity is an infinity (respond_to? infinite?) and has
|
333
|
+
# the right polarity, or if it is nil, it is automatically accepted.
|
334
|
+
# For example, the positive Infinity can be defined as +RangeExtd::Infinity::POSITIVE+
|
335
|
+
# even when the existing +Rangeary#begin+ is +Float::INFINITY+
|
336
|
+
#
|
337
|
+
# Theoretically, the following policy would be possible, but it is not adopted:
|
338
|
+
#
|
339
|
+
# # Allowing to specify Rangeary(..?d, negative: ?a, positive: ?z),
|
340
|
+
# # because nil is basically "undefined" rather than infinite.
|
341
|
+
#
|
342
|
+
# @return [void]
|
343
|
+
# @raise [ArgumentError] if they are contradictory.
|
344
|
+
def _validate_infinities
|
345
|
+
neg, pos = @infinities[:negative], @infinities[:positive]
|
346
|
+
#return if [neg, pos].include?(nil) || [neg, pos].include?(false)
|
347
|
+
|
348
|
+
msg = "specified/inherited negative and positive infinities are contradictory (such as reversed polarities or a combination of Float::INFINITY and RangeExtd::Infinity): [n,p]=#{[neg, pos].inspect}"
|
349
|
+
begin
|
350
|
+
if ![neg, pos].include?(nil) && ![neg, pos].include?(false) && neg > pos
|
351
|
+
raise ArgumentError, msg
|
352
|
+
end
|
353
|
+
rescue ArgumentError
|
354
|
+
# Likely, Float::INFINITY and RangeExtd::Infinity with the opposite polarities
|
355
|
+
# are specified, or the former is guessed from a Range.
|
356
|
+
raise ArgumentError, msg
|
357
|
+
end
|
358
|
+
return if empty_element?
|
359
|
+
|
360
|
+
fmt = "specified/inherited %s infinity (%s) is not %s enough or inconsistent: (<=> %s)"
|
361
|
+
hsp = {
|
362
|
+
negative: {
|
363
|
+
sm_lg: "small",
|
364
|
+
metho: :begin,
|
365
|
+
oper: :> # OK if self.begin > new-infinity(:negative)
|
366
|
+
},
|
367
|
+
positive: {
|
368
|
+
sm_lg: "large",
|
369
|
+
metho: :end,
|
370
|
+
oper: :< # OK if self.end < new-infinity(:positive)
|
371
|
+
},
|
372
|
+
}
|
373
|
+
|
374
|
+
# Gets a non-nil non-infinity boundary value if there is any, else +false+.
|
375
|
+
# NOTE self.begin|end is never false, because RangeExtd would not accept it.
|
376
|
+
valcmp =
|
377
|
+
if (self.begin.nil? || RangeExtd::Infinity.infinite?(self.begin))
|
378
|
+
if (self.end.nil? || RangeExtd::Infinity.infinite?(self.end))
|
379
|
+
false
|
380
|
+
else
|
381
|
+
self.end
|
382
|
+
end
|
383
|
+
else
|
384
|
+
self.begin
|
385
|
+
end
|
386
|
+
|
387
|
+
hsp.each_pair do |posneg, hsval|
|
388
|
+
ea_inf = @infinities[posneg] # Infinity value
|
389
|
+
ea_val = self.send(hsval[:metho]) # Current Range boundary value to compare with
|
390
|
+
msg = sprintf fmt, posneg.to_s, ea_inf.inspect, hsval[:sm_lg], ea_val.inspect
|
391
|
+
is_pol = (posneg.to_s+"?").to_sym # either :positive? or :negative?
|
392
|
+
next if false == ea_inf # infinity undefined (uninitialized).
|
393
|
+
next if ea_inf.nil?
|
394
|
+
next if ea_val == ea_inf
|
395
|
+
begin
|
396
|
+
if RangeExtd::Infinity.infinite?(ea_inf) && ea_inf.respond_to?(is_pol) && ea_inf.send(is_pol)
|
397
|
+
# To swap RangeExtd::Infinity <=> Float::INFINITY is allowed as long as
|
398
|
+
# the element of the range does not contradict it, e.g.,
|
399
|
+
# setting Float::INFINITY as an infinity is not allowed for a String Range.
|
400
|
+
next if false == valcmp
|
401
|
+
next if (ea_inf <=> valcmp) # namely if it is non-nil (0, 1, or 2)
|
402
|
+
raise ArgumentError, posneg.to_s.capitalize+" infinity not comparable with (#{valcmp})."
|
403
|
+
end
|
404
|
+
next if !ea_val.nil? && ea_val.send(hsval[:oper], ea_inf)
|
405
|
+
rescue ArgumentError
|
406
|
+
# raises an exception as follows
|
407
|
+
end
|
408
|
+
raise ArgumentError, msg
|
409
|
+
end
|
410
|
+
end
|
411
|
+
private :_validate_infinities
|
412
|
+
|
413
|
+
|
414
|
+
# Returns Hash (+{HashInf}) of the currently defined infinities of self
|
415
|
+
#
|
416
|
+
# The returned hash has two keys of +:positive+ and +:negative+ with infinity values.
|
417
|
+
#
|
418
|
+
# Internally, the infinities (Hash, extended with {HashInf}) are usually "guessed"
|
419
|
+
# from the provided Range etc and inherited. In the extreme case of
|
420
|
+
#
|
421
|
+
# Rangeary.new(RangeExtd::NONE)
|
422
|
+
#
|
423
|
+
# they remain undefined (with the values of the Hash being +false+orown) because there is no way to
|
424
|
+
# guess what its infinities are, in which case they are open to change,
|
425
|
+
# depending on following operations.
|
426
|
+
#
|
427
|
+
# If the optional argument of (+convert: true+) is given,
|
428
|
+
# any +false+ values are converted to something
|
429
|
+
# appropriate for the situation (that is, +nil+) before the value is returned.
|
430
|
+
# Also, the returned value is a standard Hash as opposed to Hash+HashInf.
|
431
|
+
#
|
432
|
+
# @param convert [Boolean] if true (Def: false), return never false, else +@infinities+
|
433
|
+
# @return [Hash] keys of :positive and :negative. The values are never false.
|
434
|
+
def infinities(convert: false)
|
435
|
+
convert ? _get_significant_infinities(@infinities) : @infinities
|
436
|
+
end
|
437
|
+
|
438
|
+
# Returns an infinities Hash with both ends being signifincant (never false, but maybe nil)
|
439
|
+
#
|
440
|
+
# Note this returns a standard Hash as opposed a Hash extended with {Util::HashInf}
|
441
|
+
# and the Hash contents of the returned value is modified only when
|
442
|
+
# @infinities.status_is_nil?(posneg)
|
443
|
+
# is true for +posneg+ of +:negative+ and +:positive+.
|
444
|
+
#
|
445
|
+
# @param ininf [Hash] keys of :positive and :negative.
|
446
|
+
# @return [Hash] keys of :positive and :negative. The values are never false.
|
447
|
+
def _get_significant_infinities(ininf)
|
448
|
+
if false == ininf[:positive]
|
449
|
+
case ininf[:negative]
|
450
|
+
when false
|
451
|
+
return( {negative: nil, positive: nil} )
|
452
|
+
when -Float::INFINITY
|
453
|
+
return ininf.merge({positive: Float::INFINITY})
|
454
|
+
when RangeExtd::Infinity::NEGATIVE
|
455
|
+
return ininf.merge({positive: RangeExtd::Infinity::POSITIVE})
|
456
|
+
else # including nil
|
457
|
+
return ininf.merge({positive: nil}) # Default: nil
|
458
|
+
end
|
459
|
+
elsif false == ininf[:negative]
|
460
|
+
# positive is never false. See above
|
461
|
+
case ininf[:positive]
|
462
|
+
when Float::INFINITY
|
463
|
+
return ininf.merge({negative: -Float::INFINITY})
|
464
|
+
when RangeExtd::Infinity::POSITIVE
|
465
|
+
return ininf.merge({negative: RangeExtd::Infinity::NEGATIVE})
|
466
|
+
else # including nil
|
467
|
+
return ininf.merge({negative: nil}) # Default: nil
|
468
|
+
end
|
469
|
+
else
|
470
|
+
# Both ends are defined.
|
471
|
+
{}.merge ininf
|
472
|
+
end
|
473
|
+
end
|
474
|
+
private :_get_significant_infinities
|
475
|
+
|
476
|
+
|
477
|
+
# If self covers the entire range?
|
478
|
+
#
|
479
|
+
# @note I realise this contradicts +Enumerable#all?+, which Array includes.
|
480
|
+
# This method may be removed in the future release...
|
481
|
+
#
|
482
|
+
def all?
|
483
|
+
rfirst = self[0]
|
484
|
+
((1 == size) &&
|
485
|
+
!rfirst.is_none? &&
|
486
|
+
(infinities[:negative] == rfirst.begin) &&
|
487
|
+
((infinities[:positive] == rfirst.end) || rfirst.end.nil?) && # Ruby 2.6 Endless Range
|
488
|
+
(!rfirst.exclude_begin?) &&
|
489
|
+
(!rfirst.exclude_end?))
|
490
|
+
end
|
491
|
+
|
492
|
+
# Origiinal +Array#===+
|
493
|
+
alias_method :triple_equals_orig, :=== if ! self.method_defined?(:triple_equals_orig)
|
494
|
+
|
495
|
+
# True if the inObj is in the range.
|
496
|
+
#
|
497
|
+
# This method works on the basis of each element, that is,
|
498
|
+
# if for any of the +RangeExtd+ in the +Rangeary+, +RangeExtd#===+
|
499
|
+
# returns true, this will return true. That means, if the argument is
|
500
|
+
# a +Rangeary+ (or +Range+) object, this always returns false.
|
501
|
+
# Note +#include?+ and +#member?+ work the same as in the standard Array,
|
502
|
+
# whereas {#include_element?} and {#member_element?} are the alias of
|
503
|
+
# this method.
|
504
|
+
#
|
505
|
+
# See {#cover?}. The difference between this method and {#cover?} is
|
506
|
+
# the same as that in Range.
|
507
|
+
# @return [Boolean]
|
508
|
+
def ===(inObj)
|
509
|
+
to_a.each do |ea|
|
510
|
+
if ea === inObj
|
511
|
+
return true
|
512
|
+
end
|
513
|
+
end
|
514
|
+
return false
|
515
|
+
end
|
516
|
+
|
517
|
+
alias_method :include_element?, :===
|
518
|
+
alias_method :member_element?, :===
|
519
|
+
|
520
|
+
|
521
|
+
# @return [Object] The +RangeExtd#begin+ of the first +RangeExtd+.
|
522
|
+
def begin()
|
523
|
+
if to_a.size > 0
|
524
|
+
to_a[0].begin
|
525
|
+
else
|
526
|
+
nil # Should not happen!
|
527
|
+
end
|
528
|
+
end
|
529
|
+
alias_method :begin_element, :begin
|
530
|
+
|
531
|
+
|
532
|
+
# If inObj is within the ranges, it will return true.
|
533
|
+
#
|
534
|
+
# See {#===}.
|
535
|
+
# The difference between this method and {#===} is
|
536
|
+
# the same as that in Range.
|
537
|
+
def cover?(inObj)
|
538
|
+
to_a.each do |ea|
|
539
|
+
if ea.cover? inObj
|
540
|
+
return true
|
541
|
+
elsif (ea.end <=> inObj) == 1
|
542
|
+
return false # No point of carrying on searching.
|
543
|
+
end
|
544
|
+
end
|
545
|
+
return false
|
546
|
+
end # def cover?(inObj)
|
547
|
+
|
548
|
+
|
549
|
+
# Iterator for each element in the ranges.
|
550
|
+
# @return [self]
|
551
|
+
def each_element
|
552
|
+
each do |er|
|
553
|
+
er.each do |ee|
|
554
|
+
yield ee
|
555
|
+
end
|
556
|
+
end
|
557
|
+
self
|
558
|
+
end
|
559
|
+
|
560
|
+
|
561
|
+
# If the range defined in this object is empty (as in +Range#empty?+), returns true.
|
562
|
+
#
|
563
|
+
def empty_element?
|
564
|
+
each do |er|
|
565
|
+
if ! er.empty?
|
566
|
+
return false
|
567
|
+
end
|
568
|
+
end
|
569
|
+
return true
|
570
|
+
end
|
571
|
+
|
572
|
+
|
573
|
+
# @return [Object] The +RangeExtd#end+ of the last +RangeExtd+.
|
574
|
+
def end()
|
575
|
+
if to_a.size > 0
|
576
|
+
to_a[-1].end
|
577
|
+
else
|
578
|
+
nil
|
579
|
+
end
|
580
|
+
end
|
581
|
+
alias_method :end_element, :end
|
582
|
+
|
583
|
+
|
584
|
+
# +Range#equiv?+ method, defined in range_extd library, extended to this {Rangeary}.
|
585
|
+
#
|
586
|
+
# @example
|
587
|
+
# Rangeary(RangeExtd(1,"<...",4), 5...8).equiv?(Rangeary(2..3, 5..7)) # => true
|
588
|
+
#
|
589
|
+
# @param other [Rangeary]
|
590
|
+
def equiv?(other)
|
591
|
+
return false if size() != other.size
|
592
|
+
|
593
|
+
self.zip(other).each do |ear|
|
594
|
+
if ! ear[0].equiv?(ear[1])
|
595
|
+
return false
|
596
|
+
end
|
597
|
+
end
|
598
|
+
|
599
|
+
true
|
600
|
+
end # def equiv?(other)
|
601
|
+
|
602
|
+
|
603
|
+
# Returns the first n elements of the entire range, the same as +Range#first+.
|
604
|
+
#
|
605
|
+
# If the argument is not given, this simply calls +Range#begin+ for the first
|
606
|
+
# +RangeExtd+.
|
607
|
+
#
|
608
|
+
# If not, and if the elements in the ranges are not discrete, like Float,
|
609
|
+
# an exception is raised (see +Range#first+).
|
610
|
+
# Note this works on the element basis, being different from {Rangeary#first},
|
611
|
+
# which works on the array basis.
|
612
|
+
#
|
613
|
+
# @param n [Integer] (Optional) positive.
|
614
|
+
# @return [Object] equivalent to {#begin} if no argument is given.
|
615
|
+
# @return [Array] Array of the first n elements in the range.
|
616
|
+
# @raise [TypeError] if the ranges has no iteration defined, such as, starting from Float.
|
617
|
+
def first_element(n=nil)
|
618
|
+
if n.nil?
|
619
|
+
self.first.first
|
620
|
+
#self.begin()
|
621
|
+
elsif n < 0
|
622
|
+
raise ArgumentError, "the argument #{n} has to be positive."
|
623
|
+
else
|
624
|
+
arRet = []
|
625
|
+
m = n
|
626
|
+
to_a.each do |eachr|
|
627
|
+
ar = eachr.first(m)
|
628
|
+
arRet += ar
|
629
|
+
if arRet.size >= n
|
630
|
+
break
|
631
|
+
else
|
632
|
+
m -= ar.size
|
633
|
+
end
|
634
|
+
end
|
635
|
+
|
636
|
+
arRet
|
637
|
+
end # if n.nil?
|
638
|
+
end # def first_element(n=nil)
|
639
|
+
|
640
|
+
|
641
|
+
# Return an array of objects that consist of the ranges.
|
642
|
+
#
|
643
|
+
# @return [Array]
|
644
|
+
# @raise [TypeError] if any of the ranges has no iteration defined, such as, starting from Float.
|
645
|
+
# @example
|
646
|
+
# Rangeary(2...4, 5..6, 8..9).to_a # => [2...4, 5..6, 8..9]
|
647
|
+
# Rangeary(2...4, 5..6, 8..9).flatten # => [2, 3, 5, 6, 8, 9]
|
648
|
+
# Rangeary(2...4, 5..6, 8..9).flatten.reduce(:+) # => 33
|
649
|
+
def flatten_element
|
650
|
+
to_a.reduce([]){|a,b| a+=b.to_a}
|
651
|
+
end
|
652
|
+
|
653
|
+
|
654
|
+
# @return [String]
|
655
|
+
def inspect
|
656
|
+
"<Rangeary:#{to_a.inspect}>"
|
657
|
+
end
|
658
|
+
|
659
|
+
|
660
|
+
# Can iterate?
|
661
|
+
def iteratable?
|
662
|
+
begin
|
663
|
+
each do |i|
|
664
|
+
i.each{break}
|
665
|
+
end
|
666
|
+
rescue TypeError
|
667
|
+
return false
|
668
|
+
end
|
669
|
+
true
|
670
|
+
end # def iteratable?
|
671
|
+
|
672
|
+
|
673
|
+
# Returns the last n elements of the entire range, the same as +Range#last+.
|
674
|
+
#
|
675
|
+
# If the argument is not given, this simply calls +Range#end+ for the last
|
676
|
+
# +RangeExtd+.
|
677
|
+
#
|
678
|
+
# If not, and if the elements in the ranges are not discrete, like Float,
|
679
|
+
# an exception is raised (see +Range#last+).
|
680
|
+
# Note this works on the element basis, being different from +Rangeary#last+,
|
681
|
+
# which works on the array basis.
|
682
|
+
#
|
683
|
+
# @param n [Integer] (Optional) positive.
|
684
|
+
# @return [Object] equivalent to {#end} if no argument is given.
|
685
|
+
# @return [Array] Array of the last n elements in the range.
|
686
|
+
# @raise [TypeError] if any of the ranges has no iteration defined, such as, starting from Float.
|
687
|
+
def last_element(n=nil)
|
688
|
+
if n.nil?
|
689
|
+
self.last.last # self.end() would behave differently when the last Range is like (2..)
|
690
|
+
elsif n < 0
|
691
|
+
raise ArgumentError, "the argument #{n} has to be positive."
|
692
|
+
else
|
693
|
+
arRet = []
|
694
|
+
m = n
|
695
|
+
to_a.reverse.each do |eachr|
|
696
|
+
ar = eachr.last(m)
|
697
|
+
|
698
|
+
## Dealing with a bug of Range#last(n):
|
699
|
+
# https://bugs.ruby-lang.org/issues/18994
|
700
|
+
# The bug appears in Ruby 2.7.0 onwards (at least up to Ruby-3.1.2)
|
701
|
+
if m > 0 && ar.count < [m, eachr.count].min
|
702
|
+
ar = eachr.to_a
|
703
|
+
end
|
704
|
+
|
705
|
+
arRet.unshift(*ar) # arRet = ar + arRet
|
706
|
+
if arRet.size >= n
|
707
|
+
break
|
708
|
+
else
|
709
|
+
m -= ar.size
|
710
|
+
end
|
711
|
+
end
|
712
|
+
|
713
|
+
arRet
|
714
|
+
end # if n.nil?
|
715
|
+
end # def last_element(n=nil)
|
716
|
+
|
717
|
+
|
718
|
+
# Practically equivalent to {#empty_element?}.
|
719
|
+
def null_element?
|
720
|
+
each do |er|
|
721
|
+
if ! er.null?
|
722
|
+
return false
|
723
|
+
end
|
724
|
+
end
|
725
|
+
return true
|
726
|
+
end
|
727
|
+
alias_method :null?, :null_element?
|
728
|
+
|
729
|
+
|
730
|
+
# Return the sum of +RangeExtd#size+ of all the ranges in the object.
|
731
|
+
#
|
732
|
+
# @return [Integer] 0 if +RangeExtd::NONE+
|
733
|
+
# @return [Float] Float::INFINITY if one of ranges is open-ended.
|
734
|
+
# @return [nil] if any of the range is non-Numeric and not open-ended to the infinity.
|
735
|
+
def size_element()
|
736
|
+
begin
|
737
|
+
to_a.map(&:size).reduce(:+)
|
738
|
+
rescue TypeError
|
739
|
+
nil
|
740
|
+
end
|
741
|
+
end
|
742
|
+
|
743
|
+
|
744
|
+
# ======================= Operators =======================
|
745
|
+
|
746
|
+
# Disjunction of a Rangeary (or RangeExtd or Range) and another
|
747
|
+
#
|
748
|
+
# @param r1 [Rangeary, RangeExtd, Range]
|
749
|
+
# @param r2 [Rangeary, RangeExtd, Range]
|
750
|
+
# @return [Rangeary]
|
751
|
+
def self.disjunction(r1, r2)
|
752
|
+
self.new(r1, r2)
|
753
|
+
end
|
754
|
+
|
755
|
+
# Add (Disjunction) a Rangeary (or RangeExtd or Range)
|
756
|
+
#
|
757
|
+
# @param inr [Rangeary, RangeExtd, Range]
|
758
|
+
# @return [Rangeary]
|
759
|
+
def disjunction(inr)
|
760
|
+
self.class.new(self, inr)
|
761
|
+
end
|
762
|
+
|
763
|
+
alias_method :+, :disjunction
|
764
|
+
alias_method :|, :disjunction # "|" (plus with Object#eql?) in general, but in this case it is identical.
|
765
|
+
|
766
|
+
# Exclusive Disjunction (XOR) with a Rangeary (or RangeExtd or Range)
|
767
|
+
#
|
768
|
+
# @param r1 [Rangeary, RangeExtd, Range]
|
769
|
+
# @param r2 [Rangeary, RangeExtd, Range]
|
770
|
+
# @return [Rangeary]
|
771
|
+
def self.exclusive_disjunction(r1, r2)
|
772
|
+
Rangeary.new(r1).exclusive_disjunction(r2)
|
773
|
+
end
|
774
|
+
|
775
|
+
# Exclusive Disjunction (XOR) with a Rangeary (or RangeExtd or Range)
|
776
|
+
#
|
777
|
+
# @param inr [Rangeary, RangeExtd, Range]
|
778
|
+
# @return [Rangeary]
|
779
|
+
def exclusive_disjunction(inr)
|
780
|
+
(disjunction(inr)).conjunction(conjunction(inr).negation)
|
781
|
+
end
|
782
|
+
|
783
|
+
alias_method :^, :exclusive_disjunction
|
784
|
+
alias_method :xor, :exclusive_disjunction
|
785
|
+
|
786
|
+
# Subtraction.
|
787
|
+
#
|
788
|
+
# == Deverloper's note
|
789
|
+
#
|
790
|
+
# Handling of infinities is a little tricky.
|
791
|
+
#
|
792
|
+
# If nothing was considered,
|
793
|
+
# Rangeary(6..nil) - (6...8)
|
794
|
+
# would return
|
795
|
+
# Rangeary([8..Float::INFINITY])
|
796
|
+
# because the first step of operation, negation of +(6...8)+, would
|
797
|
+
# contain +Float::INFINITY+ and then the standard conjunction would regard it
|
798
|
+
# as the *definite* sign of +Float::INFINITY+ being the correct infinity,
|
799
|
+
# as opposed to +nil+.
|
800
|
+
#
|
801
|
+
# In the case, only the infinity that appears in the user-supplied formula is +nil+.
|
802
|
+
# Therefore, at least the positive infinity should be recognized as +nil+,
|
803
|
+
# whereas the negative infinity remains uncertain.
|
804
|
+
#
|
805
|
+
# @param r [Rangeary, RangeExtd, Range]
|
806
|
+
# @return [Rangeary]
|
807
|
+
def subtraction(r)
|
808
|
+
infs = _get_infinities_from_multiple(self, r)
|
809
|
+
conjunction_core(self, Rangeary.new(r, **infs).negation, infs: infs)
|
810
|
+
end
|
811
|
+
|
812
|
+
alias_method :-, :subtraction
|
813
|
+
|
814
|
+
|
815
|
+
# Conjunction.
|
816
|
+
#
|
817
|
+
# @param r [Rangeary, RangeExtd, Range]
|
818
|
+
# @return [Rangeary]
|
819
|
+
def conjunction(r)
|
820
|
+
self.class.conjunction(self, r)
|
821
|
+
end
|
822
|
+
|
823
|
+
alias_method :&, :conjunction
|
824
|
+
alias_method :*, :conjunction
|
825
|
+
|
826
|
+
## Internal version of Conjunction, where +infinities+ can be given.
|
827
|
+
##
|
828
|
+
## @param r [Rangeary, RangeExtd, Range]
|
829
|
+
## @param infs [Hash] ({HashInf}) if supplied, +infinities+ are NOT guessed from the main arguments.
|
830
|
+
## @return [Rangeary]
|
831
|
+
#def conjunction_core(r, infs: nil)
|
832
|
+
# self.class.conjunction_core(self, r, infs: infs)
|
833
|
+
#end
|
834
|
+
#private :conjunction_core
|
835
|
+
|
836
|
+
|
837
|
+
# Negation (class method).
|
838
|
+
#
|
839
|
+
# @param r [Rangeary, RangeExtd, Range]
|
840
|
+
# @return [Rangeary]
|
841
|
+
def self.negation(r)
|
842
|
+
self.new(r).negation
|
843
|
+
end
|
844
|
+
|
845
|
+
|
846
|
+
# Negation.
|
847
|
+
#
|
848
|
+
# @return [Rangeary]
|
849
|
+
def negation()
|
850
|
+
if to_a.empty?
|
851
|
+
raise "ERROR: No range is defined." # This should not happen.
|
852
|
+
end
|
853
|
+
|
854
|
+
arran = []
|
855
|
+
prevend = nil # if "end" is nil, this is substituted with Infinity.
|
856
|
+
prevend_orig = Object # For Endless Range (Ruby 2.6), this can be nil.
|
857
|
+
prevst = nil
|
858
|
+
to_a.each do |eachr|
|
859
|
+
|
860
|
+
# ALL -> NONE (specifying infinity parameters (positive and negative) is important.)
|
861
|
+
return Rangeary.new(RangeExtd::NONE, @infinities) if RangeExtd::ALL == eachr
|
862
|
+
#return Rangeary.new(RangeExtd::NONE, :positive => @infinities[:positive], :negative => @infinities[:negative]) if RangeExtd::ALL == eachr
|
863
|
+
|
864
|
+
# null(NONE) -> ALL
|
865
|
+
#
|
866
|
+
# Note there should be no more than 1 null Range/RangeExtd in Rangeary.
|
867
|
+
if eachr.null?
|
868
|
+
begin
|
869
|
+
_ = 1.0 * eachr.begin
|
870
|
+
return Rangeary.new(-Float::INFINITY..Float::INFINITY, @infinities)
|
871
|
+
rescue TypeError # XXXX can't be coerced into Float
|
872
|
+
return Rangeary.new(infinities(convert: true)[:negative]..infinities(convert: true)[:positive], @infinities)
|
873
|
+
end
|
874
|
+
end
|
875
|
+
|
876
|
+
eachm = _comparable_beginend(eachr) # For Ruby-2.7 Beginless Range
|
877
|
+
ea_b = eachm.begin
|
878
|
+
if (RangeExtd::Infinity.infinite?(ea_b) && ea_b.respond_to?(:negative?) && ea_b.negative?) || infinities[:negative] == ea_b
|
879
|
+
# The existing first range starts from the negative infinity, so skip this one.
|
880
|
+
else
|
881
|
+
if prevend.nil?
|
882
|
+
# The returned first range starts from the negative infinity.
|
883
|
+
ran_tmp = normalized_range_for_negation(infinities(convert: true)[:negative], eachr.begin)
|
884
|
+
if RangeExtd::NONE != ran_tmp
|
885
|
+
# Avoid (inf..inf) type, which would cause RangeError (in Ruby 2.6) anyway.
|
886
|
+
arran.push( RangeExtd(ran_tmp, :exclude_end => (! eachr.exclude_begin?)) )
|
887
|
+
end
|
888
|
+
else
|
889
|
+
arran.push( RangeExtd(prevend_orig, eachr.begin, :exclude_begin => (! prevst), :exclude_end => (! eachr.exclude_begin?)) )
|
890
|
+
end
|
891
|
+
end # if (eachr.begin == -Float::INFINITY) || (RangeExtd::NONE == eachr.begin)
|
892
|
+
prevend_orig = eachr.end
|
893
|
+
prevend = _comparable_end(eachr) # For Ruby-2.6 Endless Range
|
894
|
+
prevst = eachr.exclude_end?
|
895
|
+
end # to_a.each do |eachr|
|
896
|
+
|
897
|
+
if (RangeExtd::Infinity.infinite?(prevend) && prevend.respond_to?(:positive?) && prevend.positive?) || infinities(convert: true)[:positive] == prevend
|
898
|
+
## Do nothing
|
899
|
+
else
|
900
|
+
rbeg = (prevend_orig || prevend)
|
901
|
+
ran_tmp = normalized_range_for_negation(rbeg, infinities(convert: true)[:positive])
|
902
|
+
|
903
|
+
if RangeExtd::NONE != ran_tmp
|
904
|
+
# Avoid (inf..inf) type, which would cause RangeError (in Ruby 2.6) anyway.
|
905
|
+
arran.push( RangeExtd.new(ran_tmp, :exclude_begin => (! prevst)) )
|
906
|
+
end
|
907
|
+
end
|
908
|
+
|
909
|
+
Rangeary.new(arran, @infinities) # @infinities is inherited as it is.
|
910
|
+
end # def negation()
|
911
|
+
|
912
|
+
alias_method :~@, :negation
|
913
|
+
|
914
|
+
|
915
|
+
####################
|
916
|
+
# Public class methods
|
917
|
+
####################
|
918
|
+
|
919
|
+
# Conjunction.
|
920
|
+
#
|
921
|
+
# @param r1 [Rangeary, RangeExtd, Range]
|
922
|
+
# @param r2 [Rangeary, RangeExtd, Range]
|
923
|
+
# @return [Rangeary]
|
924
|
+
def self.conjunction(r1, r2)
|
925
|
+
conjunction_core(r1, r2)
|
926
|
+
end
|
927
|
+
|
928
|
+
|
929
|
+
# Returns the infinity "end" that is comparable.
|
930
|
+
#
|
931
|
+
# Since Ruby-2.6, the Endless Range is introduced.
|
932
|
+
# In Ruby definition, the Endless Range er takes a form of er=(5..nil),
|
933
|
+
# and accordingly its "end" (or +last+) is nil:
|
934
|
+
#
|
935
|
+
# (5..nil).end # => nil
|
936
|
+
#
|
937
|
+
# Then, when you compare the "ends" of two Ranges with the method +<=>+,
|
938
|
+
# the result too is nil.
|
939
|
+
#
|
940
|
+
# In fact, in Ruby, two Range objects are not comparable with eath other,
|
941
|
+
# though the operator (or method) +<=>+ is defined for Range:
|
942
|
+
#
|
943
|
+
# (3..6) <=> (8..9) # => nil
|
944
|
+
#
|
945
|
+
# (Note this default behaviour is not unreasonable given what one compares
|
946
|
+
# is equivocal for Ranges, for example, the size, start or end?
|
947
|
+
# Indeed Range does not have a method +Range<=+ and hence +<=>+ is meaningless.)
|
948
|
+
#
|
949
|
+
# Then, when Range#end returns just nil, which should be interpreted
|
950
|
+
# as *Endless* in Range's context, it does not matter for Range.
|
951
|
+
# However, it is inconvenient for this class, {Rangeary}!
|
952
|
+
# The heart of comparison in {Rangeary} is +<=>+ for the begin first,
|
953
|
+
# and then the end if the first comparison results in equal.
|
954
|
+
#
|
955
|
+
# This method converts Range#end into something comparable when possible.
|
956
|
+
# More specifically, if the "end" of the given Range is nil, this returns
|
957
|
+
# Infinity (either +Float::INFINITY+ or +RangeExtd::Infinity::POSITIVE+),
|
958
|
+
# unless both begin and end are nil (RangeExtd::NONE, nil..nil, nil...nil),
|
959
|
+
# in which case nil is returned, and else returns simply +Range#end+.
|
960
|
+
#
|
961
|
+
# @note This method becomes a subset of {Rangeary.comparable_beginend} in
|
962
|
+
# Rangeary Ver.2, supporting Ruby-2.7 Beginless Ranges.
|
963
|
+
#
|
964
|
+
# @param ran [Range, RangeExtd]
|
965
|
+
# @param infinities [Hash<Boolean>] two-elements of :positive and :negative
|
966
|
+
# @return [Object]
|
967
|
+
# @raise [RangeError] if ran is not Range#valid?
|
968
|
+
def self.comparable_end(ran, infinities: nil)
|
969
|
+
comparable_beginend(ran, infinities: infinities).end
|
970
|
+
end
|
971
|
+
|
972
|
+
# Returns a two-element Array with comparable elements
|
973
|
+
#
|
974
|
+
# The Endless and Beginless Ranges were introduced in Ruby-2.6
|
975
|
+
# and Ruby-2.7, respectively.
|
976
|
+
# In Ruby's definition, the Endless/Beginless Ranges take a form of
|
977
|
+
# +(5..nil)+ and +(nil..5)+ (they can be expressed without "nil")
|
978
|
+
# and accordingly its "end" (n.b., +last+ raises +RangeError+) and
|
979
|
+
# "begin" are nil:
|
980
|
+
#
|
981
|
+
# (5..nil).end # => nil
|
982
|
+
# (nil..5).begin # => nil
|
983
|
+
#
|
984
|
+
# Then, when you compare the "ends" of two Ranges with the method +<=>+,
|
985
|
+
# the result is true (it may use to be nil??).
|
986
|
+
#
|
987
|
+
# In fact, in Ruby, two Range objects are not comparable with eath other,
|
988
|
+
# though the comparator operator (or method) +<=>+ is defined for Range,
|
989
|
+
# presumably inherited from Object:
|
990
|
+
#
|
991
|
+
# (3..6) <=> (8..9) # => nil
|
992
|
+
# (3..6) <=> (3..6) # => 0
|
993
|
+
#
|
994
|
+
# (Note this default behaviour is not unreasonable given what one compares
|
995
|
+
# is equivocal for Ranges, for example, the size, start or end?
|
996
|
+
# Indeed Range does not have a method +Range<=+ and hence +<=>+ is meaningless.
|
997
|
+
# except for the equality, where 0 is returned.)
|
998
|
+
#
|
999
|
+
# Then, when Range#end returns just nil, which should be interpreted
|
1000
|
+
# as *Endless* in Range's context, it does not matter for Range.
|
1001
|
+
# However, it is inconvenient for this class, {Rangeary}!
|
1002
|
+
# The heart of comparison in {Rangeary} is +<=>+ for the begin first,
|
1003
|
+
# and then the end if the first comparison results in equal.
|
1004
|
+
#
|
1005
|
+
# This method returns a +RangeExtd+, in which +Range#begin+ and +Range#end+
|
1006
|
+
# are converted into something comparable when possible.
|
1007
|
+
# More specifically, unless the given Range is +RangeExtd::NONE+,
|
1008
|
+
# if the "begin" and/or "end" of the given Range is nil,
|
1009
|
+
# the value is converted into
|
1010
|
+
# Infinity (one of +Float::INFINITY+ or +RangeExtd::Infinity::POSITIVE+
|
1011
|
+
# or their negatives, or user-specified infinities, where +nil+ is ignored)
|
1012
|
+
# for the respective value. Else, simply
|
1013
|
+
# +RangeExtd(Range#begin, Range#end)+ is returned.
|
1014
|
+
#
|
1015
|
+
# Note that because the returned value is RangeExtd, which does not
|
1016
|
+
# accept any Ranges that are not Range#valid, if such an invalid Range
|
1017
|
+
# is given RangeError is raised. This includes Ranges that contains
|
1018
|
+
# +RangeExtd::Nowhere::NOWHERE+ (except for +RangeExtd::NONE+).
|
1019
|
+
#
|
1020
|
+
# The optional argument of Hash +infinities+ having boolean values for
|
1021
|
+
# two keys of :positive and :negative can be given, in order to
|
1022
|
+
# explicitly specify your infinities.
|
1023
|
+
#
|
1024
|
+
# @overload self.comparable_beginend(ran, infinities: nil)
|
1025
|
+
# Range or RangeExtd
|
1026
|
+
# @param ran [Range, RangeExtd]
|
1027
|
+
# @param infinities [Hash<Boolean>] two-elements of :positive and :negative
|
1028
|
+
# @overload self.comparable_beginend(ran, vend, infinities: nil)
|
1029
|
+
# Begin and End values for Range
|
1030
|
+
# @param ran [Object]
|
1031
|
+
# @param vend [Object]
|
1032
|
+
# @param infinities [Hash<Boolean>] two-elements of :positive and :negative
|
1033
|
+
#
|
1034
|
+
# @return [RangeExtd] exclude statuses for begin and end follow the input.
|
1035
|
+
# @raise [RangeError] if ran is not Range#valid?
|
1036
|
+
def self.comparable_beginend(ran, vend=Object, infinities: nil)
|
1037
|
+
comparable_beginend_core(ran, vend, infinities: infinities) # In Rangeary::Util
|
1038
|
+
end
|
1039
|
+
|
1040
|
+
# Similar to {Rangeary.comparable_end} but for +begin+.
|
1041
|
+
#
|
1042
|
+
# Introduced for Ruby-2.7 beginless Range.
|
1043
|
+
#
|
1044
|
+
# @param (see Rangeary.comparable_beginend)
|
1045
|
+
# @return [Object]
|
1046
|
+
# @raise [RangeError] if ran is not Range#valid?
|
1047
|
+
def self.comparable_begin(ran, infinities: nil)
|
1048
|
+
comparable_beginend(ran, infinities: infinities).begin
|
1049
|
+
end
|
1050
|
+
|
1051
|
+
# Returns the array sorted, based on (1) the begin objects and their boundary state,
|
1052
|
+
# (2) then the end objects and their boundary state.
|
1053
|
+
# +RangeExtd::NONE+ comes first, if included in the input array.
|
1054
|
+
#
|
1055
|
+
# @param ar [<Range, RangeExtd>] Arbitrary number.
|
1056
|
+
# @return [Array<Range, RangeExtd>]
|
1057
|
+
def self.sort_ranges(*ar)
|
1058
|
+
sort_ranges_core(*ar) # In Rangery::Util
|
1059
|
+
end
|
1060
|
+
|
1061
|
+
# Return a flattened Array where {Rangeary} is not flattened.
|
1062
|
+
#
|
1063
|
+
# @param arin [Array<Range, RangeExtd, Rangeary, Array>]
|
1064
|
+
# @return [Array<Range, RangeExtd, Rangeary>]
|
1065
|
+
def self.flatten_no_rangeary(*arin)
|
1066
|
+
flatten_no_rangeary_core(*arin)
|
1067
|
+
end
|
1068
|
+
|
1069
|
+
####################
|
1070
|
+
private
|
1071
|
+
####################
|
1072
|
+
|
1073
|
+
# Called from {Rangeary.initialize}
|
1074
|
+
def _convert2range(inarall)
|
1075
|
+
inarall.flatten.map{|i|
|
1076
|
+
if defined? i.first_element
|
1077
|
+
i.to_a
|
1078
|
+
else
|
1079
|
+
i
|
1080
|
+
end
|
1081
|
+
}.flatten.map{|j|
|
1082
|
+
if (defined? j.exclude_begin?)
|
1083
|
+
j
|
1084
|
+
else
|
1085
|
+
RangeExtd(j)
|
1086
|
+
end
|
1087
|
+
}
|
1088
|
+
end
|
1089
|
+
private :_convert2range
|
1090
|
+
|
1091
|
+
# Instance method version
|
1092
|
+
#
|
1093
|
+
# where @infinities are taken into account.
|
1094
|
+
#
|
1095
|
+
# @param ran [Range, RangeExtd]
|
1096
|
+
# @return [Object]
|
1097
|
+
def _comparable_end(ran)
|
1098
|
+
_comparable_beginend(ran).end
|
1099
|
+
end
|
1100
|
+
private :_comparable_end
|
1101
|
+
|
1102
|
+
# Same as {#_comparable_end} but for begin
|
1103
|
+
#
|
1104
|
+
# Required for Ruby-2.7 and beyond.
|
1105
|
+
#
|
1106
|
+
# This was introduced for Ruby-2.6 even though this is
|
1107
|
+
# beyond the scope of Ruby-2.6, because it is
|
1108
|
+
# needed for {#negation}.
|
1109
|
+
#
|
1110
|
+
# @param rbeg [Object]
|
1111
|
+
# @param rend [Object]
|
1112
|
+
# @return [Object]
|
1113
|
+
def _comparable_begin(rbeg, rend)
|
1114
|
+
_comparable_beginend(rbeg, rend).begin
|
1115
|
+
end
|
1116
|
+
private :_comparable_begin
|
1117
|
+
|
1118
|
+
|
1119
|
+
# Instance method version
|
1120
|
+
#
|
1121
|
+
# where @infinities are taken into account.
|
1122
|
+
#
|
1123
|
+
# @overload _comparable_beginend(ran)
|
1124
|
+
# Range or RangeExtd
|
1125
|
+
# @param ran [Range, RangeExtd]
|
1126
|
+
# @overload _comparable_beginend(ran, vend)
|
1127
|
+
# Begin and End values for Range
|
1128
|
+
# @param ran [Object]
|
1129
|
+
# @param vend [Object]
|
1130
|
+
#
|
1131
|
+
# @return [RangeExtd] exclude statuses for begin and end follow the input.
|
1132
|
+
# @raise [RangeError] if ran is not Range#valid?
|
1133
|
+
def _comparable_beginend(ran, vend=Object)
|
1134
|
+
self.class.comparable_beginend(ran, vend=vend, infinities: infinities) ###################
|
1135
|
+
end
|
1136
|
+
private :_comparable_beginend
|
1137
|
+
|
1138
|
+
# Build the instance variable (Hash) @infinities
|
1139
|
+
#
|
1140
|
+
# @param opts [Hash] (maybe {HashInf}) :positive and :negative Object of infinities.
|
1141
|
+
# @param arin [Array<Range, RangeExtd, Rangeary>]
|
1142
|
+
# @return [Hash] extended with {HashInf}
|
1143
|
+
def _build_infinities(opts, arin)
|
1144
|
+
reths = _get_infinities_from_multiple(arin)
|
1145
|
+
reths.merge_hashinf!(opts, force: true)
|
1146
|
+
reths
|
1147
|
+
end
|
1148
|
+
private :_build_infinities
|
1149
|
+
|
1150
|
+
|
1151
|
+
# Normalize a Range, which is about to be used for new {Rangeary}
|
1152
|
+
#
|
1153
|
+
# @param rbeg [Object]
|
1154
|
+
# @param rend [Object]
|
1155
|
+
# @return [Range, RangeExtd::NONE]
|
1156
|
+
def normalized_range_for_negation(rbeg, rend)
|
1157
|
+
begin
|
1158
|
+
ret = rbeg..rend # Range
|
1159
|
+
rescue RangeError
|
1160
|
+
# the begin must be nil, after being converted from an Endless Range as in Ruby 2.6
|
1161
|
+
ret = (_comparable_beginend(rbeg, rend).begin..rend) # For Ruby 2.7+
|
1162
|
+
# rescue ArgumentError
|
1163
|
+
## NOTE: If this happens, something is gone wrong!!
|
1164
|
+
end
|
1165
|
+
return RangeExtd::NONE if same_infinities?(ret.begin, ret.end)
|
1166
|
+
ret
|
1167
|
+
end
|
1168
|
+
private :normalized_range_for_negation
|
1169
|
+
|
1170
|
+
# True if both are infinities and in the same parity
|
1171
|
+
#
|
1172
|
+
# @param c1 [Object]
|
1173
|
+
# @param c2 [Object]
|
1174
|
+
def same_infinities?(c1, c2)
|
1175
|
+
arin = [c1, c2]
|
1176
|
+
arin.all?{ |ec| RangeExtd::Infinity.infinite?(ec) } &&
|
1177
|
+
(arin.all?(&:positive?) || arin.all?(&:negative?))
|
1178
|
+
end
|
1179
|
+
private :same_infinities?
|
1180
|
+
|
1181
|
+
# Called from {Rangeary#initialize}.
|
1182
|
+
#
|
1183
|
+
# Process the array of RangeExtd and return the new one, in which
|
1184
|
+
# overlapped ranges are merged accordingly.
|
1185
|
+
#
|
1186
|
+
# Before returning, multiple empty Ranges are filtered out, the job
|
1187
|
+
# of which is delegated to {Rangeary#_uniq_empty_ranges}.
|
1188
|
+
# If there is no non-"empty" range, one of them will be left.
|
1189
|
+
# As a priority, an empty range with a definite class is left,
|
1190
|
+
# but if there is none, RangeExtd::NONE will be left.
|
1191
|
+
#
|
1192
|
+
# Note that (Inf..Inf) or (-Inf..-Inf) is replaced with +RangeExtd::NONE+,
|
1193
|
+
# which then will be truncated.
|
1194
|
+
#
|
1195
|
+
# @param inAr [Array<RangeExtd,Range>]
|
1196
|
+
# @return [Array<RangeExtd>]
|
1197
|
+
def _merge_overlaps(inAr)
|
1198
|
+
|
1199
|
+
### Cases
|
1200
|
+
#[st means status.]
|
1201
|
+
#(0) (prev[0]) and (prev[0].status) unchanged.
|
1202
|
+
#(1) if (now[-1]< prev[-1]), do nothing. [Totally inclusive]
|
1203
|
+
# I---* => I---*
|
1204
|
+
# *--*
|
1205
|
+
#(2) if (now[-1]== prev[-1])
|
1206
|
+
# (2-1) AND if (now[-1].st? || prev[-1].st?), prev[-1].st=T [Nearly inclusive]
|
1207
|
+
# I--O => I--I
|
1208
|
+
# I--I
|
1209
|
+
# (2-2) ELSE do nothing. [Totally inclusive]
|
1210
|
+
#(3) ELSE [namely, if (now[-1] > prev[-1])]
|
1211
|
+
# (3-1) if (now[0] > prev[-1]), append. [Totally exclusive]
|
1212
|
+
# *--* => *--* *--*
|
1213
|
+
# *--*
|
1214
|
+
# (3-2) if (now[0] == prev[-1])
|
1215
|
+
# (3-2-1) (!now[0].st? && !prev[-1].st?), append. [Totally exclusive]
|
1216
|
+
# *--O => *--O--*
|
1217
|
+
# O--*
|
1218
|
+
# (3-2-2) ELSE [namely, now[1].st? || prev[-1].st?], connect. (prev[0],now[-1])
|
1219
|
+
# *--O => *-----*
|
1220
|
+
# I--*
|
1221
|
+
# *--I => *-----*
|
1222
|
+
# O--*
|
1223
|
+
# *--I => *-----*
|
1224
|
+
# I--*
|
1225
|
+
# (3-3) ELSE [namely, if (now[0] < prev[-1])], connect. (prev[0],now[-1])
|
1226
|
+
# *--* => *---*
|
1227
|
+
# *--*
|
1228
|
+
#
|
1229
|
+
|
1230
|
+
inRanges = _replace_inf_inf(inAr) # Replace meaningless inf..inf etc.
|
1231
|
+
inRanges = self.class.sort_ranges(inRanges).map{|i| (RangeExtd === i) ? i : RangeExtd(i) } # => Rangeary.sort_ranges(ar)
|
1232
|
+
|
1233
|
+
if inRanges.size < 1
|
1234
|
+
return inRanges
|
1235
|
+
end
|
1236
|
+
|
1237
|
+
newRanges = [inRanges[0]]
|
1238
|
+
|
1239
|
+
inRanges[1..-1].each do |eachr|
|
1240
|
+
prev = newRanges[-1]
|
1241
|
+
|
1242
|
+
if prev.empty? || eachr.empty?
|
1243
|
+
newRanges.push(eachr)
|
1244
|
+
next
|
1245
|
+
# All empty Ranges (potentially except for one) are removed later.
|
1246
|
+
# If all of them are empty, at least one, preferably *not* RangeExtd::NONE
|
1247
|
+
# will remain. To maximize the possibility that one non-RangeExtd::NONE
|
1248
|
+
# empty Range remains, all of empty Ranes are pushed for now.
|
1249
|
+
end
|
1250
|
+
|
1251
|
+
# To deal with Ruby-2.6/2.7 Endless/Beginless Ranges like (5..) (=(5..nil))
|
1252
|
+
# *.end/begin must be comparable in the following comparison.
|
1253
|
+
eachm = _comparable_beginend(eachr) # EACH-M_odified-to-be-comparable
|
1254
|
+
prevm = _comparable_beginend(prev)
|
1255
|
+
|
1256
|
+
case eachm.end <=> prevm.end
|
1257
|
+
when -1 # aka, eachr_end < prev_end
|
1258
|
+
# Do nothing [Totally inclusive]
|
1259
|
+
next
|
1260
|
+
when 0
|
1261
|
+
if (!eachr.exclude_end?) && prev.exclude_end?
|
1262
|
+
# Change the status (:exclude_end => false) for prev
|
1263
|
+
newRanges[-1] = RangeExtd.new(prev.begin, prev.end, :exclude_begin => prev.exclude_begin?, :exclude_end => false)
|
1264
|
+
else
|
1265
|
+
# Do nothing [Totally inclusive]
|
1266
|
+
end
|
1267
|
+
when 1 # aka, eachr_end > prev_end
|
1268
|
+
case eachm.begin <=> prevm.end
|
1269
|
+
when -1 # Connect by combining
|
1270
|
+
newRanges[-1] = RangeExtd.new(prev.begin, eachr.end, :exclude_begin => prev.exclude_begin?, :exclude_end => eachr.exclude_end?)
|
1271
|
+
when 0
|
1272
|
+
if (eachr.exclude_begin?) && (prev.exclude_end?)
|
1273
|
+
newRanges.push(eachr) # [Totally exclude]
|
1274
|
+
else # Connect by combining
|
1275
|
+
newRanges[-1] = RangeExtd.new(prev.begin, eachr.end, :exclude_begin => prev.exclude_begin?, :exclude_end => eachr.exclude_end?)
|
1276
|
+
end
|
1277
|
+
when 1
|
1278
|
+
newRanges.push(eachr) # [Totally exclude]
|
1279
|
+
when nil
|
1280
|
+
raise RangeError "Non-comparable object in Range." # This should not happen, unless a specific object in the Range is not comparable... like user-defined NONE??
|
1281
|
+
else
|
1282
|
+
raise
|
1283
|
+
end # case eachr.begin <=> prev_end
|
1284
|
+
when nil # aka, eachr_end > prev_end
|
1285
|
+
raise RangeError "Non-comparable object in Range." # This should not happen, unless a specific object in the Range is not comparable...
|
1286
|
+
else
|
1287
|
+
raise
|
1288
|
+
end # case eachr_end <=> prev_end
|
1289
|
+
|
1290
|
+
end # inRanges[1..-1].each do |eachr|
|
1291
|
+
|
1292
|
+
_uniq_empty_ranges(newRanges)
|
1293
|
+
end # def _merge_overlaps(inAr)
|
1294
|
+
private :_merge_overlaps
|
1295
|
+
|
1296
|
+
# If nil, returns +RangeExtd::Infinity+ constant
|
1297
|
+
#
|
1298
|
+
# @param val [Object]
|
1299
|
+
# @param is_positive [Boolean] true if for the positive end
|
1300
|
+
# @return [Object] non-nil object.
|
1301
|
+
def _comparable_nil(val, is_positive: true)
|
1302
|
+
return val if !val.nil?
|
1303
|
+
is_positive ? RangeExtd::Infinity::POSITIVE : RangeExtd::Infinity::NEGATIVE
|
1304
|
+
end
|
1305
|
+
private :_comparable_nil
|
1306
|
+
|
1307
|
+
# Called from {Rangeary#_merge_overlaps}.
|
1308
|
+
#
|
1309
|
+
# "uniq" empty Ranges in the given Array and return the Array.
|
1310
|
+
#
|
1311
|
+
# If there is at least one non-empty range, delete all empty ranges.
|
1312
|
+
# If not, leave one of them, preferably not RangeExtd::NONE,
|
1313
|
+
# unless there is no choice.
|
1314
|
+
#
|
1315
|
+
# @param newRanges [Array<RangeExtd>] This is *destructively* modified!!
|
1316
|
+
# @return [Array<RangeExtd>]
|
1317
|
+
def _uniq_empty_ranges(newRanges)
|
1318
|
+
hsFlag = {
|
1319
|
+
:empty? => true,
|
1320
|
+
:klass => nil,
|
1321
|
+
:found? => false,
|
1322
|
+
}
|
1323
|
+
|
1324
|
+
# Search for non-empty range.
|
1325
|
+
newRanges.each do |er|
|
1326
|
+
if er.empty?
|
1327
|
+
if hsFlag[:klass].nil?
|
1328
|
+
obj = er.begin()
|
1329
|
+
if obj.nil?
|
1330
|
+
## Do nothing
|
1331
|
+
else
|
1332
|
+
hsFlag[:klass] = obj.class
|
1333
|
+
end
|
1334
|
+
end
|
1335
|
+
else
|
1336
|
+
hsFlag[:empty?] = false
|
1337
|
+
break
|
1338
|
+
end
|
1339
|
+
end
|
1340
|
+
|
1341
|
+
hsFlag[:found?] = false # Redundant, but for the sake of readability
|
1342
|
+
if hsFlag[:empty?]
|
1343
|
+
# It is all empty, hence delete all but one.
|
1344
|
+
hsFlag[:klass] = NilClass if hsFlag[:klass].nil?
|
1345
|
+
newRanges.delete_if do |er|
|
1346
|
+
if hsFlag[:found?]
|
1347
|
+
true
|
1348
|
+
elsif er.begin().class == hsFlag[:klass]
|
1349
|
+
hsFlag[:found?] = true
|
1350
|
+
false
|
1351
|
+
else
|
1352
|
+
true
|
1353
|
+
end
|
1354
|
+
end
|
1355
|
+
else
|
1356
|
+
# Deletes all the empty ones.
|
1357
|
+
newRanges.delete_if { |er| er.empty? }
|
1358
|
+
end
|
1359
|
+
|
1360
|
+
newRanges
|
1361
|
+
end # def _uniq_empty_ranges(newRanges)
|
1362
|
+
private :_uniq_empty_ranges
|
1363
|
+
|
1364
|
+
|
1365
|
+
# Replaces the meaningless inf..inf Range with NONE
|
1366
|
+
#
|
1367
|
+
# @note The class information (such as Float) is discarded.
|
1368
|
+
#
|
1369
|
+
# @param arin [Array<Range, RangeExtd>]
|
1370
|
+
# @return [Array]
|
1371
|
+
def _replace_inf_inf(arin)
|
1372
|
+
arin.map{ |er|
|
1373
|
+
raise 'contact the code developer' if !defined? er.exclude_end? # Sanity check.
|
1374
|
+
# Note: er.to_a raises RangeError for Ruby 2.6 Endless Range
|
1375
|
+
(_infinites_with_same_polarity?(er.begin, er.end) ? RangeExtd::NONE : er)
|
1376
|
+
}
|
1377
|
+
end
|
1378
|
+
private :_replace_inf_inf
|
1379
|
+
|
1380
|
+
####################
|
1381
|
+
# private_class_method
|
1382
|
+
####################
|
1383
|
+
|
1384
|
+
# True if object is a type of Rangeary
|
1385
|
+
def self.is_rangeary_type?(obj)
|
1386
|
+
obj.respond_to?(:infinities) && obj.class.method_defined?(:first_element)
|
1387
|
+
end
|
1388
|
+
private_class_method :is_rangeary_type?
|
1389
|
+
|
1390
|
+
end # class Rangeary < Array
|
1391
|
+
|
1392
|
+
|
1393
|
+
# Overwrites its equal operator
|
1394
|
+
class Array
|
1395
|
+
alias_method :equals_before_rangeary, :== if ! self.method_defined?(:equals_before_rangeary)
|
1396
|
+
|
1397
|
+
# Updated Array#==
|
1398
|
+
#
|
1399
|
+
# This modifies the original equality operator so that it returns true
|
1400
|
+
# when both self and other are *practically* empty,
|
1401
|
+
# that is, if +#empty_element?+ and/or +empty?+ are true for both, they are equal.
|
1402
|
+
#
|
1403
|
+
# This modification is necessary because {Rangeary#empty?} never returns +true+;
|
1404
|
+
# when it has no Range elements, it holds +RangeExtd::NONE+ or equivalent,
|
1405
|
+
# in whih case {Rangeary#empty_element?} returns +true+.
|
1406
|
+
#
|
1407
|
+
# @param other [Object]
|
1408
|
+
def ==(other)
|
1409
|
+
return true if equals_before_rangeary other
|
1410
|
+
|
1411
|
+
# It was false. Is it?
|
1412
|
+
# eg., (Rangeary[RangeExtd::NONE] == []) is true,
|
1413
|
+
# because Rangeary[] with zero components does not exist!
|
1414
|
+
# Now either other or self is guranteed to be Rangeary.
|
1415
|
+
self_empt = (respond_to?(:empty_element?) ? empty_element? : empty?)
|
1416
|
+
other_empt = (other.respond_to?(:empty_element?) ? other.empty_element? : other.empty?)
|
1417
|
+
self_empt && other_empt
|
1418
|
+
end
|
1419
|
+
end # class Array
|
1420
|
+
|