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.
@@ -0,0 +1,727 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require "range_extd" if !defined? RangeExtd
4
+ require_relative "util/hash_inf.rb"
5
+
6
+ class Rangeary < Array
7
+ # Utility containing {HashInf} module and some private methods for internal use for both Rangeary instance and class methods.
8
+ #
9
+ # External RangeExtd (range_extd) gem must be required.
10
+ # This module is supposed to be used in combination with {Rangeary}.
11
+ #
12
+ # Many methods have been migrated from +rangeary.rb+ so that this module
13
+ # is self-consistent, i.e, this module does not call Rangeary-specific
14
+ # methods except for {Rangeary.initialize}.
15
+ #
16
+ # All methods (but the module {HashInf}) in this module are both
17
+ # included and extended by {Rangeary}. All methods in this module
18
+ # are private, because any public methods in this modules would be
19
+ # exposed as both class and instance methods, which would be confusing to users.
20
+ #
21
+ # Some of the methods in this library are named +_core+, but they may be
22
+ # in practice exactly what the original would be rather than only the core part
23
+ # and the corresponding public method in the main file +rangeary.rb+ does
24
+ # no more than simply calls it. The reason of the structure is
25
+ # simply because this module is both included and extended (a bit tricky combination)!
26
+ module Util
27
+
28
+ # private constants: correspondance to keywords to Range methods
29
+ POSNEG2METHOD = { negative: :begin, positive: :end }
30
+ private_constant :POSNEG2METHOD
31
+
32
+ # private constants: Reverse order of the classes in guessing implicit infinities.
33
+ PRIORITIES = [FalseClass, NilClass, Numeric, RangeExtd::Infinity, Object]
34
+ private_constant :PRIORITIES
35
+
36
+ # private constants: Positive implicit infinities in the reverse order.
37
+ PRIORI_OBJ = [false, nil, Float::INFINITY, RangeExtd::Infinity::POSITIVE, Object.new] # the last one is meaningless!
38
+ # NOTE: if you change the order, make sure to ajust the numbers in _get_adjusted_infinities().
39
+ private_constant :PRIORI_OBJ
40
+
41
+ ####################
42
+ private
43
+ ####################
44
+
45
+ ## Returns infinities literally included in the given Range
46
+ ##
47
+ ## @param ran [Rangeary, Range]
48
+ ## @return [Hash<Object>] 2 keys of :positive and :negative. value of false if not found anything.
49
+ #def _definite_infinities_from_range(ran)
50
+ # return HashInf.construct if ran.is_none? # RangeExtd::NONE
51
+
52
+ # hs =
53
+ # HashInf.construct.map{|k, v|
54
+ # [k, ((v.respond_to?(:infinite?) && v.infinite?) ? v : nil)]
55
+ # }.to_h # This is a standard Hash (NOT HashInf-extended)
56
+
57
+ # stats = hs.map{|k, v| [k, ((false == v) ? nil : :definite)]}.to_h
58
+ #
59
+ # HashInf.construct(hs, status: stats)
60
+ #end
61
+ #private :_definite_infinities_from_range
62
+
63
+ # Get an infinity and status from a single value
64
+ #
65
+ # The returned +status+ is either of the first two {HashInf::STATUS_ORDERS}
66
+ # that is, excluding +nil+; +status+ is never +nil+ because the infinity is
67
+ # always guessable for a "valid" Range, i.e., Range with all elements mutually
68
+ # comparable, except for +RangeExtd::NONE+ .
69
+ # (The caller must judge about +RangeExtd::NONE+)
70
+ #
71
+ # The result will be fed to {HashInf#set_posinega_with_status}.
72
+ #
73
+ # This routine does not (and cannot) check the validity of the infinity,
74
+ # e.g., the positive infinity may be -Float::INFINITY .
75
+ #
76
+ # @param val [Object] nil, if it is a begin/end of a Borderless Range.
77
+ # @param posneg [Symbol] Either :negative or :positive (infinity)
78
+ # @return [Array<Object, Symbol>] 2-element Array of [infinity, :definite|:guessed]
79
+ def _get_infinity_status_single(val, posneg)
80
+ # Two cases of :definite
81
+ if val.nil?
82
+ return [nil, :definite]
83
+ elsif val.respond_to?(:infinite?) && val.infinite?
84
+ return [val, :definite]
85
+ end
86
+
87
+ # The status is now :guessed
88
+ inf =
89
+ case val
90
+ when RangeExtd::Infinity
91
+ val
92
+ when Numeric
93
+ ((:negative == posneg) ? -1 : 1) * Float::INFINITY
94
+ else
95
+ nil
96
+ end
97
+ [inf, :guessed]
98
+ end
99
+ private :_get_infinity_status_single
100
+
101
+
102
+ # Returns infinities taken or guessed from multiple Range-s and Rangeary-s
103
+ #
104
+ # See {#_get_infinities_from_obj} for detail.
105
+ #
106
+ # @param arran [Array<Rangeary, Range>]
107
+ # @return [Hash<Object>] (HashInf) 2 keys of :positive and :negative.
108
+ def _get_infinities_from_multiple(*arran)
109
+ reths = HashInf.construct
110
+
111
+ flatten_no_rangeary_core(arran).each do |rang| # Range, RangeExtd, Rangeary
112
+ _validate_opts_infinities([rang], infs: reths)
113
+ reths.merge_hashinf!(_get_infinities_from_obj(rang))
114
+ end
115
+ reths
116
+ end
117
+ private :_get_infinities_from_multiple
118
+
119
+
120
+ # Core routine for {Rangeary.flatten_no_rangeary}
121
+ #
122
+ # @param arin [Array<Range, RangeExtd, Rangeary, Array>]
123
+ # @return [Array<Range, RangeExtd, Rangeary>]
124
+ def flatten_no_rangeary_core(*arin)
125
+ arret = []
126
+ arin.each do |val|
127
+ if val.respond_to?(:infinities) || val.respond_to?(:exclude_end?)
128
+ arret << val
129
+ elsif val.respond_to?(:flatten)
130
+ arret.concat send(__method__, *val)
131
+ else
132
+ raise ArgumentError, "Argument Array must consist of only Range (RangeExtd) or Array (Rangeary)"
133
+ end
134
+ end
135
+ arret
136
+ end
137
+ private :flatten_no_rangeary_core
138
+
139
+ # Returns infinities taken or guessed from the given Range or Rangeary
140
+ #
141
+ # If the given Range has an infinity (judged with [#infinity?])
142
+ # at its either end, it is respected, and +nil+ is the same.
143
+ # Otherwise, guessed from the elements.
144
+ #
145
+ # The returned Hash extended with HashInf module consists of
146
+ # two keys of +:positive+ and +:negative+ for respective infinities.
147
+ # Also, it has the method {HashInf#definite?}, {HashInf#guessed?},
148
+ # {HashInf#status} and alike to represent the degree of confidence.
149
+ #
150
+ # @param ran [Rangeary, Range]
151
+ # @return [Hash<Object>] (HashInf) 2 keys of :positive and :negative.
152
+ def _get_infinities_from_obj(ran)
153
+ return _get_infinities_from_range(ran) if ran.respond_to? :exclude_end?
154
+ return ran.instance_variable_get("@infinities") if ran.respond_to? :infinities
155
+ # to get the instance variable @infinities, as opposed to Rangeary#infinities
156
+ raise ArgumentError, "Neither Range/RangeExtd nor Rangeary is given."
157
+ end
158
+ private :_get_infinities_from_obj
159
+
160
+ # Returns infinities taken or guessed from elements of the given Range
161
+ #
162
+ # If the given Range has an infinity (judged with [#infinity?])
163
+ # at its either end, it is respected, and +nil+ is the same.
164
+ # Otherwise, guessed from the elements.
165
+ #
166
+ # The returned Hash extended with HashInf module consists of
167
+ # two keys of +:positive+ and +:negative+ for respective infinities.
168
+ # Also, it has the method {HashInf#definite?}, {HashInf#guessed?},
169
+ # {HashInf#status} and alike to represent the degree of confidence.
170
+ #
171
+ # @param ran [Rangeary, Range]
172
+ # @return [Hash<Object>] (HashInf) 2 keys of :positive and :negative.
173
+ def _get_infinities_from_range(ran)
174
+ reths = HashInf.construct
175
+ return reths if ran.is_none? # RangeExtd::NONE
176
+
177
+ POSNEG2METHOD.each_pair do |posneg, metho| # n.b. method() is a Ruby built-in function.
178
+ val = ran.send(metho)
179
+ inf, stat = _get_infinity_status_single(val, posneg)
180
+ reths.set_posinega_with_status(posneg, inf, stat)
181
+ end
182
+
183
+ _get_adjusted_infinities(reths)
184
+ end
185
+ private :_get_infinities_from_range
186
+
187
+ # Returns adjusted infinities
188
+ #
189
+ # "adjusted" means:
190
+ # for example, when a Range of +("b"..RangeExtd::Infinity::POSITIVE)+ is given,
191
+ # the standard guess of infinities (with #{_get_infinity_status_single})
192
+ # are +(nil..RangeExtd::Infinity::POSITIVE)+.
193
+ # However, the two infinities are inconsistent and it is better
194
+ # to be +(RangeExtd::Infinity::NEGATIVE..RangeExtd::Infinity::POSITIVE)+
195
+ # given that the user deliberately set +RangeExtd::Infinity::POSITIVE+.
196
+ # In such a case, the tentative negative infinity "nil" is replaced with
197
+ # +RangeExtd::Infinity::NEGATIVE+ to make everything consistent.
198
+ #
199
+ # In short, this method looks into the infinities at both ends of a Range,
200
+ # validates them, and corrects them if need be.
201
+ #
202
+ # Note that if the infinities at both ends are a user specifies they are respected
203
+ # unless both infinites have the same polarity, in which case the initialized
204
+ # default infinities {HashInf} is returned.
205
+ #
206
+ # Priorities are in the standard order (though {Util::PRIORITIES} is in the reverse order):
207
+ #
208
+ # 1. exact value
209
+ # 2. RangeExtd::Infinity
210
+ # 3. Float::INFINITY
211
+ # 4. nil
212
+ # 5. false
213
+ #
214
+ # This order basically follows the order of unlikeliness of what a user does.
215
+ # For example, +RangeExtd::Infinity+ cannot never be set unless a user deliberately sets it.
216
+ # Therefore, if a user does it, it should be respected. But if a user doesn't,
217
+ # other infinities must be used (in Ruby-2.7). Note +false+ is the initial state.
218
+ #
219
+ # Note that if an exact value is given, unless the other end is ":definite",
220
+ # nil is set.
221
+ #
222
+ # @param ininf [Hash] 2 keys of :positive and :negative.
223
+ # @return [Hash<Object>] (HashInf) 2 keys of :positive and :negative. false if not found anything.
224
+ def _get_adjusted_infinities(ininf)
225
+ reths = HashInf.construct(ininf)
226
+
227
+ # If both infinites have the same polarity, the information is reset.
228
+ return HashInf.construct() if _infinites_with_same_polarity?(*(ininf.values))
229
+
230
+ # Sets the priority Hash (not HashInf).
231
+ hspri = {}
232
+ reths.each_pair do |posneg, inf|
233
+ hspri[posneg] =
234
+ PRIORITIES.each_with_index do |klass, i|
235
+ break i if inf.is_a?(klass)
236
+ end
237
+ raise "Should not happen" if !hspri[posneg]
238
+ end
239
+ return reths if hspri[:positive] == hspri[:negative]
240
+
241
+ if hspri[:positive] < hspri[:negative]
242
+ # :negative has a higher priority
243
+
244
+ # This might happen when
245
+ # the Infinity at an end is a user-specified one (Object) and the other end
246
+ # is RangeExtd::Infinity or Float::INFINITY
247
+ return reths if reths.definite?(:positive)
248
+
249
+ inf =
250
+ case hspri[:negative]
251
+ when 2, 3
252
+ PRIORI_OBJ[hspri[:negative]] # either RangeExtd::Infinity::POSITIVE or Float::INFINITY
253
+ when 4
254
+ # user-defined infinite object is taken into account.
255
+ (reths[:positive].respond_to?(:infinite?) && reths[:positive].infinite?) ? reths[:positive] : nil
256
+ else # should be 1 only ((false..false) may be an exception, though invalid for RangeExted or Rangeary?)
257
+ nil
258
+ end
259
+ reths.set_positive_with_status(inf, :guessed)
260
+ return reths
261
+ end
262
+
263
+ return reths if reths.definite?(:negative) # Very unlikely, but it might happen (see above).
264
+
265
+ # Now, negative infinity is overwritten.
266
+ begin # "begin" just for indentation.
267
+ inf =
268
+ case hspri[:positive]
269
+ when 2, 3
270
+ -PRIORI_OBJ[hspri[:positive]] # either RangeExtd::Infinity::NEGATIVE or -Float::INFINITY
271
+ when 4
272
+ # user-defined infinite object is taken into account.
273
+ (reths[:negative].respond_to?(:infinite?) && reths[:negative].infinite?) ? reths[:negative] : nil
274
+ else
275
+ nil
276
+ end
277
+ end
278
+ reths.set_negative_with_status(inf, :guessed)
279
+
280
+ reths
281
+ end
282
+ private :_get_adjusted_infinities
283
+
284
+ # Validate @infinities from the command-line options.
285
+ #
286
+ # This basically applies +<=>+ operator and if an Exception raises,
287
+ # converts it to ArgumentError.
288
+ # Almost no Object should fail, because +<=>+ is defined in
289
+ # the Object class and it does not fail! For example, +3 <=> "a"+
290
+ # returns +nil+ and so it does not fail.
291
+ #
292
+ # @param arin [Array<Range, RangeExtd, Rangeary>]
293
+ # @param infs [Hash]
294
+ # @return [void]
295
+ # @raise [ArgumentError]
296
+ def _validate_opts_infinities(arin, infs: @infinities)
297
+ infs.each_pair do |ek, my_inf|
298
+ next if !my_inf #|| RangeExtd::Infinity.infinite?(my_inf)
299
+ arin.flatten.each do |er| # Rangeary is flattened.
300
+ next if er.is_none? # Required for Ruby 2.7+ (for updated RangeExtd::NONE for beginless Range)
301
+ [er.begin, er.end].each do |ev|
302
+ next if !ev
303
+ next if (is_num_type?(ev) && is_num_type?(my_inf))
304
+ begin
305
+ case my_inf <=> ev
306
+ when -1, 0, 1
307
+ next
308
+ else # nil or ralse
309
+ next
310
+ end
311
+ rescue
312
+ msg = "invalid parameter for :#{ek} => (#{my_inf.inspect}), incompatible with the range with Range=(#{er.inspect})."
313
+ raise ArgumentError, msg
314
+ end
315
+ end
316
+ end
317
+ end
318
+ end
319
+ private :_validate_opts_infinities
320
+
321
+
322
+ # Returns the candidate @infinities from the input Rangeary
323
+ #
324
+ # If there is any Rangeary in the input.
325
+ #
326
+ # Example return:
327
+ # { :positive => [nil, Float::INFINITY],
328
+ # :negative => [nil, -Float::INFINITY] }
329
+ #
330
+ # Note the standard @infinities is a Hash, but NOT a Hash of Array.
331
+ # This method returns the candidates.
332
+ #
333
+ # @param arin [Array<Hash<Infinity, nil>>] Inherited infinities from the input Rangeary-s
334
+ # @return [Hash<Array>] keys (:positive and :negative) Array#size may not agree between them.
335
+ def _get_cand_infinities(arin)
336
+ hsret = { positive: [], negative: [] }
337
+ arin.each do |ec|
338
+ hsret.each_key do |k|
339
+ hsret[k] << ec[k]
340
+ end
341
+ end
342
+ hsret
343
+ end
344
+ private :_get_cand_infinities
345
+
346
+ # Sort infinities obtained from inherited objects
347
+ #
348
+ # RangeExtd::Infinity is ignored. Float::INFINITY has the lowest priority.
349
+ #
350
+ # @param hs_inherit [Hash<Array>] :positive => Array, etc (From Rangeary-s in the main Array given)
351
+ # @return [Hash<Array>] each key (:(posi|nega)tive) contains the sorted candidate Array.
352
+ def _sort_inherited_infinities_all(hs_inherit)
353
+ hs_inherit.map do |ek, ev|
354
+ [ek, _sort_inherited_infinities_each(ev, key=ek)]
355
+ end.to_h # Ruby 2.1 or later
356
+ end
357
+ private :_sort_inherited_infinities_all
358
+
359
+ # Sort infinities obtained from inherited objects
360
+ #
361
+ # RangeExtd::Infinity is ignored. Float::INFINITY has the lowest priority.
362
+ #
363
+ # @param ar_infs [Array] of Infinities (inherited)
364
+ # @param key [Symbol] :positive or :negative
365
+ # @return [Array]
366
+ def _sort_inherited_infinities_each(ar_infs, key=:positive)
367
+ ar_infs.map {|j|
368
+ (RangeExtd::Infinity === j) ? nil : j
369
+ }.compact.sort{|a,b|
370
+ if is_num_type?(a) && RangeExtd::Infinity.infinite?(a)
371
+ -1
372
+ elsif is_num_type?(b) && RangeExtd::Infinity.infinite?(b)
373
+ 1
374
+ else
375
+ begin
376
+ (key == :positive) ? (a<=>b) : (b<=>a)
377
+ rescue
378
+ 0
379
+ end
380
+ end
381
+ }
382
+ end
383
+ private :_sort_inherited_infinities_each
384
+
385
+ # True if two values are both infinite and have the same polarity
386
+ #
387
+ # Both are assumed to have methods of :positive? and :negative?,
388
+ # if they have a method of +infinite?+ (as +Float::INFINITY+ does).
389
+ #
390
+ # @param v1 [Array]
391
+ # @param v2 [Array]
392
+ def _infinites_with_same_polarity?(v1, v2)
393
+ arran = [v1, v2]
394
+ (( arran.all?{ |ea| RangeExtd::Infinity.infinite?(ea) } &&
395
+ (arran.all?(&:positive?) || arran.all?(&:negative?)) ))
396
+ #(arran.all?(&:nil?))) # allowed in Ruby 2.7+
397
+ end
398
+ private :_infinites_with_same_polarity?
399
+
400
+
401
+ # Core routine for {Rangeary.sort_ranges}
402
+ #
403
+ # @param ar [<Range, RangeExtd>] Arbitrary number.
404
+ # @return [Array<Range, RangeExtd>]
405
+ def sort_ranges_core(*ar)
406
+ ar.flatten.sort{ |a,b|
407
+ err_msg_ab = "invalid parameter (#{a.inspect} or #{b.inspect})."
408
+
409
+ if a.is_none?
410
+ next (b.is_none? ? 0 : -1)
411
+ elsif b.is_none?
412
+ next 1
413
+ end
414
+
415
+ # Since Ruby-2.6, the end can be nil (Endless Range).
416
+ # Before Ruby-2.6, they used to raise Exception (ArgumentError) in Range
417
+ # and so they would not have sneaked in to RangeExtd, either.
418
+ # The following is in that sense meaningful only for Ruby 2.6 and later.
419
+ #
420
+ # Even in Ruby-2.7, this routine need no change, because nil is
421
+ # correctly handled.
422
+ ends = { :a => a, :b => b }
423
+ ends.each_pair do |k, v|
424
+ ends[k] = comparable_beginend_core(v).end
425
+ end
426
+
427
+ # ret = begs[:a] <=> begs[:b]
428
+ ret = a.begin <=> b.begin # Without filtering with comparable_beginend()
429
+ case ret
430
+ when -1, 1
431
+ ret
432
+ when 0
433
+ a_exc_begin = (a.exclude_begin? rescue false)
434
+ b_exc_begin = (b.exclude_begin? rescue false)
435
+ if (a_exc_begin ^ b_exc_begin)
436
+ if a_exc_begin # but not b
437
+ 1
438
+ else
439
+ -1
440
+ end
441
+ else # <= (a.exclude_begin? == b.exclude_begin?)
442
+ ret = ends[:a] <=> ends[:b]
443
+ case ret
444
+ when -1, 1
445
+ ret
446
+ when 0
447
+ if (a.exclude_end? && b.exclude_end?)
448
+ 0
449
+ elsif a.exclude_end? # but not b
450
+ -1
451
+ else # <= b.exclude_end? but not a
452
+ 1
453
+ end # if (a.exclude_end? && b.exclude_end?)
454
+ when nil
455
+ # This should not happen for Range, let alone RangeExtd.
456
+ # But could be the case for a user-defined class.
457
+ if ends[:a].nil? && ends[:b].nil?
458
+ 0
459
+ elsif ends[:a].nil?
460
+ -1
461
+ elsif ends[:b].nil?
462
+ 1
463
+ else
464
+ raise(TypeError, err_msg_ab)
465
+ end
466
+ else # case ret # ends[:a] <=> ends[:b]
467
+ raise(TypeError, err_msg_ab)
468
+ end # case ret # ends[:a] <=> ends[:b]
469
+ end # if (a.exclude_begin? ^ b.exclude_begin?)
470
+ when nil # case ret #(a.begin <=> b.begin)
471
+ if a.begin.nil? && b.begin.nil?
472
+ 0
473
+ elsif a.begin.nil?
474
+ -1
475
+ elsif b.begin.nil?
476
+ 1
477
+ else
478
+ raise(TypeError, err_msg_ab)
479
+ end
480
+ else # case ret #(a.begin <=> b.begin)
481
+ raise(TypeError, err_msg_ab)
482
+ end # case ret #(a.begin <=> b.begin)
483
+ } # ar.sort{ |a,b|
484
+ end # def sort_ranges_core(ar)
485
+ private :sort_ranges_core
486
+
487
+
488
+ # Core routine of {Rangeary.comparable_beginend}
489
+ #
490
+ # @overload self.comparable_beginend(ran, infinities: nil)
491
+ # Range or RangeExtd
492
+ # @param ran [Range, RangeExtd]
493
+ # @param infinities [Hash<Boolean>] two-elements of :positive and :negative
494
+ # @overload self.comparable_beginend(ran, vend, infinities: nil)
495
+ # Begin and End values for Range
496
+ # @param ran [Object]
497
+ # @param vend [Object]
498
+ # @param infinities [Hash<Boolean>] two-elements of :positive and :negative
499
+ #
500
+ # @return [RangeExtd] exclude statuses for begin and end follow the input.
501
+ # @raise [RangeError] if ran is not Range#valid?
502
+ def comparable_beginend_core(ran, vend=Object, infinities: nil)
503
+ if ran.respond_to? :is_none?
504
+ if ran.is_none? # Range should have the method.
505
+ return RangeExtd::NONE
506
+ end
507
+ _ = RangeExtd(ran) # may raise RangeError
508
+ else
509
+ ran = RangeExtd(ran, vend) # may raise RangeError
510
+ end
511
+
512
+ ar_inf = [infinities[:negative], infinities[:positive]] if infinities
513
+
514
+ is_numeric = [:begin, :end].any?{|method| ran.send(method).respond_to? :to_int}
515
+ arret = [:begin, :end].map.with_index{ |method, i|
516
+ obj = ran.send(method)
517
+ if obj.nil?
518
+ if infinities && ar_inf[i] # infinity of nil or false is ignored.
519
+ ar_inf[i]
520
+ else
521
+ # plus-minus is reversed only for "begin", like RangeExtd::Infinity::NEGATIVE
522
+ val = (is_numeric ? Float::INFINITY : RangeExtd::Infinity::POSITIVE)
523
+ (i == 0) ? -val : val
524
+ end
525
+ else
526
+ obj
527
+ end
528
+ }
529
+ RangeExtd(arret[0], arret[1], (ran.respond_to?(:exclude_begin?) ? ran.exclude_begin? : false), ran.exclude_end?)
530
+ end
531
+ private :comparable_beginend_core
532
+
533
+ # Internal version of Conjunction, where +infinities+ can be given.
534
+ #
535
+ # @param r1 [Rangeary, RangeExtd, Range]
536
+ # @param r2 [Rangeary, RangeExtd, Range]
537
+ # @param infs [Hash] ({HashInf}) if supplied, +infinities+ are NOT guessed from the main arguments. Note that there is an instance method called +infinities+()
538
+ # @return [Rangeary]
539
+ def conjunction_core(r1, r2, infs: nil)
540
+
541
+ r1 = Rangeary.new(r1) if ! defined? r1.first_element
542
+ r2 = Rangeary.new(r2) if ! defined? r2.first_element
543
+
544
+ hsinf = (infs || _get_infinities_from_multiple(r1, r2))
545
+ return Rangeary.new(RangeExtd::NONE, **hsinf) if r1.null? || r2.null?
546
+ #return Rangeary.new(r1, **hsinf) if r2.all?
547
+ #return Rangeary.new(r2, **hsinf) if r1.all?
548
+ return(infs ? Rangeary.new(r1, infs) : Rangeary.new(r1, **hsinf)) if r2.all?
549
+ return(infs ? Rangeary.new(r2, infs) : Rangeary.new(r2, **hsinf)) if r1.all?
550
+
551
+ # Initialisation
552
+ a1 = r1.to_a
553
+ a2 = r2.to_a
554
+ rc = Rangeary.new( RangeExtd::NONE, **hsinf ) # hsinf is essential to define @infinities
555
+
556
+ if a1.empty? || a2.empty?
557
+ return rc
558
+ end
559
+
560
+ ### Algorithm
561
+ # Conditions: Both a1 and a2 are sorted in order of #begin().
562
+ # a1 is a reference array of RangeExtd
563
+ # Then, Sigma(i=0..(a2.size-1)) a2[i]*a1[j=0..-1] => Returned Rangeary
564
+ #
565
+ # In reality, I put some tricks to avoid unnecessary calculation
566
+ # for the sake of the processing speed. But essentially
567
+ # it is a very simple algorithm.
568
+ #
569
+ last_a1index = 0
570
+ a2.each do |ea2|
571
+ a1.each_with_index do |ea1, ind|
572
+ # A bit of trick just to avoid unnecessary process
573
+ next if ind < last_a1index # skip
574
+
575
+ # For Ruby-2.6 Endless Range, comparable_end() is employed.
576
+ # Note: this comparison ignores @infinities even if set,
577
+ # because @infinities may not be defined in the arguments!
578
+ # Anyway, nothing should be larger than the upper limit (as tested below),
579
+ # and so this should be fine.
580
+ #
581
+ # Now, considering Ruby-2.7 Beginless Range, too.
582
+ ran1 = comparable_beginend_core(ea1)
583
+ ran2 = comparable_beginend_core(ea2)
584
+ break if ran2.end < ran1.begin # Completely out of range
585
+ if ran1.end < ran2.begin # Completely out of range
586
+ last_a1index = ind if last_a1index < ind
587
+ next
588
+ end
589
+
590
+ # Core - Perform conjunction.
591
+ pq1 = conjunctionRangeExtd(ea1, ea2) # => Rangeary.conjunctionRangeExtd()
592
+ if ! pq1.empty?
593
+ rc += (infs ? Rangeary.new(pq1, infs) : Rangeary.new(pq1))
594
+ last_a1index = ind
595
+ end
596
+ end # a1.each_with_index do |ea1, ind|
597
+ end # a2.each do |ea2|
598
+
599
+ rc
600
+ end # def self.conjunction_core(r1, r2)
601
+ private :conjunction_core
602
+
603
+
604
+ #== Logical conjunction of two RangeExtd
605
+ #
606
+ # To assure this logical conjunction meaningful,
607
+ # the objects that consist of RangeExtd objects have to be
608
+ # monotonic (increase), namely, for any potential element,
609
+ # x_n and x_m, within the given range,
610
+ # (x_n <=> x_m) == 1 if (n > m),
611
+ # have to be true. In other words, the derivative must be always
612
+ # non-negative.
613
+ #
614
+ # For example, (?a..?d) and (?x..?z) satisfies this condition.
615
+ # However, ('d'..'gg') does not, as follows.
616
+ # rd = RangeExtd('d'..'gg')
617
+ # rf = RangeExtd('f'..'h')
618
+ # Rangeary.conjunctionRangeExtd(rd, rf) # => ('f'..'gg')
619
+ #
620
+ # @note If you give a built-in Range object(s) for the arguments,
621
+ # make sure they are valid, that is, Range#valid? returns true.
622
+ #
623
+ #=== Algorithm
624
+ #
625
+ #[st means status. - true if excl; Estimate (Init(in|ex),Fini(in|ex))]
626
+ #[b4 <= af] (sort!)
627
+ #
628
+ #(1) Init = af[0], Init.st=af[0].st
629
+ # If (b4[0]==af[0]), then Init.st = ( b4[0].ex || af[0].ex)
630
+ #(2) Fini = [b4[-1], af[-1]].min, which belongs to (b4|af)
631
+ # If (b4[-1]==af[-1]), then Fini.st = (b4[-1].ex || af[-1].ex), otherwise nil for now.
632
+ #(3) if (Init > Fini) => none.
633
+ # *---* => ....
634
+ # *--* ....
635
+ #(4) if (Init == FiniMax), then Fini.st=b4[-1].st
636
+ # (4-1) if (Init.in&&Fini.in), => Single-Number-Range(InitCand(in))
637
+ # *--I => ...I
638
+ # I--* I...
639
+ # (4-2) else, => none
640
+ #(5) if (Init < FiniMax)
641
+ # (5-1) if Fini belongs to b4, Fini.st=b4[-1].st
642
+ # *---* => .*--*
643
+ # *---*
644
+ # (5-2) if Fini belongs to af, Fini.st=af[-1].st
645
+ # *---* => .*-*
646
+ # *-*
647
+ # (5-3) if Fini belongs to both, Fini.st is defined already.
648
+ # *---* => .*--*
649
+ # *--*
650
+ #
651
+ # @param r1 [RangeExtd] Can be Range
652
+ # @param r2 [RangeExtd] Can be Range
653
+ # @return [RangeExtd]
654
+ #
655
+ def conjunctionRangeExtd(r1, r2)
656
+
657
+ [r1, r2].each do |er|
658
+ return er if er.is_none?
659
+ end
660
+
661
+ r = *( sort_ranges_core([RangeExtd(r1), RangeExtd(r2)]) ) # => Rangeary.sort_ranges
662
+
663
+ ## Note: the end product will be (cBeg(:stBeg), cEnd(:stEnd))
664
+ # where :stBeg and :stEnd mean exclude_(begin|end)?
665
+
666
+ # Set the candidate begin value.
667
+ cBeg = r[1].begin
668
+ if r[0].begin == r[1].begin
669
+ stBeg = (r[1].exclude_begin? || r[0].exclude_begin?)
670
+ else
671
+ stBeg = r[1].exclude_begin?
672
+ end
673
+
674
+ # Set the candidate end value. (Rangeary.comparable_end() for Ruby-2.6 Endless Range)
675
+ # Note: this comparison ignores @infinities even if set,
676
+ # because @infinities may not be defined in the arguments!
677
+ # Anyway, nothing should be larger than the upper limit
678
+ # and so this should be fine.
679
+ if comparable_beginend_core(r[0]).end == comparable_beginend_core(r[1]).end
680
+ cEndOrig = r[1].end
681
+ cEnd = comparable_beginend_core(r[1]).end
682
+ stEnd = (r[0].exclude_end? || r[1].exclude_end?)
683
+ else
684
+ a = [[comparable_beginend_core(r[0]).end, 0, r[0].end], [comparable_beginend_core(r[1]).end, 1, r[1].end]].min
685
+ cEnd = a[0]
686
+ cEndIndex = a[1] # r[cEndIndex] == RangeExtd obj that gives the end of the resultant range.
687
+ cEndOrig = a[2]
688
+ stEnd = nil
689
+ end
690
+
691
+ case cBeg <=> cEnd
692
+ when 1 # cBeg > cEnd
693
+ RangeExtd::NONE
694
+
695
+ when 0 # cBeg == cEnd
696
+ stEnd = r[0].exclude_end?
697
+ if (!stBeg) && (!stEnd)
698
+ RangeExtd(cBeg..cBeg) # Point range
699
+ else
700
+ RangeExtd::NONE
701
+ end
702
+
703
+ when -1 # cBeg < cEnd
704
+ # Now, the range must be (cBeg, cEnd). May need adjustment of the exclude status.
705
+ if stEnd.nil?
706
+ stEnd = r[cEndIndex].exclude_end?
707
+ # else
708
+ # # Already defined.
709
+ end # if stEnd.nil?
710
+
711
+ RangeExtd(cBeg, cEndOrig, :exclude_begin => stBeg, :exclude_end => stEnd)
712
+ else
713
+ raise
714
+ end # case cBeg <=> cEnd
715
+
716
+ end # def self.conjunctionRangeExtd(r1, r2)
717
+ private :conjunctionRangeExtd
718
+
719
+ # True if object is a type of Numeric and comparable
720
+ def is_num_type?(obj)
721
+ Numeric === obj && obj.respond_to?(:between?) && obj.class.method_defined?(:<)
722
+ end
723
+ private :is_num_type?
724
+
725
+ end # module Util
726
+ end # class Rangeary < Array
727
+