Class: RangeExtd

Inherits:
Range show all
Defined in:
lib/range_extd/range_extd.rb,
lib/range_extd/infinity/infinity.rb

Overview

This file is required from range_open/range_open.rb

Defined Under Namespace

Classes: Infinity

Constant Summary collapse

@@middle_strings =

To conrol how the RangeExtd should be displayed or set (in one form). It can be read and reset by middle_strings and middle_strings= Default is [”, ”, ‘<’, ‘..’, ‘.’, ”, ”]

[]

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Range

#empty?, #eql_prerangeextd?, #equal_prerangeextd?, #null?, #valid?

Constructor Details

#new(range, [exclude_begin = false, [exclude_end=false]], opts) ⇒ RangeExtd #new(obj_begin, obj_end, [exclude_begin = false, [exclude_end=false]], opts) ⇒ RangeExtd #new(obj_begin, string_form, obj_end, [exclude_begin = false, [exclude_end=false]], opts) ⇒ RangeExtd

Note:

The flag of exclude_begin|end can be given in the arguments in a couple of ways. If there is any duplication, those specified in the optional hash have the highest priority. Then the two descrete Boolean parameters have the second. If not, the values embeded in the Range or RangeExtd object or the String form in the parameter are used. In default, both of them are false.

Note if you use the third form with “string_form” with the user-defined string (via middle_strings=()), make 100 per cent sure you know what you are doing. If the string is ambiguous, the result may differ from what you thought you would get! See middle_strings=() for detail. Below are a couple of examples:

RangeExtd.new(5, '....', 6)      # => RangeError because (5..("....")) is an invalid Range.
RangeExtd.new("%", '....', "y")  # => ("%" <.. "....")
                                 #   n.b., "y" is interpreted as TRUE for
                                 #   the flag for "exclude_begin?"
RangeExtd.new("x", '....', "y")  # => RangeError because ("x" <..("....")) is an invalid RangeExte,
                                 #   in the sense String "...." is *smaller* than "x"
                                 #   in terms of the "<=>" operator comparison.

Examples:

RangeExtd(1...2)
RangeExtd(1..3, true)
RangeExtd(2..3, :exclude_begin => true)
RangeExtd(1, 4, false, true)
RangeExtd(1,'<...',5)
RangeExtd.middle_strings = :math
RangeExtd(2,'<x<=',5)
RangeExtd(RangeExtd::Infinity::NEGATIVE..RangeExtd::Infinity::POSITIVE)

Overloads:

  • #new(range, [exclude_begin = false, [exclude_end=false]], opts) ⇒ RangeExtd

    Parameters:

    • range (Object)

      Instance of Range or its subclasses, including RangeExtd

    • exclude_begin (Boolean)

      If specified, this has the higher priority, or false in default.

    • exclude_end (Boolean)

      If specified, this has the higher priority, or false in default.

    Options Hash (opts):

    • :exclude_begin (Boolean)

      If specified, this has the highest priority, or false in default.

    • :exclude_end (Boolean)

      If specified, this has the highest priority, or false in default.

  • #new(obj_begin, obj_end, [exclude_begin = false, [exclude_end=false]], opts) ⇒ RangeExtd

    Parameters:

    • obj_begin (Object)

      Any object that is Comparable with end

    • obj_end (Object)

      Any object that is Comparable with begin

    • exclude_begin (Boolean)

      If specified, this has the lower priority, or false in default.

    • exclude_end (Boolean)

      If specified, this has the lower priority, or false in default.

    Options Hash (opts):

    • :exclude_begin (Boolean)

      If specified, this has the higher priority, or false in default.

    • :exclude_end (Boolean)

      If specified, this has the higher priority, or false in default.

  • #new(obj_begin, string_form, obj_end, [exclude_begin = false, [exclude_end=false]], opts) ⇒ RangeExtd

    Parameters:

    • obj_begin (Object)

      Any object that is Comparable with end

    • string_form (Object)

      String form (without pre/postfix) of range expression set by middle_strings=()

    • obj_end (Object)

      Any object that is Comparable with begin

    • exclude_begin (Boolean)

      If specified, this has the lower priority, or false in default.

    • exclude_end (Boolean)

      If specified, this has the lower priority, or false in default.

    Options Hash (opts):

    • :exclude_begin (Boolean)

      If specified, this has the higher priority, or false in default.

    • :exclude_end (Boolean)

      If specified, this has the higher priority, or false in default.

Raises:

  • (ArgumentError)

    particularly if the range to be created is not Range#valid?.



127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/range_extd/range_extd.rb', line 127

def initialize(*inar, **hsopt)  # **k expression from Ruby 1.9?

  if inar[4] == :Constant
    # Special case to create two Constants
    super(*inar[0..2])
    @rangepart = (inar[2] ? (inar[0]...inar[1]) : (inar[0]..inar[1]))
    @exclude_end, @exclude_begin = inar[2..3]
    return
  end

  # Note: the order of exclude_begin? and end? is reversed from the input!
  arout = RangeExtd.send(:_get_init_args, *inar, hsopt)
  # == [RangeBeginValue, RangeEndValue, exclude_end?, exclude_begin?]

  ### The following routine is obsolete.
  ### Users, if they wish, should call RangeExtd::Infinity.overwrite_compare() beforehand.
  ### Or better, design their class properly in the first place!
  ### See the document in Object#<=> in this code for detail.
  #
  # # Modify (<=>) method for the given object, so that
  # # it becomes comparable with RangeExtd::Infinity,
  # # if the object is already Comparable.
  # #
  # # This must come first.
  # # Otherwise it may raise ArgumentError "bad value for range",
  # # because the native Range does not accept
  # #   (Obj.new..RangeExtd::Infinity::POSITIVE)
  # #
  # boundary = nil
  # aroutid0 = arout[0].object_id
  # aroutid1 = arout[1].object_id
  # if    aroutid0 == RangeExtd::Infinity::NEGATIVE.object_id ||
  #       aroutid0 == RangeExtd::Infinity::POSITIVE.object_id
  #   boundary = arout[1]
  # elsif aroutid1 == RangeExtd::Infinity::NEGATIVE.object_id ||
  #       aroutid1 == RangeExtd::Infinity::POSITIVE.object_id
  #   boundary = arout[0]
  # end
  # if (! boundary.nil?) && !defined?(boundary.infinity?)
  #   RangeExtd::Infinity.overwrite_compare(boundary) # To modify (<=>) method for the given object.
  #   # Infinity::CLASSES_ACCEPTABLE ...
  # end

  if ! RangeExtd.valid?(*arout)
    raise RangeError, "the combination of the arguments does not constitute a valid RangeExtd instance."
  end

  @exclude_begin = arout.pop
  @exclude_end   = arout[-1]
  @rangepart = Range.new(*arout)
  super(*arout)

end

Class Method Details

.middle_stringsArray<String>

See RangExtd.middle_strings=() for detail.

Returns:

  • (Array<String>)


1205
1206
1207
# File 'lib/range_extd/range_extd.rb', line 1205

def self.middle_strings()
  @@middle_strings
end

.middle_strings=(ary) ⇒ Array, Symbol

Set the class variable to be used in #to_s and #inspect to configure the format of their returned values.

The parameters should be given as an Array with 7 elements of string in principle, which gives the characters for each index:

  1. prefix

  2. begin-inclusive

  3. begin-exclusive

  4. middle-string to bridge both ends

  5. end-exclusive

  6. end-inclusive

  7. postfix

If the elements [1] and [2], or [4] and [5] are equal, a warning is issued as some of RangeExtd in display will be indistinguishable. Note even if no warning is issued, that does not mean all the forms will be not ambiguous. For example, if you specify

['(', '', '.', '..', '.', '', ')']

a string (3…7) can mean either exclusive #begin or #end. It is user’s responsibility to make it right.

The two most popular forms can be given as a Symbol instead of Array, that is,

:default  ( ['', '', '<', '..', '.', '', ''] )
:math     ( ['', '<=', '<', 'x', '<', '<=', ''] )

Examples:

RangeExtd.middle_strings=:default  # Default
RangeExtd(2...6).to_s    # => "2...6"
RangeExtd(2,6,1).to_s    # => "2<..6"
RangeExtd.middle_strings=:math
RangeExtd(2...6).to_s    # => "2<=x<6"
RangeExtd(2,6,1).to_s    # => "2<x<=6"
RangeExtd.middle_strings=['[','(in)','(ex)',', ','(ex)','(in)',']']
RangeExtd(2...6).to_s    # => "[2(in), (ex)6]"
RangeExtd(2,6,1).to_s    # => "[2(ex), (in)6]"

Parameters:

  • ary (Array, Symbol)

Returns:

  • (Array, Symbol)


1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
# File 'lib/range_extd/range_extd.rb', line 1179

def self.middle_strings=(ary)
  case ary
  when :default
    @@middle_strings = ['', '', '<', '..', '.', '', '']
  when :math
    @@middle_strings = ['', '<=', '<', 'x', '<', '<=', '']
  else
    begin
      if ary.size == 7
        _dummy = 'a' + ary[6]
        @@middle_strings = ary
        if (ary[1] == ary[2]) || (ary[4] == ary[5])
          warn "warning: some middle_strings are indistinguishable."
        end
      else
        raise
      end
    rescue
      raise ArgumentError, "invalid argument"
    end
  end
end

.new(range, [exclude_begin = false, [exclude_end=false]]) ⇒ Boolean .new(obj_begin, obj_end, [exclude_begin = false, [exclude_end=false]]) ⇒ Boolean

Note:

The flag of exclude_begin|end can be given in the arguments in a couple of ways. If there is any duplication, those specified in the optional hash have the highest priority. Then the two descrete Boolean parameters have the second. If not, the values embeded in the Range or RangeExtd object in the parameter are used. In default, both of them are false.

Returns true if the range to be constructed (or given) is valid, as a range, accepted in RangeExtd.

This routine is also impremented as a method in Range, and accordingly its sub-classes.

This routine is called from new, hence for any instance of RangeExtd class, its Range#valid? returns true.

What is valid is defined as follows:

  1. The #begin and #end elements must be Comparable to each other, and the comparison results must be consistent betwen the two. The two sole exceptions are NONE and Endless Range introduced in Ruby 2.6 (see below for the exceptions), both of which are valid. For example, (nil..nil) is NOT valid (nb., it raised Exception in Ruby 1.8).

  2. Except for NONE, #begin must have the method <=. Therefore, some Endless Ranges (Ruby 2.6 and later) like (true..) are not valid. Note even “true” has the method <=> and hence checking <= is essential.

  3. #begin must be smaller than or equal to #end, that is, (#begin <=> #end) must be either -1 or 0.

  4. If #begin is equal to #end, namely, (#begin <=> #end) == 0, the exclude status of the both ends must agree. That is, if the #begin is excluded, #end must be also excluded, and vice versa. For example, (1…1) is NOT valid for that reason, because any built-in Range object has the exclude status of false (namely, inclusive) for #begin.

Note the last example may change in the future release.

Note ([2]..) is NOT valid, because Array does not include Comparable for some reason, as of Ruby 2.1.1, even though it has the redefined and working [#<=>]. You can make those valid, by including Comparable in Array class, should you wish.

Examples:


RangeExtd.valid?(nil..nil)     # => false
RangeExtd.valid?(nil...nil)    # => false
RangeExtd.valid?(0..0)         # => true
RangeExtd.valid?(0...0)        # => false
RangeExtd.valid?(0...)         # => true
RangeExtd.valid?(true..)       # => false
RangeExtd.valid?(0..0,  true)  # => false
RangeExtd.valid?(0...0, true)  # => true
RangeExtd.valid?(2..-1)        # => false
RangeExtd.valid?(RangeExtd::NONE)     # => true
RangeExtd.valid?(RangeExtd::ALL)      # => true
RangeExtd.valid?(3..Float::INFINITY)  # => true
RangeExtd.valid?(3..Float::INFINITY, true)  # => true
RangeExtd.valid?(RangeExtd::Infinity::NEGATIVE..?d)        # => true
RangeExtd.valid?(RangeExtd::Infinity::NEGATIVE..?d, true)  # => true

Overloads:

  • .new(range, [exclude_begin = false, [exclude_end=false]]) ⇒ Boolean

    Parameters:

    • range (Object)

      Instance of Range or its subclasses, including RangeExtd

    • exclude_begin (Boolean)

      If specified, this has the higher priority, or false in default.

    • exclude_end (Boolean)

      If specified, this has the higher priority, or false in default.

  • .new(obj_begin, obj_end, [exclude_begin = false, [exclude_end=false]]) ⇒ Boolean

    Parameters:

    • obj_begin (Object)

      Any object that is Comparable with end

    • obj_end (Object)

      Any object that is Comparable with begin (or nil, for Ruby 2.6 onwards)

    • exclude_begin (Boolean)

      If specified, this has the lower priority, or false in default.

    • exclude_end (Boolean)

      If specified, this has the lower priority, or false in default.

Returns:

  • (Boolean)


1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
# File 'lib/range_extd/range_extd.rb', line 1089

def self.valid?(*inar)
  (vbeg, vend, exc_beg, exc_end) = _get_init_args(*inar)

  if defined?(inar[0].is_none?) && inar[0].is_none? && exc_beg && exc_end
    return true
  end
  begin
    t = (vbeg <=> vend)
    begin
      if vend.nil?
        begin
          _ = (vbeg..nil)  # Endless Range introduced in Ruby 2.6
          return vbeg.class.method_defined?(:<=)
        rescue ArgumentError
          # Before Ruby 2.6
          return false
        end
      end
      return false if t != -1*(vend <=> vbeg) # false if not commutative, or possibly exception (such as -1*nil).
    rescue NoMethodError, TypeError
      if (Float === vend && defined?(vbeg.infinity?) && vbeg.infinity?) ||
         (Float === vbeg && defined?(vend.infinity?) && vend.infinity?)
        warn self.const_get(:ERR_MSGS)[:infinity_compare] if !$VERBOSE.nil?  # one of the tests comes here.
      end
      return false  # return
    end
  rescue # NoMethodError
    false # return
  else
    case t
    when -1
      true
    when 0
      if defined?(vbeg.<=) && defined?(vend.<=) # Comparable?
        ((true && exc_beg) ^! exc_end)  # True if single value or empty, false if eg, (1...1)
      else
        false   # Not Comparable
      end
    when 1
      false
    else
      if (Float === vend && defined?(vbeg.infinity?) && vbeg.infinity?) ||
         (Float === vbeg && defined?(vend.infinity?) && vend.infinity?)
        warn self.const_get(:ERR_MSGS)[:infinity_compare] if !$VERBOSE.nil?  # not tested so far?
      end
      false # Not Comparable.
    end # case t
    # All statements of return above.
  end
end

Instance Method Details

#==(r) ⇒ Boolean

Like Range, returns true only if both of them are Range (or its subclasses), and in addition if both #exclude_begin? and #exclude_end? match (==) between the two objects. For the empty ranges they are somewhat different. In short, when both of them are empty and they belong to the same Class or have common ancestors (apart from Object and BasicObject, excluding all the included modules), this returns true, regardless of their boundary values. And any empty range is equal to RangeExtd::Infinity::NONE.

Note the last example will return false for #eql? – see #eql?

See #eql?

Examples:

(1<...1)   == RangeExtd::NONE # => true
(?a<...?b) == RangeExtd::NONE # => true
(1<...1) == (2<...2)     # => true
(1<...1) == (3<...4)     # => true
(?a<...?b) == (?c<...?c) # => true
(1<...1) != (?c<...?c)   # - because of Fixnum and String
(1.0<...1.0) == (3<...4) # => true

Returns:

  • (Boolean)


234
235
236
# File 'lib/range_extd/range_extd.rb', line 234

def ==(r)
  re_equal_core(r, :==)
end

#===(obj) ⇒ Boolean Also known as: include?, member?

If the object is open-ended to the negative (Infinity), this returns nil in default, unless the given object is Numeric (and comparable of Real), in which case this calls #cover?, or if self is ALL and the object is Comparable.

In the standard Range, this checks whether the given object is a member, hence,

(?D..?z) === ?c    # => true
(?a..?z) === "cc"  # => false

In the case of the former, after finite trials of [#succ] from ?c, it reaches the end (?z). In the latter, after finit trials of [#succ] from the begin ?a, it reaches the end (?z). Therefore it is theoretically possible to prove it (n.b., the actual algorithm of built-in Range#include? is different and cheating! See below.).

However, in the case of

(?D..Infinity) === ?c

it can never prove ?c is a member after infinite trials of [#succ], whether it starts the trials from the begin (?D) or the object (?c).

For anything but Numeric, use #cover? instead.

Note

(?B..?z) === 'dd'  # => false

as Ruby’s Range knows the algorithm of String#succ and String#<=> and specifically checks with it, before using Enumerable#include?. https://github.com/ruby/ruby/blob/trunk/range.c

Therefore, even if you change the definition of String#succ so that ‘B’.succ => ‘dd’, ‘dd’.succ => ‘z’, as follows,

class String
  alias :succ_orig :succ
  def succ
    if self == 'B'
      'dd'
    elsif self == 'dd'
      'z'
    else
      :succ_orig
    end
  end
end

the resutl of Range#=== will unchange;

(?B..?z) === 'dd'  # => false
(?B..?z).to_a      # => ["B", "dd", "z"]

Similarly Range treats String differently;

(?X..?z).each do |i| print i;end  # => "XYZ[\]^_`abcdefghijklmnopqrstuvwxyz"
?Z.succ  # => 'AA'

Parameters:

  • obj (Object)

    If this Object is a member?

Returns:

  • (Boolean)


307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
# File 'lib/range_extd/range_extd.rb', line 307

def ===(obj)
  # ("a".."z")===("cc") # => false

  return false if is_none?  # No need of null?(), supposedly!

  begin
    1.0+(obj) # OK if Numeric.

  rescue TypeError
    # obj is not Numeric, hence runs brute-force check.
    beg = self.begin()
    if defined?(beg.infinity?) && beg.infinity? || beg == -Infinity::FLOAT_INFINITY
      return nil
      # raise TypeError "can't iterate from -Infinity"
    end

    each do |ei|
      if ei == obj
        return true
      end
    end
    false

  else
    cover?(obj)
  end
end

#beginObject

Returns:



411
412
413
# File 'lib/range_extd/range_extd.rb', line 411

def begin()
  @rangepart.begin()
end

#bsearch(*rest, &bloc) ⇒ Object

bsearch is internally implemented by converting a float into 64-bit integer. The following examples demonstrate what is going on.

ary = [0, 4, 7, 10, 12]
(3...4).bsearch{    |i| ary[i] >= 11} # => nil
(3...5).bsearch{    |i| ary[i] >= 11} # => 4   (Integer)
(3..5.1).bsearch{   |i| ary[i] >= 11} # => 4.0 (Float)
(3.6..4).bsearch{   |i| ary[i] >= 11} # => 4.0 (Float)
(3.6...4).bsearch{  |i| ary[i] >= 11} # => nil
(3.6...4.1).bsearch{|i| ary[i] >= 11} # => 4.0 (Float)

class Special
  def [](f)
   (f>3.5 && f<4) ? true : false
  end
end
sp = Special.new
(3..4).bsearch{   |i| sp[i]}  # => nil
(3...4).bsearch{  |i| sp[i]}  # => nil
(3.0...4).bsearch{|i| sp[i]}  # => 3.5000000000000004
(3...4.0).bsearch{|i| sp[i]}  # => 3.5000000000000004
(3.3..4).bsearch{ |i| sp[i]}  # => 3.5000000000000004

(Rational(36,10)..5).bsearch{|i| ary[i] >= 11}  => # TypeError: can't do binary search for Rational (Ruby 2.1)
(3..Rational(61,10)).bsearch{|i| ary[i] >= 11}  => # TypeError: can't do binary search for Fixnum (Ruby 2.1)

In short, bsearch works only with Integer and/or Float (as in Ruby 2.1). If either of begin and end is an Float, the search is conducted in Float and the returned value will be Float, unless nil. If Float, it searches on the binary plane. If Integer, the search is conducted on the descrete Integer points only, and no search will be made in between the adjascent integers.

Given that, #bsearch follows basically the same, even when exclude_begin? is true. If either end is Float, it searches between begin*(1+Float::EPSILON) and end. If both are Integer, it searches from begin+1. When #exclude_begin? is false, #bsearch is identical to Range#bsearch.



457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
# File 'lib/range_extd/range_extd.rb', line 457

def bsearch(*rest, &bloc)
  if is_none? # No need of null?(), supposedly!
    raise TypeError, "can't do binary search for NONE range"
  end

  if @exclude_begin
    if ((Float === self.begin()) ||
        (Integer === self.begin()) && (Float === self.end()))
      #NOTE: Range#bsearch accepts Infinity, whether it makes sense or not.
      # if Infinity::FLOAT_INFINITY == self.begin()
      #   raise TypeError, "can't do binary search from -Infinity"
      # else
        Range.new(self.begin()*(Float::EPSILON+1.0), self.end, exclude_end?).send(__method__, *rest, &bloc)
        # @note Technically, if begin is Rational, there is no strong reason it should not work.
        #   However Range#bsearch does not accept Rational (at Ruby 2.1), hence this code.
        #   Users should give a RangeExtd with begin being Rational.to_f in that case.
      # end
    elsif (defined? self.begin().succ)  # Both non-Float
      Range.new(self.begin().succ, self.end, exclude_end?).send(__method__, *rest, &bloc) # In practice it will not raise an Exception, only when both are Integer.
    else
      @rangepart.send(__method__, *rest, &bloc) # It will raise an exception anyway!  Such as, (Rational..Rational)
    end
  else
    @rangepart.send(__method__, *rest, &bloc)
  end
end

#cover?(i) ⇒ Boolean

See #include? or #===, and Range#cover?

Returns:

  • (Boolean)


486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
# File 'lib/range_extd/range_extd.rb', line 486

def cover?(i)
  # ("a".."z").cover?("cc") # => true
  # (?B..?z).cover?('dd') # => true  (though 'dd'.succ would never reach ?z)

  return false if is_none?  # No need of null?(), supposedly!

  if @exclude_begin
    if self.begin == i
      false
    else
      @rangepart.send(__method__, i)
    end
  else
    @rangepart.send(__method__, i)
  end
end

#each(*rest, &bloc) ⇒ RangeExtd, Enumerator

Returns:

  • (RangeExtd)

    self

  • (Enumerator)

    if block is not given.

Raises:

  • (TypeError)

    If #exclude_begin? is true, and #begin() or #rangepart does not have a method of [#succ], then even if no block is given, this method raises TypeError straightaway.



508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
# File 'lib/range_extd/range_extd.rb', line 508

def each(*rest, &bloc)
  # (1...3.5).each{|i|print i}  # => '123' to STDOUT
  # (1.3...3.5).each  # => #<Enumerator: 1.3...3.5:each>
  # (1.3...3.5).each{|i|print i}  # => TypeError: can't iterate from Float
  # Note: If the block is not given and if @exclude_begin is true, the self in the returned Enumerator is not the same as self here.
  if @exclude_begin # including RangeExtd::NONE
    if defined? self.begin.succ
      ret = Range.new(self.begin.succ,self.end,exclude_end?).send(__method__, *rest, &bloc)
      if block_given?
        self
      else
        ret
      end
    elsif is_none?
      raise TypeError, "can't iterate for NONE range"
    else
      raise TypeError, "can't iterate from "+self.begin.class.name
    end
  else
    @rangepart.send(__method__, *rest, &bloc)
  end
end

#endObject

Returns:



416
417
418
# File 'lib/range_extd/range_extd.rb', line 416

def end()
  @rangepart.end()
end

#eql?(r) ⇒ Boolean

The same as #== but it uses eql?() as each comparison. For the empty ranges, it is similar to #==, except the immediate class has to agree to return true. Only the exception is the comparison with RangeExtd::Infinity::NONE. Therefore,

Examples:

(1...5) ==  (1.0...5.0)  # => true
(1...5).eql?(1.0...5.0)  # => false
(1<...1).eql?(  RangeExtd::NONE)  # => true
(?a<...?b).eql?(RangeExtd::NONE)  # => true
(1<...1).eql?(    3<...4)  # => true
(1.0<...1.0).eql?(3<...4)  # => false

Returns:

  • (Boolean)


251
252
253
# File 'lib/range_extd/range_extd.rb', line 251

def eql?(r)
  re_equal_core(r, :eql?)
end

#equiv?(other) ⇒ Boolean

Return true if self and the other are equivalent; if [#to_a] is defined, it is similar to

(self.to_a == other.to_a)

(though the ends are checked more rigorously), and if not, equivalent to

(self == other)

Examples:

RangeExtd(2...7,true).equiv?(3..6)     # => true
RangeExtd(2...7,true).equiv?(3..6.0)   # => false
RangeExtd(2...7,true).equiv?(3.0..6.0) # => false
RangeExtd(2...7,true).equiv?(3..6.5)   # => false
RangeExtd(2...7,true).equiv?(RangeExtd(2.0...7.0,true))   # => true
RangeExtd(2...7,true).equiv?(3...7.0)  # => true

Parameters:

Returns:

  • (Boolean)


353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
# File 'lib/range_extd/range_extd.rb', line 353

def equiv?(other)
  # This routine is very similar to Range#equiv? except
  # exclude_begin? in this object is always defined, hence
  # a more thorough check is needed.

  t_or_f = (defined?(self.begin.succ) && defined?(other.begin.succ) && defined?(other.end) && defined?(other.exclude_end?))
  if ! t_or_f
    return(self == other) # succ() for begin is not defined.
  else
    # Checking the begins.
    if defined?(other.exclude_begin?)
      other_excl_beg = other.exclude_begin?
    else
      other_excl_beg = false
    end

    if (self.begin == other.begin)
      if (exclude_begin? ^! other_excl_beg)
        # Pass
      else
        return false
      end
    else
      if (exclude_begin? ^! other_excl_beg)
        return false
      elsif (exclude_begin? && (self.begin.succ == other.begin)) ||
            (other_excl_beg && (self.begin == other.begin.succ))
        # Pass
      else
        return false
      end
    end # if (self.begin == other.begin)  # else
        
    # Now, the begins agreed.  Checking the ends.
    if (self.end == other.end)
      if (exclude_end? ^! other.exclude_end?)
        return true
      else
        return false
      end
    else  # if (self.end == other.end)
      if (exclude_end? ^! other.exclude_end?)
        return false
        # elsif defined?(other.last) && (self.last(1) == other.last(1)) # Invalid for Ruby 1.8 or earlier # This is not good - eg., in this case, (1..5.5).equiv?(1..5.4) would return true.
        
      #   return true
      elsif (      exclude_end? && defined?(other.end.succ) && (self.end == other.end.succ)) ||
            (other.exclude_end? && defined?( self.end.succ) && (self.end.succ == other.end))
        return true
      else
        return false
      end
    end # if (self.end == other.end)
  end # if ! t_or_f
end

#exclude_begin?Boolean

Returns true if the “begin” boundary is excluded, or false otherwise.

Returns:

  • (Boolean)


202
203
204
# File 'lib/range_extd/range_extd.rb', line 202

def exclude_begin?
  @exclude_begin
end

#exclude_end?Boolean

Returns true if the “end” boundary is excluded, or false otherwise.

Returns:

  • (Boolean)


207
208
209
# File 'lib/range_extd/range_extd.rb', line 207

def exclude_end?
  @exclude_end
end

#first(*rest) ⇒ Object, Array

Like Range#last, if no argument is given, it behaves like #begin(), that is, it returns the initial value, regardless of #exclude_begin?. However, if an argument is given (nb., acceptable since Ruby 1.9) when #exclude_begin? is true, it returns the array that starts from #begin().succ().

Parameters:

  • rest (Integer)

    Optional. Must be non-negative. Consult Range#first for detail.

Returns:

  • (Object)

    if no argument is given, equivalent to #end.

  • (Array)

    if an argument is given.

Raises:



538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
# File 'lib/range_extd/range_extd.rb', line 538

def first(*rest)
  # (1...3.1).last  # => 3.1
  # (1...3.1).last(1) # => [3]
  if ! @exclude_begin # hence, not NONE.
    @rangepart.first(*rest)
  else
    case rest.size
    when 0
      self.begin
    when 1
      if (RUBY_VERSION < "1.9.1") && (1 == rest[0]) # Range#first() does not accept an argument in Ruby 1.8.
        raise ArgumentError, "wrong number of arguments (#{rest.size} for 0) (Use Ruby 1.9.2 or later)."
      end

      ## Check the argument.
      Array.new[ rest[0] ]  # Check Type of rest[0] (if invalid, it should raise TypeError)

      begin
        if rest[0] < 0
          raise ArgumentError, "negative array size (or size too big)"
        end
      rescue NoMethodError
        # Should not happen, but just to play safe.
      end

      ## Main
      if ! defined? self.begin.succ
        raise TypeError, "can't iterate from "+self.begin.class.name
      end

      Range.new(self.begin.succ, self.end, exclude_end?).send(__method__, *rest)
    else
      raise ArgumentError, "wrong number of arguments (#{rest.size} for 0..1)"
    end
  end   # if ! @exclude_begin
end

#hash(*rest) ⇒ Object

When #exclude_begin? is true, the returned value is not strictly guaranteed to be unique, though in pracrtice it is most likely to be so.



578
579
580
581
582
583
584
# File 'lib/range_extd/range_extd.rb', line 578

def hash(*rest)
  if @exclude_begin
    @rangepart.send(__method__, *rest) - 1
  else
    @rangepart.send(__method__, *rest)
  end
end

#inspectString

Return eg., ‘(“a”<…“c”)’, ‘(“a”<..“c”)’, if #exclude_begin? is true, or else, identical to those for Range.

Returns:

  • (String)


590
591
592
# File 'lib/range_extd/range_extd.rb', line 590

def inspect
  re_inspect_core(__method__)
end

#is_all?Boolean

true if self is identical to ALL (#== does not mean it at all!)

Examples:

(RangeExtd::Infinity::NEGATIVE..RangeExtd::Infinity::POSITIVE).is_all?  # => false

Returns:

  • (Boolean)


196
197
198
# File 'lib/range_extd/range_extd.rb', line 196

def is_all?
  self.begin.object_id == Infinity::NEGATIVE.object_id && self.end.object_id == Infinity::POSITIVE.object_id && !@exclude_begin && !@exclude_end  # Direct comparison with object_id should not work for this one!! (because users can create an identical one.)
end

#is_none?Boolean

true if self is identical to NONE. This is different from #== method!

Examples:

RangeExtd(0,0,false,false) == RangeExtd::NONE  # => true
RangeExtd(0,0,false,false).empty?    # => true
RangeExtd(0,0,false,false).is_none?  # => false
RangeExtd::NONE.is_none?     # => true

Returns:

  • (Boolean)


189
190
191
# File 'lib/range_extd/range_extd.rb', line 189

def is_none?
  self.begin.nil? && self.end.nil? && @exclude_begin && @exclude_end  # Direct comparison with object_id should be OK?
end

#last(*rest) ⇒ Object, Array

See Range#last. If either (let alone both) side of the edge is Infinity, you can not give an argument in practice, the number of the members of the returned array.

Returns:

  • (Object)

    if no argument is given, equivalent to #end.

  • (Array)

    if an argument is given.

Raises:

  • (TypeError)

    If self.begin.succ is not defined, or if either side is Infinity.



609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
# File 'lib/range_extd/range_extd.rb', line 609

def last(*rest)
  return nil if null?
  nSize = rest.size
  case nSize
  when 0
    self.end
  when 1
      if (RUBY_VERSION < "1.9.1") && (1 == rest[0]) # Range#first() does not accept an argument in Ruby 1.8.
        raise ArgumentError, "wrong number of arguments (#{rest.size} for 0) (Use Ruby 1.9.2 or later)."
      end

    if  defined?(self.begin.infinity?) && self.begin.infinity? || self.begin == -Infinity::FLOAT_INFINITY
      raise TypeError, "can't iterate from "+self.begin.to_s
    elsif defined?(self.end.infinity?) && self.end.infinity?   || self.end   ==  Infinity::FLOAT_INFINITY
      raise TypeError, "can't get elements to "+self.end.to_s
    elsif ! defined? self.begin.succ
      raise TypeError, "can't iterate from "+self.begin.class.name
    else
      @rangepart.send(__method__, *rest)
    end
  else
    raise ArgumentError, "wrong number of arguments (#{rest.size} for 0..1)"
  end
end

#max(*rest, &bloc) ⇒ Object

See #first for the definition when #exclude_begin? is true.



670
671
672
# File 'lib/range_extd/range_extd.rb', line 670

def max(*rest, &bloc)
  re_min_max_core(__method__, *rest, &bloc)
end

#max_by(*rest, &bloc) ⇒ Object

See #first for the definition when #exclude_begin? is true.



676
677
678
# File 'lib/range_extd/range_extd.rb', line 676

def max_by(*rest, &bloc)
  re_min_max_core(__method__, *rest, &bloc)
end

#min(*rest, &bloc) ⇒ Object

See #first for the definition when #exclude_begin? is true.



637
638
639
# File 'lib/range_extd/range_extd.rb', line 637

def min(*rest, &bloc)
  re_min_max_core(__method__, *rest, &bloc)
end

#min_by(*rest, &bloc) ⇒ Object

See #first for the definition when #exclude_begin? is true.



643
644
645
# File 'lib/range_extd/range_extd.rb', line 643

def min_by(*rest, &bloc)
  re_min_max_core(__method__, *rest, &bloc)
end

#minmax(*rest, &bloc) ⇒ Object

See #first for the definition when #exclude_begin? is true.



650
651
652
653
654
655
656
# File 'lib/range_extd/range_extd.rb', line 650

def minmax(*rest, &bloc)
  # (0...3.5).minmax  # => [0, 3]
  # (1.3...5).minmax  # => TypeError: can't iterate from Float
  # Note that max() for the same Range raises an exception.
  # In that sense, it is inconsistent!
  re_min_max_core(__method__, *rest, &bloc)
end

#minmax_by(*rest, &bloc) ⇒ Object

See #first for the definition when #exclude_begin? is true.



660
661
662
663
664
665
# File 'lib/range_extd/range_extd.rb', line 660

def minmax_by(*rest, &bloc)
  # (0...3.5).minmax  # => [0, 3]
  # Note that max() for the same Range raises an exception.
  # In that sense, it is inconsistent!
  re_min_max_core(__method__, *rest, &bloc)
end

#size(*rest) ⇒ Integer, ...

Note:

When both ends n are the same INFINITY (of the same parity), (n..n).size used to be 0. As of Ruby 2.6, it is FloatDomainError: NaN. This routine follows what Ruby produces, depending on Ruby’s version it is run on.

Implementation of Range#size to this class.

It is essentially the same, but the behaviour when #exclude_begin? is true may not always be natural. See #first for the definition when #exclude_begin? is true.

Range#size only works for Numeric ranges. And in Range#size, the value is calculated when the initial value is non-Integer, by stepping by 1.0 from the #begin value, and the returned value is an integer. For example,

(1.4..2.6).size == 2

because both 1.4 and 2.4 (== 1.4+1.0) are included in the Range.

That means you had better be careful with the uncertainty (error) of floating-point. For example, at least in an environment,

4.8 - 4.5   # => 0.2999999999999998
(2.5...4.5000000000000021).size  => 2
(2.8...4.8000000000000021).size  => 3
(2.8..4.8).size  => 3

In #size, the principle is the same. If the #begin value has the method [#succ] defined, the object is regarded to consist of discrete values. If not, it is a range with continuous elements. This dinstinguishment affects the behavious seriously in some cases when #exclude_begin? is true. For example, the following two cases may seem unnatural.

RangeExtd(1..5, true, true)      == RangeExtd(Rational(1,1), 5, true, true)
RangeExtd(1..5, true, true).size != RangeExtd(Rational(1,1), 5, true, true).size

Although those two objects are equal by [#==], they are different in nature, as far as Range and RangeExtd are concerned, and that is why they work differently;

RangeExtd(1..5, true, true).eql?(RangeExtd(Rational(1,1), 5, true, true))  # => false
RangeExtd(1..5, true, true).to_a      # => [2, 3, 4]
RangeExtd(1..5, true, true).to_a.size # => 3
RangeExtd(Rational(1,1)..5).to_a   # => TypeError

Also, the floating-point uncertainties in Float can more often be problematic; for example, in an environment,

4.4 - 2.4   # => 2.0000000000000004
4.8 - 2.8   # => 2.0
RangeExtd(2.4..4.4, true, true).size  # => 3
RangeExtd(2.8..4.8, true, true).size  # => 2

The last example is what you would naively expect, because both

2.8+a(lim a->0)  and  3.8+a(lim a->0) are

in the range whereas 4.8 is not in the range by definition, but not the example right above.

Ruby 2.6 Endless Range and Infinity.

Before RangeExtd Ver.1.1, if a RangeExtd object contains Infinity objects for either begin or end, #size used to be always Float::INFINITY no matter what the other object is (except when the other object is also a Infinity object). However, since the introduction of the endless Range in Ruby 2.6, Ruby returns as follows:

(5..).size  # => Float::INFINITY
(?a..).size # => nil

Accordingly, this class RangeExtd now behaves the same as Ruby (2.6 or later).

Similarly,

(Float::INFINITY..Float::INFINITY).size

has changed (I do not know in which Ruby version)! It used to be 0. However, As of Ruby 2.6, it is FloatDomainError: NaN Again this class now follows Ruby’s default (RangeExtd Ver.1.0 or later).

Returns:

  • (Integer)

    0 if NONE

  • (Float)

    Float::INFINITY if either (or both) the end is infinity, regardless of the class of the elements.

  • (nil)

    if the range is non-Numeric.

See Also:



761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
# File 'lib/range_extd/range_extd.rb', line 761

def size(*rest)
  # (1..5).size # => 5
  # (1...5).size  # => 4
  # (0.8...5).size  # => 5 # Why???
  # (1.2...5).size  # => 4 # Why???
  # (1.2..5).size # => 4  # Why???
  # (Rational(3,2)...5).size  # => 3
  # (1.5...5).size  # => 4 # Why not 3??
  # (1.5...4.9).size  # => 4 # Why not 3??
  # (1.5...4.5).size  # => 3
  # (0...Float::INFINITY).size  # => Infinity

  if is_none? # No need of null?(), supposedly!
    return 0

  ### (Infinity..Infinity) => 0  (as in Ruby 2.1)
  # elsif self.begin().infinity? || self.end().infinity?
  #   return Infinity::FLOAT_INFINITY

  # Checking Infinity.
  # Note (Infinity..Infinity) => 0  (Range as in Ruby 2.1)
  # however, 
  elsif (defined?(self.begin.infinity?) && self.begin.infinity? || self.begin == -Infinity::FLOAT_INFINITY) ||
        (defined?(self.end.infinity?)   && self.end.infinity?   || self.end == Infinity::FLOAT_INFINITY) ||
        (self.end.nil?)  # RangeExtd#end can be nil only for Ruby-2.6
    if self.begin == self.end
      # This varies, depending on Ruby's version!  It used to be 0.  As of Ruby 2.6, it is FloatDomainError: NaN.
      return (Float::INFINITY..Float::INFINITY).size
      # return 0
    elsif self.end.nil?
      # Behaves as Ruby does -
      #  Infinity::FLOAT_INFINITY for Numeric and nil for any other
      return (self.begin..nil).size
    else
      return Infinity::FLOAT_INFINITY
    end

  elsif @exclude_begin

    begin
      _dummy = 1.0 + self.begin() # _dummy to suppress warning: possibly useless use of + in void context

      # Numeric
      if defined? (self.begin().succ)
        Range.new(self.begin().succ, self.end, exclude_end?).send(__method__, *rest)
      else
        size_no_exclude = Range.new(self.begin, self.end).send(__method__, *rest) # exclude_end? == true, ie., Range with both ends inclusinve.
        diff = self.end - self.begin
        if diff.to_i == diff    # Integer difference
          return size_no_exclude - 1  # At least exclude_begin?==true (so exclude_end? does not matter)
        else
          return size_no_exclude
        end
      end
    rescue TypeError
      # Non-Numeric
      if defined? self.begin().succ
        Range.new(self.begin().succ, self.end, exclude_end?).send(__method__, *rest)  # => nil in Ruby 2.1
      else
        nil # See the line above.
        # raise TypeError, "can't iterate from "+self.begin.class.name
      end

    end

  else
    @rangepart.send(__method__, *rest)
  end
end

#step(*rest, &bloc) ⇒ RangeExtd, Enumerator

See #each.

Returns:

  • (RangeExtd)

    self

  • (Enumerator)

    if block is not given.

Raises:

  • (TypeError)

    If #exclude_begin? is true, and #begin() does not have the method [#succ], then even if no block is given, this method raises TypeError straightaway.



837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
# File 'lib/range_extd/range_extd.rb', line 837

def step(*rest, &bloc)
  # (1...3.5).each{|i|print i}  # => '123' to STDOUT
  # (1.3...3.5).each  # => #<Enumerator: 1.3...3.5:each>
  # (1.3...3.5).each{|i|print i}  # => TypeError: can't iterate from Float
  # Note: If the block is not given and if exclude_begin?() is true, the self in the returned Enumerator is not the same as self here.

  if @exclude_begin # including RangeExtd::NONE
    if defined? self.begin.succ
      ret = Range.new(self.begin.succ,self.end,exclude_end?).send(__method__, *rest, &bloc)
      if block_given?
        self
      else
        ret
      end
    elsif is_none?  # No need of null?(), supposedly!
      raise TypeError, "can't iterate for NONE range"
    else
      raise TypeError, "can't iterate from "+self.begin.class.name
    end
  else
    @rangepart.send(__method__, *rest, &bloc)
  end
end

#to_sString

Return eg., “(a<…c)”, “(a<..c)”, if #exclude_begin? is true, or else, identical to those for Range.

Returns:

  • (String)


597
598
599
# File 'lib/range_extd/range_extd.rb', line 597

def to_s
  re_inspect_core(__method__)
end