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.
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
+