Class: RangeExtd

Inherits:
Range show all
Defined in:
lib/range_extd/range_extd.rb,
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

NONE =

Two constants

RangeExtd.new(nil...nil, true, true)
ALL =
RangeExtd.new(Infinity::NEGATIVE..Infinity::POSITIVE, false, false)
@@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 of 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.

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



35
36
37
38
39
# File 'lib/range_extd/range_extd.rb', line 35

def initialize(rangepart, ex_begin, ex_end)
  @rangepart = rangepart
  @exclude_begin = ex_begin
  @exclude_end   = ex_end
end

Class Method Details

.middle_stringsArray<String>

See RangExtd.middle_strings=() for detail.

Returns:

  • (Array<String>)


1115
1116
1117
# File 'lib/range_extd/range_extd.rb', line 1115

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)


1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
# File 'lib/range_extd/range_extd.rb', line 1089

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. Both #begin and #end elements must be Comparable to each other, and the comparison results must be consistent betwen the two. The sole exception is NONE, which is valid. For example, (nil..nil) is NOT valid (nb., it raised Exception in Ruby 1.8).

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

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

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


1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
# File 'lib/range_extd/range_extd.rb', line 1006

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
      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 "Float::INFINITY is not comparable with other Infinity."
      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 "Float::INFINITY is not comparable with other Infinity."
      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)


224
225
226
# File 'lib/range_extd/range_extd.rb', line 224

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)


297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
# File 'lib/range_extd/range_extd.rb', line 297

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:



401
402
403
# File 'lib/range_extd/range_extd.rb', line 401

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.



447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
# File 'lib/range_extd/range_extd.rb', line 447

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)


476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
# File 'lib/range_extd/range_extd.rb', line 476

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.



498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
# File 'lib/range_extd/range_extd.rb', line 498

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:



406
407
408
# File 'lib/range_extd/range_extd.rb', line 406

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)


241
242
243
# File 'lib/range_extd/range_extd.rb', line 241

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)


343
344
345
346
347
348
349
350
351
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
# File 'lib/range_extd/range_extd.rb', line 343

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)


192
193
194
# File 'lib/range_extd/range_extd.rb', line 192

def exclude_begin?
  @exclude_begin
end

#exclude_end?Boolean

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

Returns:

  • (Boolean)


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

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:



528
529
530
531
532
533
534
535
536
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
# File 'lib/range_extd/range_extd.rb', line 528

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.



568
569
570
571
572
573
574
# File 'lib/range_extd/range_extd.rb', line 568

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)


580
581
582
# File 'lib/range_extd/range_extd.rb', line 580

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)


186
187
188
# File 'lib/range_extd/range_extd.rb', line 186

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)


179
180
181
# File 'lib/range_extd/range_extd.rb', line 179

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.



599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
# File 'lib/range_extd/range_extd.rb', line 599

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.



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

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.



666
667
668
# File 'lib/range_extd/range_extd.rb', line 666

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.



627
628
629
# File 'lib/range_extd/range_extd.rb', line 627

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.



633
634
635
# File 'lib/range_extd/range_extd.rb', line 633

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.



640
641
642
643
644
645
646
# File 'lib/range_extd/range_extd.rb', line 640

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.



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

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

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:



725
726
727
728
729
730
731
732
733
734
735
736
737
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
# File 'lib/range_extd/range_extd.rb', line 725

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



794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
# File 'lib/range_extd/range_extd.rb', line 794

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)


587
588
589
# File 'lib/range_extd/range_extd.rb', line 587

def to_s
  re_inspect_core(__method__)
end