rangeary 0.3.0 → 1.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.
@@ -43,64 +43,127 @@ end
43
43
  # Class to express the multiple ranges.
44
44
  #
45
45
  # The library package <tt>range_extd</tt> is required.
46
- # {https://rubygems.org/gems/range_extd}
46
+ # https://rubygems.org/gems/range_extd
47
+ #
48
+ # An arbitrary number of {Rangeary},
49
+ # {RangeExtd}[http://rubygems.org/gems/range_extd] or Range objects (or its
50
+ # descendant classes) can be supplied for the constructor {#initialize}.
51
+ # Then, an operation of disjunction is performed to get the clean Array of
52
+ # RangeExtd, none of which overlaps with the range.
53
+ #
54
+ # Once it is constructed, {Rangeary} objects are immutable. Any subsequent
55
+ # operations return a new {Rangeary} object. All but few methods of {Array}
56
+ # are inherited, though some methods, such as +push+ do not work because of
57
+ # immutability of {Rangeary} objects.
47
58
  #
48
59
  class Rangeary < Array
49
60
  undef_method :*, :+, :length, :reverse
50
61
 
51
- # Hash with the keys of :negative and :positive
52
- attr_reader :infinities
62
+ # # Hash with the keys of :negative and :positive => becomes a method in Ver.1
63
+ # attr_reader :infinities
53
64
 
65
+ # Constructor
54
66
  #
55
67
  # Arbitrary (positive) number of arguments can be given.
56
68
  # (r1, [r2, ...])
57
69
  #
58
- # It is possible to supply the user-defined infinity objects for both or either
59
- # positive and negative infinity. If one (or more) of the arguments is
60
- # a {Rangeary} object, their infinity values are inherited, unless explicitly
61
- # specified in the optional arguments.
70
+ # == Algorithm about the infinity
71
+ #
72
+ # In {Rangeary} operations, "infinity" is essential. Without it, negation
73
+ # could not be definied. To determine what the positive and negative
74
+ # infinities for the given elements is not a trivial task.
75
+ #
76
+ # Callers can supply user-defined infinity objects for both or either
77
+ # positive and negative infinity and in that case they are accepted
78
+ # as the infinities with the highest priority, though ArgumentError might be
79
+ # issued if they contradict the elements; for example, if a {Rangeary}
80
+ # instance consists of an array of Integer Ranges (RangeExtd) like (3..8),
81
+ # and yet if String "abc" is specified as an infinity, it *contradicts*
82
+ # with the elements in the sense they are not comparable.
83
+ #
84
+ # Alternatively, if the main argument contains one or moer {Rangeary}
85
+ # instances, their infinities are inherited. If their infinities are not
86
+ # identical to one another, warning may be issued if $VERBOSE is true, and
87
+ # the infinities are selected in the following priority order:
88
+ #
89
+ # 1. If one or more user-supplied infinities are found, the smallest and
90
+ # largest ones are accepted for the positive and negative infinities.
91
+ # Note the consistency is not checked; that is, even if one or more of
92
+ # the provided Range elements from which a {Rangeary} instance is about
93
+ # to be constructed exceed the infinities, this does not warn. Obviously
94
+ # subsequent operations may result in some unexpected result. Be warned.
95
+ # 2. Float::INFINITY.
96
+ # 3. RangeExtd::Infinity instances (RangeExtd::Infinity::POSITIVE
97
+ # and RangeExtd::Infinity::NEGATIVE)
98
+ #
99
+ # If none of the main arguments contains {Rangeary}, the elements of each
100
+ # Range are checked whether they have any comparable Numeric instances
101
+ # like Integer (but not Complex), and if so, Float::INFINITY is set
102
+ # as the infinities. Otherwise, it falls back to the defaults of
103
+ # the RangeExtd::Infinity instances.
62
104
  #
63
- # @param inarall [Object] An arbitrary number of either {Rangeary}, {RangeExtd} or {Range} objects (or its subclasses).
64
- # @option **opts [Object] :positive Object for positive infinity. In default {Float::INFINITY} for Numeric Real or else {RangeExtd::Infinity::POSITIVE}.
65
- # @option **opts [Object] :negative Object for negative infinity. In default -{Float::INFINITY} for Numeric Real or else {RangeExtd::Infinity::NEGATIVE}.
105
+ # The registered infinities for each instance is obtained as a Hash with
106
+ # two keys of +:positive+ and +negative+ with the method {#infinities};
107
+ # for example,
108
+ #
109
+ # ran.infinities # => { :positive => RangeExtd::Infinity::POSITIVE,
110
+ # # :negative => "aa" }
111
+ #
112
+ # === Note for Developers about the infinity
113
+ #
114
+ # Instance variable @infinities (Hash) is defined. The elements can be nil;
115
+ # for example, when only the element is RangeExtd::NONE.
116
+ # So, it is recommended to access with the {#infinities} method rather
117
+ # than accessing @infinities directly.
118
+ #
119
+ # At the moment, the infinities are guessed from the elements of the Range
120
+ # it contains and set (+guess_strict: false+ is given to {#_get_infinities}).
121
+ # If +guess_strict: true+ is given, unless a Range contains literally
122
+ # +Float::INFINITY+ etc, (the element of) @infinities is not set (remains nil).
123
+ # I hope it would work even if +guess_strict: true+ is given. However,
124
+ # it is not tested properly! Anyway, to check the consistency of
125
+ # the infinities is desirable, which is performed now, thanks to
126
+ # +guess_strict: false+ given to {#_get_infinities}.
127
+ #
128
+ # @param inarall [Object] An arbitrary number of either {Rangeary}, RangeExtd or Range objects (or its subclasses).
129
+ # @option **opts [Object] :positive Object for positive infinity. In default Float::INFINITY for comparable Numeric or else {RangeExtd::Infinity::POSITIVE}.
130
+ # @option **opts [Object] :negative Object for negative infinity. In default -Float::INFINITY for comparable Numeric or else {RangeExtd::Infinity::NEGATIVE}.
66
131
  def initialize(*inarall, **opts)
132
+ @infinities = { :negative => nil, :positive => nil }
67
133
 
68
134
  if inarall.size < 1
69
- raise ArgumentError, "wrong number of arguments (#{inarall.size} for 1 or more)."
135
+ super [RangeExtd::NONE] # Since Ver.1
136
+ return
137
+ # raise ArgumentError, "wrong number of arguments (#{inarall.size} for 1 or more)."
70
138
  end
71
139
 
72
- hsInheritedObj = {:negative =>nil, :positive =>nil}
73
- hsInheritedAry = {:negative => [], :positive => []}
74
- hsInheritedClass = {:negative => [], :positive => []}
75
- inarall = inarall.map{|i|
76
- if defined?(i.first_element) && defined?(i.infinities)
77
- begin
78
- [:negative, :positive].each do |nega_posi|
79
- hsInheritedAry[nega_posi].push( i.infinities[nega_posi])
80
- hsInheritedClass[nega_posi].push(i.infinities[nega_posi].class)
81
- end
82
- rescue
83
- warn "warning: Rangeary#infinities looks wrong in the input (#{i})."
84
- end
85
- i.to_a
86
- else
87
- i
88
- end
140
+ # Unfold Rangeary into Array of Arrays and convert Range-s into RangeExtd-s
141
+ in_ranges = inarall.map{|i|
142
+ self.class.send(:is_rangeary_type?, i) ? i.to_a : i
89
143
  }.flatten.map{|j|
90
144
  if (defined? j.exclude_begin?)
91
145
  j
92
146
  else
93
147
  begin
94
148
  RangeExtd.new(j)
95
- rescue ArgumentError # Just to change the error message.
149
+ rescue ArgumentError, RangeError # Just to change the error message.
96
150
  raise ArgumentError, "invalid parameter for RangeExtd, hence for Rangeary (#{j.inspect})."
97
151
  end
98
152
  end
99
153
  }
100
154
 
101
- # _merge_overlaps
155
+ # NOTE: Simple map(&:infinities) is not ideal here, because map(&:infinities) tries to
156
+ # supplement when the value is nil. However, in self.conjunction(), it uses map(&:infinities),
157
+ # which is not ideal.
158
+ inherited_infs = inarall.find_all{|i| self.class.send(:is_rangeary_type?, i)}.map{ |ec|
159
+ ec.instance_variable_get(:@infinities)
160
+ }
161
+
162
+ @infinities = _get_infinities(in_ranges, inherited_infs: inherited_infs, guess_strict: false, **opts) # set @infinities (wherever possible)
163
+
164
+ # Call _merge_overlaps
102
165
  begin
103
- arRange = _merge_overlaps( convert2range(inarall) )
166
+ arRange = _merge_overlaps( convert2range(in_ranges) )
104
167
  rescue => err
105
168
  # Trap just to change the type of the exception.
106
169
  raise ArgumentError, err.message, err.backtrace
@@ -111,87 +174,7 @@ class Rangeary < Array
111
174
  end
112
175
 
113
176
  ## Setting @infinities[:negative, :positive]
114
-
115
- # Check inherited objects if there is any, namely if the argument includes any RangeAry object.
116
- # Priority: Float > Others > RangeExtd::Infinity
117
- if hsInheritedAry[:negative].size > 0
118
- [:negative, :positive].each do |es|
119
- iFloat = hsInheritedClass[es].find_index(Float)
120
- if iFloat.nil?
121
- iElse = hsInheritedClass[es].find_index{|i| (i != RangeExtd::Infinity) && (i != Float)}
122
- if iElse.nil?
123
- iRangeInf = hsInheritedClass[es].find_index(RangeExtd::Infinity)
124
- if iRangeInf.nil?
125
- raise "Rangeary#infinities is not set in the input." # Should not happen, as Rangeary#infinities must be set always.
126
- else
127
- hsInheritedObj[es] = hsInheritedAry[es][iRangeInf]
128
- end
129
- else
130
- hsInheritedObj[es] = hsInheritedAry[es][iElse]
131
- end
132
- else
133
- hsInheritedObj[es] = hsInheritedAry[es][iFloat]
134
- end # if iFloat.nil?
135
- end # [:negative, :positive].each do |es|
136
- end # if hsInheritedAry.size > 0
137
-
138
- hsFlag = { :found => {:negative => false, :positive => false} }
139
- hsCand = { :negative => arRange[0].begin, :positive => arRange[-1].end }
140
- infDef = { :negative => RangeExtd::Infinity::NEGATIVE, :positive => RangeExtd::Infinity::POSITIVE }
141
- @infinities={ :negative => nil, :positive => nil }
142
- [:negative, :positive].each do |es|
143
- if (infDef[es] == hsCand[es]) # Can be Float or whatever.
144
- @infinities[es] = hsCand[es] # highest priority
145
- hsFlag[:found][:negative] = true
146
- else
147
- strtmp = ""
148
- [opts[es], hsInheritedObj[es]].each do |opts_or_inherited|
149
- @infinities[es] ||= opts_or_inherited # uses ots[:****tive] or hsInheritedObj[:****tive] if not set.
150
- # Now, checking the compatibility of the infinity value specified (or inherited) with the given range.
151
- if (! opts_or_inherited.nil?) && (opts_or_inherited == @infinities[es]) && (! arRange[0].is_none?)
152
- begin
153
- _ = 0 * (opts_or_inherited <=> hsCand[es])
154
- rescue TypeError
155
- raise ArgumentError, "invalid #{strtmp}parameter for :#{es} => (#{opts_or_inherited.inspect}), incompatible with the range with begin=(#{hsCand[es].inspect})."
156
- end
157
- end
158
- strtmp = "inherited "
159
- end # [opts[es], hsInheritedObj[es]].each do |opts_or_inherited|
160
- end # if (infDef[es] == hsCand[es]) # else
161
- end # [:negative, :positive].each do |es|
162
-
163
- if ! (@infinities[:negative] && @infinities[:positive])
164
- # Either or both @infinities[:negative, :positive] is not set, yet.
165
- # Need to set it now. The possibilities are,
166
- # (1) arRange[0].null? && no opts/inheritance given.
167
- # (2) one of them is given by either arRange or opts or inheritance, but not the other.
168
- # (3) neither of them is given by arRange nor opts nor inheritance.
169
- if arRange[0].null?
170
- [:negative, :positive].each do |es|
171
- @infinities[es] ||= infDef[es]
172
- end
173
- else
174
- # There must be a non-infinity object - we will find it out.
175
- if hsFlag[:found][:negative]
176
- obj2refer = arRange[0].end
177
- else
178
- obj2refer = arRange[-1].begin
179
- end
180
-
181
- # Now, if Numeric === obj2refer, Float::INFINITY is the default.
182
- begin
183
- _dummy = (1.0 < obj2refer)
184
- rescue ArgumentError
185
- # Not Numeric, hence the current infDef is used as it is.
186
- else
187
- # Numeric
188
- infDef = { :negative => -Float::INFINITY, :positive => Float::INFINITY }
189
- end
190
- [:negative, :positive].each do |es|
191
- @infinities[es] ||= infDef[es] # uses default infinity if not set.
192
- end
193
- end # if arRange[0].null?
194
- end # if ! (@infinities[:negative] && @infinities[:positive])
177
+ # set_infinities_legacy(arRange, hsInheritedObj, hsInheritedAry, hsInheritedClass, **opts)
195
178
 
196
179
  super(arRange)
197
180
  self.freeze
@@ -199,13 +182,38 @@ class Rangeary < Array
199
182
 
200
183
  alias_method :triple_equals_orig, :=== if ! self.method_defined?(:triple_equals_orig)
201
184
 
185
+ # Returns @infinities where nil values are replaced with something significant.
186
+ #
187
+ # Best guess approach is taken.
188
+ #
189
+ # @return [Hash] keys of :positive and :negative. The values are never nil.
190
+ def infinities
191
+ defhs = {
192
+ :positive => RangeExtd::Infinity::POSITIVE,
193
+ :negative => RangeExtd::Infinity::NEGATIVE,
194
+ }
195
+ hsret = {}.merge @infinities
196
+ if hsret[:positive].nil?
197
+ return defhs if hsret[:negative].nil?
198
+ hsret[:positive] = (is_num_type?(hsret[:negative]) ? Float::INFINITY : defhs[:positive])
199
+ return hsret
200
+ end
201
+ if hsret[:negative].nil?
202
+ hsret[:negative] = (is_num_type?(hsret[:positive]) ? -Float::INFINITY : defhs[:negative])
203
+ return hsret
204
+ end
205
+
206
+ hsret
207
+ end
208
+
202
209
  # If self covers the entire range?
203
210
  #
204
211
  def all?
205
212
  rfirst = self[0]
206
213
  ((1 == size) &&
207
- (@infinities[:negative] == rfirst.begin) &&
208
- (@infinities[:positive] == rfirst.end) &&
214
+ !rfirst.is_none? &&
215
+ (infinities[:negative] == rfirst.begin) &&
216
+ ((infinities[:positive] == rfirst.end) || rfirst.end.nil?) && # Ruby 2.6 Endless Range
209
217
  (!rfirst.exclude_begin?) &&
210
218
  (!rfirst.exclude_end?))
211
219
  end
@@ -359,13 +367,13 @@ class Rangeary < Array
359
367
  #
360
368
  # @return [Array]
361
369
  # @raise [TypeError] if any of the ranges has no iteration defined, such as, starting from Float.
362
- # @example
370
+ # @example
363
371
  # Rangeary(2...4, 5..6, 8..9).to_a # => [2...4, 5..6, 8..9]
364
372
  # Rangeary(2...4, 5..6, 8..9).flatten # => [2, 3, 5, 6, 8, 9]
365
373
  # Rangeary(2...4, 5..6, 8..9).flatten.reduce(:+) # => 33
366
- def flatten
374
+ def flatten_element
367
375
  to_a.reduce([]){|a,b| a+=b.to_a}
368
- end # def flatten
376
+ end
369
377
 
370
378
 
371
379
  # @return [String]
@@ -515,7 +523,7 @@ class Rangeary < Array
515
523
  alias_method :*, :conjunction
516
524
 
517
525
 
518
- # Negation.
526
+ # Negation (class method).
519
527
  #
520
528
  # @param r [Rangeary, RangeExtd, Range]
521
529
  # @return [Rangeary]
@@ -533,7 +541,8 @@ class Rangeary < Array
533
541
  end
534
542
 
535
543
  arran = []
536
- prevend = nil
544
+ prevend = nil # if "end" is nil, this is substituted with Infinity.
545
+ prevend_orig = Object # For Endless Range (Ruby 2.6), this can be nil.
537
546
  prevst = nil
538
547
  to_a.each do |eachr|
539
548
 
@@ -546,7 +555,7 @@ class Rangeary < Array
546
555
  _beg = 1.0 * eachr.begin
547
556
  return (-Float::INFINITY..Float::INFINITY)
548
557
  rescue TypeError # XXXX can't be coerced into Float
549
- return Rangeary.new(@infinities[:negative]..@infinities[:positive])
558
+ return Rangeary.new(infinities[:negative]..infinities[:positive])
550
559
  end
551
560
  end
552
561
 
@@ -556,22 +565,34 @@ class Rangeary < Array
556
565
  else
557
566
  if prevend.nil?
558
567
  # The returned first range starts from the negative infinity.
559
- arran.push( RangeExtd(@infinities[:negative]..eachr.begin, :exclude_end => (! eachr.exclude_begin?)) )
568
+ ran_tmp = normalized_range_for_negation(infinities[:negative], eachr.begin)
569
+ if RangeExtd::NONE != ran_tmp
570
+ # Avoid (inf..inf) type, which would cause RangeError (in Ruby 2.6) anyway.
571
+ arran.push( RangeExtd(ran_tmp, :exclude_end => (! eachr.exclude_begin?)) )
572
+ end
560
573
  else
561
- arran.push( RangeExtd(prevend, eachr.begin, :exclude_begin => (! prevst), :exclude_end => (! eachr.exclude_begin?)) )
574
+ arran.push( RangeExtd(prevend_orig, eachr.begin, :exclude_begin => (! prevst), :exclude_end => (! eachr.exclude_begin?)) )
575
+ # arran.push( RangeExtd(prevend, eachr.begin, :exclude_begin => (! prevst), :exclude_end => (! eachr.exclude_begin?)) )
562
576
  end
563
577
  end # if (eachr.begin == -Float::INFINITY) || (RangeExtd::NONE == eachr.begin)
564
- prevend = eachr.end
578
+ prevend_orig = eachr.end
579
+ prevend = _comparable_end(eachr) # For Ruby-2.6 Endless Range
565
580
  prevst = eachr.exclude_end?
566
581
  end # to_a.each do |eachr|
567
582
 
568
583
  if (RangeExtd::Infinity::POSITIVE == prevend) # Including Float::INFINITY and other general positive infinities (nb., [#==] is not commutative!).
569
584
  ## Do nothing
570
585
  else
571
- arran.push( RangeExtd.new(prevend..@infinities[:positive], :exclude_begin => (! prevst)) )
586
+ rbeg = (prevend_orig || prevend)
587
+ ran_tmp = normalized_range_for_negation(rbeg, infinities[:positive])
588
+
589
+ if RangeExtd::NONE != ran_tmp
590
+ # Avoid (inf..inf) type, which would cause RangeError (in Ruby 2.6) anyway.
591
+ arran.push( RangeExtd.new(ran_tmp, :exclude_begin => (! prevst)) )
592
+ end
572
593
  end
573
594
 
574
- Rangeary.new(arran, :positive => @infinities[:positive], :negative => @infinities[:negative])
595
+ Rangeary.new(arran, :positive => infinities[:positive], :negative => infinities[:negative])
575
596
  end # def negation()
576
597
 
577
598
  alias_method :~@, :negation
@@ -585,7 +606,7 @@ class Rangeary < Array
585
606
  #
586
607
  # @param r1 [Rangeary, RangeExtd, Range]
587
608
  # @param r2 [Rangeary, RangeExtd, Range]
588
- # @return [Rangeary]
609
+ # @return [Rangeary]
589
610
  def self.conjunction(r1, r2)
590
611
 
591
612
  r1 = Rangeary.new(r1) if ! defined? r1.first_element
@@ -595,20 +616,8 @@ class Rangeary < Array
595
616
  return Rangeary.new(r2) if r1.all?
596
617
 
597
618
  # Getting inherited options (if Rangeary is given) for the later use.
598
- hsInherited = {}
599
- [:negative, :positive].each do |ei|
600
- hsInherited[ei] = [r1.infinities[ei], r2.infinities[ei]].map{|j|
601
- (RangeExtd::Infinity === j) ? nil : j
602
- }.compact.sort{|a,b|
603
- if a.class == Float
604
- -1
605
- elsif b.class == Float
606
- 1
607
- else
608
- 0
609
- end
610
- }[0]
611
- end
619
+ hsInherited = _validate_select_infinities [r1, r2].map(&:infinities)
620
+ # hsInherited = _best_inherited_infinities [r1, r2].map(&:infinities)
612
621
 
613
622
  # Initialisation
614
623
  a1 = r1.to_a
@@ -633,8 +642,14 @@ class Rangeary < Array
633
642
  a1.each_with_index do |ea1, ind|
634
643
  # A bit of trick just to avoid unnecessary process
635
644
  next if ind < last_a1index # skip
636
- break if ea2.end < ea1.begin # Completely out of range
637
- if ea1.end < ea2.begin # Completely out of range
645
+
646
+ # For Ruby-2.6 Endless Range, comparable_end() is employed.
647
+ # Note: this comparison ignores @infinities even if set,
648
+ # because @infinities may not be defined in the arguments!
649
+ # Anyway, nothing should be larger than the upper limit (as tested below),
650
+ # and so this should be fine.
651
+ break if comparable_end(ea2) < ea1.begin # Completely out of range
652
+ if comparable_end(ea1) < ea2.begin # Completely out of range
638
653
  last_a1index = ind if last_a1index < ind
639
654
  next
640
655
  end
@@ -652,6 +667,54 @@ class Rangeary < Array
652
667
  end # def self.conjunction(r1, r2)
653
668
 
654
669
 
670
+ # Returns the infinity "end" that is comparable.
671
+ #
672
+ # Since Ruby-2.6, the Endless Range is introduced.
673
+ # In Ruby definition, the Endless Range er takes a form of er=(5..nil),
674
+ # and accordingly its "end" (or +last+) is nil:
675
+ #
676
+ # (5..nil).end # => nil
677
+ #
678
+ # Then, when you compare the "ends" of two Ranges with the method +<=>+,
679
+ # the result too is nil.
680
+ #
681
+ # In fact, in Ruby, two Range objects are not comparable with eath other,
682
+ # though the operator (or method) +<=>+ is defined for Range:
683
+ #
684
+ # (3..6) <=> (8..9) # => nil
685
+ #
686
+ # (Note this default behaviour is not unreasonable given what one compares
687
+ # is equivocal for Ranges, for example, the size, start or end?
688
+ # Indeed Range does not have a method +Range<=+ and hence +<=>+ is meaningless.)
689
+ #
690
+ # Then, when Range#end returns just nil, which should be interpreted
691
+ # as *Endless* in Range's context, it does not matter for Range.
692
+ # However, it is inconvenient for this class, {Rangeary}!
693
+ # The heart of comparison in {Rangeary} is +<=>+ for the begin first,
694
+ # and then the end if the first comparison results in equal.
695
+ #
696
+ # This method converts Range#end into something comparable when possible.
697
+ # More specifically, if the "end" of the given Range is nil, this returns
698
+ # Infinity (either +Float::INFINITY+ or +RangeExtd::Infinity::POSITIVE+),
699
+ # unless both begin and end are nil (RangeExtd::NONE, nil..nil, nil...nil),
700
+ # in which case nil is returned, and else returns simply +Range#end+.
701
+ #
702
+ # @param ran [Range, RangeExtd]
703
+ # @return [Object]
704
+ def self.comparable_end(ran)
705
+ if ran.class.method_defined?(:is_none?) && ran.is_none?
706
+ nil
707
+ elsif ran.end.nil? && ran.begin.nil?
708
+ nil
709
+ elsif ! ran.end.nil?
710
+ ran.end # Before Ruby 2.6, this is always the case.
711
+ elsif ran.begin.class.method_defined? :to_int
712
+ Float::INFINITY
713
+ else
714
+ RangeExtd::Infinity::POSITIVE
715
+ end
716
+ end
717
+
655
718
  # Returns the array sorted, based on (1) the begin objects and their boundary state,
656
719
  # (2) then the end objects and their boundary state.
657
720
  # {RangeExtd::NONE} comes first, if included in the input array.
@@ -661,6 +724,16 @@ class Rangeary < Array
661
724
  def self.sort_ranges(*ar)
662
725
  ar.flatten.sort{ |a,b|
663
726
  err_msg_ab = "invalid parameter (#{a.inspect} or #{b.inspect})."
727
+
728
+ # Since Ruby-2.6, the end can be nil (Endless Range).
729
+ # Before Ruby-2.6, they used to raise Exception (ArgumentError) in Range
730
+ # and so they would not have sneaked in to RangeExtd, either.
731
+ # The following is in that sense meaningful only for Ruby 2.6 and later.
732
+ ends = { :a => a, :b => b }
733
+ ends.each_pair do |k, v|
734
+ ends[k] = comparable_end(v)
735
+ end
736
+
664
737
  ret = a.begin <=> b.begin
665
738
  case ret
666
739
  when -1, 1
@@ -675,7 +748,7 @@ class Rangeary < Array
675
748
  -1
676
749
  end
677
750
  else # <= (a.exclude_begin? == b.exclude_begin?)
678
- ret = a.end <=> b.end
751
+ ret = ends[:a] <=> ends[:b]
679
752
  case ret
680
753
  when -1, 1
681
754
  ret
@@ -690,18 +763,18 @@ class Rangeary < Array
690
763
  when nil
691
764
  # This should not happen for Range, let alone RangeExtd.
692
765
  # But could be the case for a user-defined class.
693
- if a.end.nil? && b.end.nil?
766
+ if ends[:a].nil? && ends[:b].nil?
694
767
  0
695
- elsif a.end.nil?
768
+ elsif ends[:a].nil?
696
769
  -1
697
- elsif b.end.nil?
770
+ elsif ends[:b].nil?
698
771
  1
699
772
  else
700
773
  raise(TypeError, err_msg_ab)
701
774
  end
702
- else # case ret # a.end <=> b.end
775
+ else # case ret # ends[:a] <=> ends[:b]
703
776
  raise(TypeError, err_msg_ab)
704
- end # case ret # a.end <=> b.end
777
+ end # case ret # ends[:a] <=> ends[:b]
705
778
  end # if (a.exclude_begin? ^ b.exclude_begin?)
706
779
  when nil # case ret #(a.begin <=> b.begin)
707
780
  if a.begin.nil? && b.begin.nil?
@@ -720,7 +793,6 @@ class Rangeary < Array
720
793
  end # def self.sort_ranges(ar)
721
794
 
722
795
 
723
-
724
796
  ####################
725
797
  private
726
798
  ####################
@@ -742,8 +814,362 @@ class Rangeary < Array
742
814
  }
743
815
  end
744
816
 
817
+ # Get the instance variable (Hash) @infinities
818
+ #
819
+ # @param in_infs [Hash] infinities template with keys: :positive, :negative
820
+ # @param arin [Array<Range, RangeExtd, Rangeary>]
821
+ # @param strict: [Boolean] if true (Def: false), set only when the value satisfies RangeExtd::Infinity.infinite?
822
+ # @param leave_existing: [Boolean] if true (Def: false), the existing non-nil values are not updated.
823
+ # @return [Array] Best guess for @infinities
824
+ def _best_guessed_infinities(in_infs, arin, strict: false, leave_existing: false)
825
+ reths = {}.merge in_infs
826
+ # Making the best effort to guess.
827
+ ar_rae = [RangeExtd::Infinity::NEGATIVE, RangeExtd::Infinity::POSITIVE]
828
+ ar_num = [-Float::INFINITY, Float::INFINITY]
829
+ [arin].flatten.each do |ea|
830
+ hs = { negative: ea.begin, positive: ea.end }
831
+ reths.each_key do |ek|
832
+ errmsg = "Contradictory reference value #{hs[ek].inspect} for infinity (Existent: #{reths[ek].inspect}) is found, but ignored."
833
+ next if !hs[ek]
834
+ case reths[ek]
835
+ when nil
836
+ reths[ek] = _guessed_infinity(ek, hs[ek], strict: strict)
837
+ next
838
+ when *ar_rae
839
+ reths[ek] = (_guessed_infinity(ek, hs[ek], strict: strict) || reths[ek]) if !leave_existing # Update whatever.
840
+ next
841
+ when *ar_num
842
+ # Once Float::INFINITY is set, it will unchange.
843
+ next if ar_num.include? _guessed_infinity(ek, hs[ek]) # Consistent
844
+ next if ar_rae.include? _guessed_infinity(ek, hs[ek]) # Inconsistent, but ignore (Prev: Range::Inf, Given: Float::INF)
845
+ warn errmsg if !$VERBOSE.nil?
846
+ next
847
+ else
848
+ # Once User's value is set, it will unchange.
849
+ next if ar_rae.include? _guessed_infinity(ek, hs[ek]) # Inconsistent, but ignore (Prev: User, Given: Infinity)
850
+ warn errmsg if !$VERBOSE.nil?
851
+ next
852
+ end
853
+ end
854
+ end
855
+ reths
856
+ end
857
+ private :_best_guessed_infinities
858
+
859
+ # Instance method version
860
+ #
861
+ # where @infinities are taken into account.
862
+ #
863
+ # @param ran [Range, RangeExtd]
864
+ # @return [Object]
865
+ def _comparable_end(ran)
866
+ if ran.class.method_defined?(:is_none?) && ran.is_none?
867
+ nil
868
+ elsif ran.end.nil? && ran.begin.nil?
869
+ nil
870
+ elsif ! ran.end.nil?
871
+ ran.end # Before Ruby 2.6, this is always the case.
872
+ else
873
+ infinities[:positive] || RangeExtd::Infinity::POSITIVE
874
+ end
875
+ end
876
+ private :_comparable_end
877
+
878
+ # Same as {#_comparable_end} but for begin
879
+ #
880
+ # Beyond the current Ruby (2.6).
881
+ # Needed for {#negation}.
882
+ #
883
+ # @param rbeg [Object]
884
+ # @param rend [Object]
885
+ # @return [Object]
886
+ def _comparable_begin(rbeg, rend)
887
+ if rend.nil? && rbeg.nil?
888
+ nil
889
+ elsif ! rbeg.nil?
890
+ rbeg
891
+ elsif rend.class.method_defined? :to_int
892
+ -Float::INFINITY
893
+ else
894
+ RangeExtd::Infinity::NEGATIVE
895
+ end
896
+ end
897
+ private :_comparable_begin
898
+
899
+
900
+ # Get the instance variable (Hash) @infinities
901
+ #
902
+ # @param arin [Array<Range, RangeExtd, Rangeary>]
903
+ # @param inherited_infs [Array<Hash<Infinity, nil>>] Inherited infinities from the input Rangeary-s
904
+ # @param guess_strict [Boolean] if true, make only strict guess for infinities, namely, unless the existing elements contain "infinity"-type objects, leave it nil; it is passed to {#_best_guessed_infinities}(strict: false)
905
+ # @option **opts [Object] :positive Object for positive infinity. In default {Float::INFINITY} for Numeric Comparable or else {RangeExtd::Infinity::POSITIVE}.
906
+ # @option **opts [Object] :negative Object for negative infinity. In default -{Float::INFINITY} for Numeric Comparable or else {RangeExtd::Infinity::NEGATIVE}.
907
+ # @return [Array] guessed @infinities
908
+ def _get_infinities(arin, inherited_infs: [], guess_strict: false, **opts)
909
+ # Explicitly specified?
910
+ reths = _get_infinities_from_opts(opts)
911
+ if reths
912
+ _validate_opts_infinities(arin, infs: reths)
913
+ # return reths if (reths.all?{ |i| i[1] })
914
+ end
915
+ leave_existing = !!reths
916
+
917
+ hs = {}.merge(@infinities)
918
+ hs.each_key {|k| hs[k] = nil } # :positive, :negative => nil
919
+ reths ||= hs
920
+
921
+ # Read @infinities from Rangeary-s in arin, if exits
922
+ reths = self.class.send(:_validate_select_infinities, inherited_infs, reths)
923
+
924
+ return reths if (reths.all?{ |i| i[1] })
925
+ leave_existing ||= reths.any?{ |i| i[1] }
926
+
927
+ _best_guessed_infinities(reths, arin, strict: guess_strict, leave_existing: leave_existing)
928
+ end
929
+ private :_get_infinities
930
+
931
+ # Returns @infinities from the options.
932
+ #
933
+ # If not specified actually, returns nil.
934
+ #
935
+ # @param opts [Hash]
936
+ # @return [Hash, nil]
937
+ def _get_infinities_from_opts(opts)
938
+ hsret = {}
939
+ if opts.keys.include?(:positive) || opts.keys.include?(:negative)
940
+ if opts[:positive]
941
+ hsret[:positive] = opts[:positive]
942
+ opts[:negative] = -Float::INFINITY if is_num_type? opts[:positive]
943
+ hsret[:negative] ||= opts[:negative]
944
+ return hsret
945
+ elsif opts[:negative]
946
+ hsret[:negative] = opts[:negative]
947
+ opts[:positive] = Float::INFINITY if is_num_type? opts[:negative]
948
+ return hsret
949
+ end
950
+ end
951
+ return nil
952
+ end
953
+ private :_get_infinities_from_opts
954
+
955
+ # Returns the best guess Infinity from a given value (like 5.2 or "a")
956
+ #
957
+ # @param key [Symbol] either :positive or :negative
958
+ # @param val [Object] from which Infinity is guessed.
959
+ # @param strict [Boolean] if strict, unless the value is a kind of Infinite, return nil
960
+ # @return [Hash<Array>] keys (:positive and :negative) Array#size may not agree between them.
961
+ def _guessed_infinity(key, val, strict: false)
962
+ return nil if !val
963
+ return(RangeExtd::Infinity.infinite?(val) ? val : nil) if strict
964
+ return(((key == :positive) ? 1 : -1) * Float::INFINITY) if is_num_type? val
965
+ ((key == :positive) ? RangeExtd::Infinity::POSITIVE : RangeExtd::Infinity::NEGATIVE)
966
+ end
967
+ private :_guessed_infinity
968
+
969
+
970
+ # Instance method version
971
+ def is_num_type?(obj)
972
+ self.class.send(__method__, obj)
973
+ end
974
+ private :is_num_type?
975
+
976
+ # Normalize a Range, which is about to be used for new {Rangeary}
977
+ #
978
+ # @param rbeg [Object]
979
+ # @param rend [Object]
980
+ # @return [Range, RangeExtd::NONE]
981
+ def normalized_range_for_negation(rbeg, rend)
982
+ begin
983
+ ret = rbeg..rend # Range
984
+ rescue RangeError
985
+ # the begin must be nil, after being converted from an Endless Range as in Ruby 2.6
986
+ ret = (_comparable_begin(rbeg, rend)..rend) # Range
987
+ # rescue ArgumentError
988
+ ## NOTE: If this happens, something is gone wrong!!
989
+ end
990
+ return RangeExtd::NONE if same_infinities?(ret.begin, ret.end)
991
+ ret
992
+ end
993
+ private :normalized_range_for_negation
994
+
995
+ # True if both are infinities and in the same parity
996
+ #
997
+ # @param c1 [Object]
998
+ # @param c2 [Object]
999
+ def same_infinities?(c1, c2)
1000
+ arin = [c1, c2]
1001
+ arin.all?{ |ec| RangeExtd::Infinity.infinite?(ec) } &&
1002
+ (arin.all?(&:positive?) || arin.all?(&:negative?))
1003
+ end
1004
+ private :same_infinities?
1005
+
1006
+ # Validate @infinities from the options.
1007
+ #
1008
+ # If fails, raise Exception.
1009
+ #
1010
+ # @param arin [Array<Range, RangeExtd, Rangeary>]
1011
+ # @param infs [Hash]
1012
+ # @return [void]
1013
+ # @raise [ArgumentError]
1014
+ def _validate_opts_infinities(arin, infs: @infinities)
1015
+ infs.each_pair do |ek, my_inf|
1016
+ next if !my_inf #|| RangeExtd::Infinity.infinite?(my_inf)
1017
+ arin.flatten.each do |er|
1018
+ [er.begin, er.end].each do |ev|
1019
+ next if !ev
1020
+ next if (is_num_type?(ev) && is_num_type?(my_inf))
1021
+ begin
1022
+ case my_inf <=> ev
1023
+ when -1, 0, 1
1024
+ next
1025
+ else # nil
1026
+ end
1027
+ rescue # NoMethodError
1028
+ end
1029
+ msg = "invalid parameter for :#{ek} => (#{my_inf.inspect}), incompatible with the range with Range=(#{er.inspect})."
1030
+ raise ArgumentError, msg
1031
+ end
1032
+ end
1033
+ end
1034
+ end
1035
+ private :_validate_opts_infinities
1036
+
1037
+
1038
+ # Legacy routine to set the instance variable (Hash) @infinities
1039
+ #
1040
+ # Maybe more complete?
1041
+ #
1042
+ # @param [Array]
1043
+ # @param [Hash]
1044
+ # @param [Hash]
1045
+ # @param [Hash]
1046
+ # @return [void]
1047
+ def set_infinities_old(arRange, hsInheritedObj, hsInheritedAry, hsInheritedClass, **opts)
1048
+
1049
+ ### The following is required in the caller before calling this routine
1050
+ ## inarall is the arguement received by initialize()
1051
+ #
1052
+ #
1053
+ # hsInheritedObj = {:negative =>nil, :positive =>nil}
1054
+ # hsInheritedAry = {:negative => [], :positive => []}
1055
+ # hsInheritedClass = {:negative => [], :positive => []}
1056
+ # inarall = inarall.map{|i|
1057
+ # if defined?(i.first_element) && defined?(i.infinities)
1058
+ # begin
1059
+ # [:negative, :positive].each do |nega_posi|
1060
+ # hsInheritedAry[nega_posi].push( i.infinities[nega_posi])
1061
+ # hsInheritedClass[nega_posi].push(i.infinities[nega_posi].class)
1062
+ # end
1063
+ # rescue
1064
+ # warn "warning: Rangeary#infinities looks wrong in the input (#{i})."
1065
+ # end
1066
+ # i.to_a
1067
+ # else
1068
+ # i
1069
+ # end
1070
+ # }.flatten.map{|j|
1071
+ # if (defined? j.exclude_begin?)
1072
+ # j
1073
+ # else
1074
+ # begin
1075
+ # RangeExtd.new(j)
1076
+ # rescue ArgumentError, RangeError # Just to change the error message.
1077
+ # raise ArgumentError, "invalid parameter for RangeExtd, hence for Rangeary (#{j.inspect})."
1078
+ # end
1079
+ # end
1080
+ # }
1081
+
1082
+ # Check inherited objects if there is any, namely if the argument includes any RangeAry object.
1083
+ # Priority: Float > Others > RangeExtd::Infinity
1084
+ if hsInheritedAry[:negative].size > 0
1085
+ [:negative, :positive].each do |es|
1086
+ iFloat = hsInheritedClass[es].find_index(Float)
1087
+ if iFloat.nil?
1088
+ iElse = hsInheritedClass[es].find_index{|i| (i != RangeExtd::Infinity) && (i != Float)}
1089
+ if iElse.nil?
1090
+ iRangeInf = hsInheritedClass[es].find_index(RangeExtd::Infinity)
1091
+ if iRangeInf.nil?
1092
+ raise "Rangeary#infinities is not set in the input." # Should not happen, as Rangeary#infinities must be set always.
1093
+ else
1094
+ hsInheritedObj[es] = hsInheritedAry[es][iRangeInf]
1095
+ end
1096
+ else
1097
+ hsInheritedObj[es] = hsInheritedAry[es][iElse]
1098
+ end
1099
+ else
1100
+ hsInheritedObj[es] = hsInheritedAry[es][iFloat]
1101
+ end # if iFloat.nil?
1102
+ end # [:negative, :positive].each do |es|
1103
+ end # if hsInheritedAry.size > 0
1104
+
1105
+ # Determines what the infinities are: either Float::INFINITY or RangeExtd::Infinity::(POSI|NEGA)TIVE
1106
+ hsFlag = { :found => {:negative => false, :positive => false} }
1107
+ hsCand = {
1108
+ :negative => arRange[0].begin,
1109
+ :positive => _comparable_end(arRange[-1]),
1110
+ # :pos_orig => arRange[-1].end, # may be nil in Ruby-2.6
1111
+ }
1112
+ infDef = { :negative => RangeExtd::Infinity::NEGATIVE, :positive => RangeExtd::Infinity::POSITIVE }
1113
+ @infinities={ :negative => nil, :positive => nil }
1114
+ [:negative, :positive].each do |es|
1115
+ if (infDef[es] == hsCand[es]) # Can be Float or whatever.
1116
+ @infinities[es] = hsCand[es] # highest priority
1117
+ hsFlag[:found][:negative] = true
1118
+ else
1119
+ strtmp = ""
1120
+ [opts[es], hsInheritedObj[es]].each do |opts_or_inherited|
1121
+ @infinities[es] ||= opts_or_inherited # uses ots[:****tive] or hsInheritedObj[:****tive] if not set.
1122
+ # Now, checking the compatibility of the infinity value specified (or inherited) with the given range.
1123
+ if (! opts_or_inherited.nil?) && (opts_or_inherited == @infinities[es]) && (! arRange[0].is_none?)
1124
+ begin
1125
+ _ = 0 * (opts_or_inherited <=> hsCand[es])
1126
+ rescue TypeError
1127
+ raise ArgumentError, "invalid #{strtmp}parameter for :#{es} => (#{opts_or_inherited.inspect}), incompatible with the range with begin=(#{hsCand[es].inspect})."
1128
+ end
1129
+ end
1130
+ strtmp = "inherited "
1131
+ end # [opts[es], hsInheritedObj[es]].each do |opts_or_inherited|
1132
+ end # if (infDef[es] == hsCand[es]) # else
1133
+ end # [:negative, :positive].each do |es|
1134
+
1135
+ if ! (@infinities[:negative] && @infinities[:positive])
1136
+ # Either or both @infinities[:negative, :positive] is not set, yet.
1137
+ # Need to set it now. The possibilities are,
1138
+ # (1) arRange[0].null? && no opts/inheritance given.
1139
+ # (2) one of them is given by either arRange or opts or inheritance, but not the other.
1140
+ # (3) neither of them is given by arRange nor opts nor inheritance.
1141
+ if arRange[0].null?
1142
+ [:negative, :positive].each do |es|
1143
+ @infinities[es] ||= infDef[es]
1144
+ end
1145
+ else
1146
+ # There must be a non-infinity object - we will find it out.
1147
+ if hsFlag[:found][:negative]
1148
+ obj2refer = _comparable_end(arRange[0]) # For Ruby-2.6 Endless Range
1149
+ # obj2refer = arRange[0].end
1150
+ else
1151
+ obj2refer = arRange[-1].begin
1152
+ end
1153
+
1154
+ # Now, if Numeric === obj2refer, Float::INFINITY is the default.
1155
+ begin
1156
+ _dummy = (1.0 < obj2refer)
1157
+ rescue ArgumentError
1158
+ # Not Numeric, hence the current infDef is used as it is.
1159
+ else
1160
+ # Numeric
1161
+ infDef = { :negative => -Float::INFINITY, :positive => Float::INFINITY }
1162
+ end
1163
+ [:negative, :positive].each do |es|
1164
+ @infinities[es] ||= infDef[es] # uses default infinity if not set.
1165
+ end
1166
+ end # if arRange[0].null?
1167
+ end # if ! (@infinities[:negative] && @infinities[:positive])
1168
+ end
1169
+ private :set_infinities_old
745
1170
 
746
1171
  # Called from {Rangeary#initialize}.
1172
+ #
747
1173
  # Process the array of RangeExtd and return the new one, in which
748
1174
  # overlapped ranges are merged accordingly.
749
1175
  #
@@ -751,6 +1177,9 @@ class Rangeary < Array
751
1177
  # As a priority, an empty range with a definite class is left,
752
1178
  # but if there is none, RangeExtd::NONE will be left.
753
1179
  #
1180
+ # Note that (Inf..Inf) or (-Inf..-Inf) is replaced with +RangeExtd::NONE+,
1181
+ # which then will be truncated.
1182
+ #
754
1183
  # @param inAr [Array<RangeExtd,Range>]
755
1184
  # @return [Array]
756
1185
  def _merge_overlaps(inAr)
@@ -760,7 +1189,7 @@ class Rangeary < Array
760
1189
  #[st means status.]
761
1190
  #(0) (prev[0]) and (prev[0].status) unchanged.
762
1191
  #(1) if (now[-1]< prev[-1]), do nothing. [Totally inclusive]
763
- # I---* => I---*
1192
+ # I---* => I---*
764
1193
  # *--*
765
1194
  #(2) if (now[-1]== prev[-1])
766
1195
  # (2-1) AND if (now[-1].st? || prev[-1].st?), prev[-1].st=T [Nearly inclusive]
@@ -787,8 +1216,8 @@ class Rangeary < Array
787
1216
  # *--*
788
1217
  #
789
1218
 
790
- # inRanges = sort_arrange( inAr )
791
- inRanges = self.class.sort_ranges( inAr ).map{|i| (RangeExtd === i) ? i : RangeExtd(i) } # => Rangeary.sort_ranges(ar)
1219
+ inRanges = _replace_inf_inf(inAr) # Replace meaningless inf..inf etc.
1220
+ inRanges = self.class.sort_ranges(inRanges).map{|i| (RangeExtd === i) ? i : RangeExtd(i) } # => Rangeary.sort_ranges(ar)
792
1221
 
793
1222
  if inRanges.size < 1
794
1223
  return inRanges
@@ -798,8 +1227,14 @@ class Rangeary < Array
798
1227
 
799
1228
  inRanges[1..-1].each do |eachr|
800
1229
  prev = newRanges[-1]
801
- case eachr.end <=> prev.end
802
- when -1 # aka, eachr.end < prev.end
1230
+
1231
+ # To deal with Ruby-2.6 Endless Range like (5..) (=(5..nil))
1232
+ # *.end is guaranteed not to be false.
1233
+ eachr_end = _comparable_end(eachr)
1234
+ prev_end = _comparable_end(prev)
1235
+
1236
+ case eachr_end <=> prev_end
1237
+ when -1 # aka, eachr_end < prev_end
803
1238
  # Do nothing [Totally inclusive]
804
1239
  when 0
805
1240
  if (!eachr.exclude_end?) && prev.exclude_end?
@@ -808,8 +1243,8 @@ class Rangeary < Array
808
1243
  else
809
1244
  # Do nothing [Totally inclusive]
810
1245
  end
811
- when 1 # aka, eachr.end > prev.end
812
- case eachr.begin <=> prev.end
1246
+ when 1 # aka, eachr_end > prev_end
1247
+ case eachr.begin <=> prev_end
813
1248
  when -1 # Connect by combining
814
1249
  newRanges[-1] = RangeExtd.new(prev.begin, eachr.end, :exclude_begin => prev.exclude_begin?, :exclude_end => eachr.exclude_end?)
815
1250
  when 0
@@ -824,12 +1259,12 @@ class Rangeary < Array
824
1259
  newRanges.push(eachr) # must be RangeExtd::NONE (or user-defined equivalent)
825
1260
  else
826
1261
  raise
827
- end # case eachr.begin <=> prev.end
828
- when nil # aka, eachr.end > prev.end
1262
+ end # case eachr.begin <=> prev_end
1263
+ when nil # aka, eachr_end > prev_end
829
1264
  newRanges.push(eachr) # must be RangeExtd::NONE (or user-defined equivalent)
830
1265
  else
831
1266
  raise
832
- end # case eachr.end <=> prev.end
1267
+ end # case eachr_end <=> prev_end
833
1268
 
834
1269
  end # inRanges[1..-1].each do |eachr|
835
1270
 
@@ -885,11 +1320,43 @@ class Rangeary < Array
885
1320
 
886
1321
  private :_merge_overlaps
887
1322
 
1323
+ # Replaces the invalid inf..inf Range with NONE
1324
+ #
1325
+ # @param arin [Array<Range, RangeExtd>]
1326
+ # @return [Array]
1327
+ def _replace_inf_inf(arin)
1328
+ arin.map{ |er|
1329
+ raise 'contact the code developer' if !defined? er.exclude_end? # Sanity check.
1330
+ arran = [er.begin, er.end] # to_a raises RangeError for Ruby 2.6 Endless Range
1331
+ if (( arran.all?{ |ea| RangeExtd::Infinity.infinite?(ea) } &&
1332
+ (arran.all?(&:positive?) || arran.all?(&:negative?)) ) ||
1333
+ (arran.all?(&:nil?)))
1334
+ RangeExtd::NONE
1335
+ else
1336
+ er
1337
+ end
1338
+ }
1339
+ end
1340
+ private :_replace_inf_inf
888
1341
 
889
1342
  ####################
890
1343
  # private_class_method
891
1344
  ####################
892
1345
 
1346
+ # Sort infinities obtained from inherited objects and returns the best one
1347
+ #
1348
+ # RangeExtd::Infinity is ignored. Float::INFINITY has the lowest priority.
1349
+ #
1350
+ # @param *ar_infs [Array] of Infinities Hash (inherited)
1351
+ # @return [Hash] each key (:(posi|nega)tive) contains a single value (potentially null) for infinity.
1352
+ def self._best_inherited_infinities(*ar_infs)
1353
+ hsar = _sort_inherited_infinities_all( _get_cand_infinities(ar_infs.flatten) )
1354
+ hsar.map{ |k, ev|
1355
+ [k, ev[0]]
1356
+ }.to_h # Ruby 2.1 or later
1357
+ end
1358
+ private_class_method :_best_inherited_infinities
1359
+
893
1360
  #== Logical conjunction of two RangeExtd
894
1361
  #
895
1362
  # To assure this logical conjunction meaningful,
@@ -948,7 +1415,6 @@ class Rangeary < Array
948
1415
  end
949
1416
 
950
1417
  r = *( sort_ranges([RangeExtd(r1), RangeExtd(r2)]) ) # => Rangeary.sort_ranges
951
- # r = *( sort_arrange([RangeExtd(r1), RangeExtd(r2)]) )
952
1418
 
953
1419
  ## Note: the end product will be (cBeg(:stBeg), cEnd(:stEnd))
954
1420
  # where :stBeg and :stEnd mean exclude_(begin|end)?
@@ -961,14 +1427,20 @@ class Rangeary < Array
961
1427
  stBeg = r[1].exclude_begin?
962
1428
  end
963
1429
 
964
- # Set the candidate end value.
965
- if r[0].end == r[1].end
966
- cEnd = r[1].end
1430
+ # Set the candidate end value. (comparable_end() for Ruby-2.6 Endless Range)
1431
+ # Note: this comparison ignores @infinities even if set,
1432
+ # because @infinities may not be defined in the arguments!
1433
+ # Anyway, nothing should be larger than the upper limit
1434
+ # and so this should be fine.
1435
+ if comparable_end(r[0]) == comparable_end(r[1])
1436
+ cEndOrig = r[1].end
1437
+ cEnd = comparable_end(r[1])
967
1438
  stEnd = (r[0].exclude_end? || r[1].exclude_end?)
968
1439
  else
969
- a = [[r[0].end, 0], [r[1].end, 1]].min
1440
+ a = [[comparable_end(r[0]), 0, r[0].end], [comparable_end(r[1]), 1, r[1].end]].min
970
1441
  cEnd = a[0]
971
1442
  cEndIndex = a[1] # r[cEndIndex] == RangeExtd obj that gives the end of the resultant range.
1443
+ cEndOrig = a[2]
972
1444
  stEnd = nil
973
1445
  end
974
1446
 
@@ -992,219 +1464,180 @@ class Rangeary < Array
992
1464
  # # Already defined.
993
1465
  end # if stEnd.nil?
994
1466
 
995
- RangeExtd(cBeg, cEnd, :exclude_begin => stBeg, :exclude_end => stEnd)
1467
+ RangeExtd(cBeg, cEndOrig, :exclude_begin => stBeg, :exclude_end => stEnd)
996
1468
  else
997
1469
  raise
998
1470
  end # case cBeg <=> cEnd
999
1471
 
1000
1472
  end # def self.conjunctionRangeExtd(r1, r2)
1001
-
1002
1473
  private_class_method :conjunctionRangeExtd
1003
1474
 
1004
-
1005
- # Logical conjunction of two Rangeary
1006
- #
1007
- #===Algorithm
1008
- #
1009
- # r1 == [p1, p2, p3, ...] # Array of RangeExtd
1010
- # r2 == [q1, q2, q3, ...]
1011
- # rc = [] # Initial value of Conjunction r1*r2
1012
- # 1. q1 = r2.shift
1013
- # 2. r1.delete_if(pi.end<q1.begin) -> (r1==[P1,P2(>q1),P3,...])
1014
- # 3. make conjunction between RangeExtd P1*q1 => pq1
1015
- # 4. rc+=Rangeary.new(pq1)
1016
- # 5a. if q1.end<P1.end, will do P1<->q2 next, hence return to 1 (will do r2.shift).
1017
- # *--* => *--* + nil
1018
- # *--* *--* .... *--*
1019
- # *---* => .O--* + -*
1020
- # *---* *--* ..... *--* ...-* ----
1021
- # 5b. if q1.end==P1.end, r1.shift, return to 1 (will do r2.shift).
1022
- # *--* *-- => .... *-- + ---* ---
1023
- # *---* *--* ..... *--* .---* ----
1024
- # 5c. if q1.end>P1.end, will do P2<->q1, hence r1.shift, return to 2.
1025
- # *---* *-- => ..... *--
1026
- # *---- .O---
1475
+ # Returns the candidate @infinities from the input Rangeary
1027
1476
  #
1028
- # @return [Rangeary]
1029
- def self.conjunction_orig(r1, r2)
1030
-
1031
- r1 = Rangeary.new(r1) if ! defined? r1.first_element
1032
- r2 = Rangeary.new(r2) if ! defined? r2.first_element
1033
-
1034
- # Getting inherited options (if Rangeary is given) for the later use.
1035
- hsInherited = {}
1036
- [:negative, :positive].each do |ei|
1037
- hsInherited[ei] = [r1.infinities[ei], r2.infinities[ei]].map{|j|
1038
- (RangeExtd::Infinity === j) ? nil : j
1039
- }.compact.sort{|a,b|
1040
- if a.class == Float
1041
- -1
1042
- elsif b.class == Float
1043
- 1
1044
- else
1045
- 0
1046
- end
1047
- }[0]
1048
- end
1049
-
1050
- # Initialisation
1051
- a1 = r1.to_a
1052
- a2 = r2.to_a
1053
- rc = Rangeary.new( RangeExtd::NONE, hsInherited )
1054
-
1055
- if a1.empty? || a2.empty?
1056
- return rc
1477
+ # If there is any Rangeary in the input.
1478
+ #
1479
+ # Example return:
1480
+ # { :positive => [nil, Float::INFINITY],
1481
+ # :negative => [nil, -Float::INFINITY] }
1482
+ #
1483
+ # Note the standard @infinities is a Hash, but NOT a Hash of Array.
1484
+ # This method returns the candidates.
1485
+ #
1486
+ # @param arin [Array<Hash<Infinity, nil>>] Inherited infinities from the input Rangeary-s
1487
+ # @return [Hash<Array>] keys (:positive and :negative) Array#size may not agree between them.
1488
+ def self._get_cand_infinities(arin)
1489
+ hsret = { positive: [], negative: [] }
1490
+ arin.each do |ec|
1491
+ hsret.each_key do |k|
1492
+ hsret[k] << ec[k]
1493
+ end
1057
1494
  end
1495
+ hsret
1496
+ end
1497
+ private_class_method :_get_cand_infinities
1058
1498
 
1059
- # Main loop
1060
- hsFlag = {:a2shift => true}
1061
- while true
1062
- if hsFlag[:a2shift]
1063
- if a2.size > 0
1064
- q1 = a2.shift
1065
- else
1066
- break
1067
- end
1068
- end
1069
- index_valid = a1.find_index{|i| i.begin >= q1.begin }
1070
- if (! index_valid.nil?) && index_valid > 1
1071
- a1.shift(index_valid)
1072
- end
1499
+ # True if object is a type of Rangeary
1500
+ def self.is_rangeary_type?(obj)
1501
+ obj.respond_to?(:infinities) && obj.class.method_defined?(:first_element)
1502
+ end
1503
+ private_class_method :is_rangeary_type?
1073
1504
 
1074
- if a1.size < 1
1075
- break
1076
- end
1077
- p1 = a1[0]
1505
+ # True if object is a type of Numeric and comparable
1506
+ def self.is_num_type?(obj)
1507
+ Numeric === obj && obj.respond_to?(:between?) && obj.class.method_defined?(:<)
1508
+ end
1509
+ private_class_method :is_num_type?
1078
1510
 
1079
- # Core - Perform conjunction.
1080
- pq1 = Rangeary.conjunctionRangeExtd(p1, q1)
1081
- rc += Rangeary.new(pq1)
1511
+ # Sort infinities obtained from inherited objects
1512
+ #
1513
+ # RangeExtd::Infinity is ignored. Float::INFINITY has the lowest priority.
1514
+ #
1515
+ # @param hs_inherit [Hash<Array>] :positive => Array, etc (From Rangeary-s in the main Array given)
1516
+ # @return [Hash<Array>] each key (:(posi|nega)tive) contains the sorted candidate Array.
1517
+ def self._sort_inherited_infinities_all(hs_inherit)
1518
+ hs_inherit.map do |ek, ev|
1519
+ [ek, _sort_inherited_infinities_each(ev, key=ek)]
1520
+ end.to_h # Ruby 2.1 or later
1521
+ end
1522
+ private_class_method :_sort_inherited_infinities_all
1082
1523
 
1083
- case q1.end <=> p1.end
1084
- when -1 # q1.end < p1.end
1085
- hsFlag[:a2shift] = true
1086
- when 0
1087
- if a1.size > 0
1088
- a1.shift
1089
- else
1090
- break
1091
- end
1092
- hsFlag[:a2shift] = true
1093
- when 1 # q1.end > p1.end
1094
- if a1.size > 0
1095
- a1.shift
1096
- else
1097
- break
1098
- end
1099
- hsFlag[:a2shift] = false
1524
+ # Sort infinities obtained from inherited objects
1525
+ #
1526
+ # RangeExtd::Infinity is ignored. Float::INFINITY has the lowest priority.
1527
+ #
1528
+ # @param ar_infs [Array] of Infinities (inherited)
1529
+ # @param key [Symbol] :positive or :negative
1530
+ # @return [Array]
1531
+ def self._sort_inherited_infinities_each(ar_infs, key=:positive)
1532
+ ar_infs.map {|j|
1533
+ (RangeExtd::Infinity === j) ? nil : j
1534
+ }.compact.sort{|a,b|
1535
+ if is_num_type?(a) && RangeExtd::Infinity.infinite?(a)
1536
+ -1
1537
+ elsif is_num_type?(b) && RangeExtd::Infinity.infinite?(b)
1538
+ 1
1100
1539
  else
1101
- raise
1102
- end # case q1.end <=> p1.end
1103
- end # while (a1.size > 0) || (a2.size > 0)
1104
-
1105
- rc
1540
+ begin
1541
+ (key == :positive) ? (a<=>b) : (b<=>a)
1542
+ rescue
1543
+ 0
1544
+ end
1545
+ end
1546
+ }
1106
1547
  end
1107
- private_class_method :conjunction_orig
1108
-
1548
+ private_class_method :_sort_inherited_infinities_each
1109
1549
 
1110
- # Subtract a Range from an Array of Range objects.
1111
- def self.subtractRange(arRng, r2sub)
1550
+ # Validate the infinities by Options and inherited and select the best
1551
+ #
1552
+ # If Option is specified, that has the priority.
1553
+ # Among the inherited, the youngest non-nil one has the highest priority.
1554
+ #
1555
+ # If there are any inconsistencies, issue a warning message, if $VERBOSE==true.
1556
+ #
1557
+ # Note this method does not look at the contents of the Range Array given.
1558
+ #
1559
+ # @param ar_inherit [Array<Hash<Infinity, nil>>] Inherited infinities from the input Rangeary-s
1560
+ # @param hs_opts [Hash, nil] :positive, :negative, specified by the option to {Rangeary.initialize}
1561
+ # @return [Hash] keys (:positive and :negative) with a single value for each
1562
+ def self._validate_select_infinities(ar_inherit, hs_opts=nil)
1563
+ hs_inherit = _get_cand_infinities(ar_inherit)
1564
+ if !hs_opts
1565
+ hs_opts = hs_inherit.map{ |ek, ev|
1566
+ [hs_inherit[ek], nil]
1567
+ }.to_h # Ruby 2.1 or later
1568
+ end
1569
+ # e.g., hs_inherit[:positive] == [ "z"(From-Option), nil(Inherited), "y"(Inherited), INFINITY(inherited) ]
1570
+
1571
+ # Selection
1572
+ hsret = _sort_inherited_infinities_all( hs_inherit ).map{ |ek, ev|
1573
+ [ek, ([hs_opts[ek]]+ev).compact[0]]
1574
+ }.to_h # Ruby 2.1 or later
1575
+
1576
+ # Validation (for warning, issued when $VERBOSE==true)
1577
+ hs_inherit.each_pair do |ek, ev|
1578
+ ev_uniq = ([hs_opts[ek]]+ev).compact.uniq
1579
+ msg = "Inconsistent %s infinities are found: %s (=> %s is used)"%[ek, ev_uniq.inspect, hsret[ek]]
1580
+ #warn msg if $VERBOSE && (ev_uniq.size > 2) && (!ev_uniq.all?{ |c| RangeExtd::Infinity.infinite?(c) })
1581
+ warn msg if (ev_uniq.size > 1) && (!ev_uniq.all?{ |c| RangeExtd::Infinity.infinite?(c) })
1582
+ end
1112
1583
 
1113
- ### Cases
1114
- #[st means status.]
1115
- #(1) if (now[0] < prev[0])
1116
- # (1-1) if (now[-1] < prev[0]), do nothing. [Totally exclusive]
1117
- # *--* => *--*
1118
- # *--*
1119
- # (1-2) if (now[-1] == prev[0])
1120
- # (1-2-1) AND if (now[-1].in?)&&(prev[-1].in?), Change prev[0].ex?=T [Nearly exclusive]
1121
- # I--* => O--*
1122
- # *--I
1123
- # (1-2-1) ELSE, do nothing. [Totally inclusive]
1124
- # O--* => *--*
1125
- # *--I
1126
- # *--* => *--*
1127
- # *--o
1128
- # (1-3) if (now[-1] > prev[0])
1129
- # (1-3-1) if (now[-1] < prev[-1]), => (now[-1],Prev[-1]).
1130
- # *---* => ..*-*
1131
- # *--*
1132
- # (1-3-2) if (now[-1] == prev[-1]), => (now[-1],Prev[-1]).
1133
- # (1-3-2-1) if (now[-1].ex?)&&(prev[-1].in?), (Prev[-1],Prev[-1])(Single_number)
1134
- # *---I => ....I
1135
- # *----O
1136
- # (1-3-2-2) ELSE, none.
1137
- # *---O => .....
1138
- # *----*
1139
- # *---* => .....
1140
- # *----I
1141
- # (1-3-3) if (now[-1] > prev[-1]), none
1142
- # *---* => .....
1143
- # *-----*
1144
- #(2) if (now[0] == prev[0])
1145
- # (2A-1) if (now[0].ex?)&&(prev[0].in?), => (Prev[0],Prev[0])(Single_number) + Any?
1146
- # I--- => I???
1147
- # O---
1148
- # (2A-2) ELSE, none for the first part.
1149
- # O--- => .???
1150
- # *---
1151
- # *--- => .???
1152
- # I---
1153
- # (2B) Follow (1) to get the New Array.
1154
- #(3) if (now[0] > prev[0])
1155
- # (3-1) if (now[-1] < prev[-1]), 2 Rangeary (Prev[0],now[0]),(now[-1],prev[-1])
1156
- # *-----* => *-*.*-*
1157
- # *-*
1158
- # (3-2) if (now[-1] == prev[-1])
1159
- # (3-2-1) if (now[-1].ex?)&&(prev[-1].in?), => (Prev[0],now[0]),(prev[-1],prev[-1])
1160
- # *----I => *-*...I
1161
- # *--O
1162
- # (3-2-2) ELSE, => (Prev[0],now[0])
1163
- # *----I => *-*....
1164
- # *--I
1165
- # *----O => *-*....
1166
- # *--*
1167
- # (3-3) if (now[-1] > prev[-1])
1168
- # (3-3-1) if (now[0] < prev[-1]), => (Prev[0],now[0]),(prev[-1],prev[-1])
1169
- # *---* => *-*....
1170
- # *---*
1171
- # (3-3-2) if (now[0] == prev[-1])
1172
- # (3-3-2-1) if (now[0].in?)&&(prev[0].in?), => (Prev[0],prev[-1].ex)
1173
- # *--I => *--O
1174
- # I--*
1175
- # (3-3-2-2) ELSE, unchanged.
1176
- # *--* => *--*
1177
- # O--*
1178
- # *--O => *--O
1179
- # *--*
1180
- # (3-3-3) if (now[0] > prev[-1]), unchanged.
1181
- # *--* => *--*
1182
- # *--*
1183
- #
1584
+ hsret
1585
+ end
1586
+ private_class_method :_validate_select_infinities
1184
1587
 
1185
- sort_ranges(arRng).map{ |er| # => Rangeary.sort_ranges
1186
- # sort_arrange(arRng).map{ |er|
1187
- if er.end < r2sub.begin
1188
- er
1189
- elsif er.begin < r2sub.begin && er.end < r2sub.end
1190
- (er.begin .. (r2sub.begin-1))
1191
- elsif er.begin < r2sub.begin && r2sub.end < er.end
1192
- [er.begin .. (r2sub.begin-1),
1193
- (r2sub.end+1) .. er.end ]
1194
- elsif r2sub.begin <= er.begin && er.end <= r2sub.end
1195
- nil
1196
- elsif r2sub.begin <= er.begin && er.begin <= r2sub.end && r2sub.end < er.end
1197
- (r2sub.end+1) .. er.end
1198
- elsif r2sub.end < er.begin
1199
- er
1200
- else
1201
- raise "This should not happen."
1202
- end
1203
- }.flatten.compact
1204
- end # def self.subtractRange(arRng, r2sub)
1205
- private_class_method :subtractRange
1588
+ end # class Rangeary < Array
1206
1589
 
1590
+ # Overwrites its equal operator
1591
+ class Array
1592
+ alias_method :equals_before_rangeary, :== if ! self.method_defined?(:equals_before_rangeary)
1207
1593
 
1208
- end
1594
+ # Updated Array#==
1595
+ #
1596
+ # This returns true even if the standard #{==} is false, if both are *practically* empty,
1597
+ # that is, if +#empty_element?+ and/or +empty?+ are true for both, they are equal.
1598
+ # Also, this equates the "end" of Endless Range (Ruby 2.6) with Float::INFINITY or
1599
+ # RangeExtd::Infinity::POSITIVE.
1600
+ # Note by definition, it would appear at the last element in Rangeary only,
1601
+ # if it does.
1602
+ #
1603
+ # @param other [Object]
1604
+ def ==(other)
1605
+ return true if equals_before_rangeary other
1606
+ return false if !other.class.method_defined?(:to_ary)
1607
+ return false if !self.class.method_defined?(:empty_element?) && !other.class.method_defined?(:empty_element?)
1608
+
1609
+ # It was false. Is it?
1610
+ # eg., (Rangeary[RangeExtd::NONE] == []) is true,
1611
+ # because Rangeary[] with zero components does not exist!
1612
+ # Now either other or self is guranteed to be Rangeary.
1613
+ self_empt = (respond_to?(:empty_element?) ? empty_element? : empty?)
1614
+ other_empt = (other.respond_to?(:empty_element?) ? other.empty_element? : other.empty?)
1615
+ return true if self_empt && other_empt
1616
+ return false if self_empt ^ other_empt
1617
+ # return false if size != other.size # evaluated at the beginning.
1618
+ return false if size >= 2 && self[0..-2] != other[0..-2]
1619
+ return false if !self[-1].respond_to?(:exclude_end?) || !other[-1].respond_to?(:exclude_end?)
1620
+
1621
+ # Now, both are guaranteed to have the same number of non-zero elements,
1622
+ # all their elements except for the last one are equal,
1623
+ # and their last elements are Range-type instances.
1624
+ # Yet, the standard "equal" operator has failed.
1625
+ # Only the potential they may be equal is their last elements differ
1626
+ # between Endless Range (Ruby 2.6) and Infinity.
1627
+ c_self = Rangeary.comparable_end self[-1]
1628
+ c_other = Rangeary.comparable_end other[-1]
1629
+ return false if c_self != c_other
1630
+
1631
+ if !c_self.class.method_defined?(:infinite?) && !c_self.class.method_defined?(:infinity?)
1632
+ # :infinite? for Float::INFINITY, :infinity? is defined in RangeExtd::Infinity
1633
+ warn "sanity check failed. c_self should be infinite (#{c_self.inspect}). The result is not guranteed. Contact the code developer."
1634
+ end
1209
1635
 
1636
+ # The end of their last elements are both positive Infinity.
1637
+ # How about "begin"?
1638
+ self_flag = (self[-1].class.method_defined?(:exclude_begin?) ? self[-1].exclude_begin? : false)
1639
+ other_flag = (other[-1].class.method_defined?(:exclude_begin?) ? other[-1].exclude_begin? : false)
1640
+ (self[-1].begin == other[-1].begin) && (self_flag == other_flag)
1641
+ end
1642
+ end # class Array
1210
1643