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 =
[]

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



126
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
# File 'lib/range_extd/range_extd.rb', line 126

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>)


1151
1152
1153
# File 'lib/range_extd/range_extd.rb', line 1151

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)


1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
# File 'lib/range_extd/range_extd.rb', line 1125

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 upwards) 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)


1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
# File 'lib/range_extd/range_extd.rb', line 1034

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]  # 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]  # 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)


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

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)


306
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
# File 'lib/range_extd/range_extd.rb', line 306

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:



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

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.



456
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
# File 'lib/range_extd/range_extd.rb', line 456

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)


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

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.



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

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:



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

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)


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

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)


352
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
# File 'lib/range_extd/range_extd.rb', line 352

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)


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

def exclude_begin?
  @exclude_begin
end

#exclude_end?Boolean

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

Returns:

  • (Boolean)


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

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:



537
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
# File 'lib/range_extd/range_extd.rb', line 537

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.



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

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)


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

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)


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

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)


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

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.



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

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.



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

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.



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

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.



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

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.



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

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.



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

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.



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

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.

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:



738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
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
# File 'lib/range_extd/range_extd.rb', line 738

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



809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
# File 'lib/range_extd/range_extd.rb', line 809

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)


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

def to_s
  re_inspect_core(__method__)
end