Class: RangeExtd
- 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
-
.middle_strings ⇒ Array<String>
See RangExtd.middle_strings=() for detail.
- .middle_strings=(ary) ⇒ Array, Symbol
-
.valid?(*inar) ⇒ Boolean
Returns true if the range to be constructed (or given) is valid, as a range, accepted in RangeExtd.
Instance Method Summary collapse
-
#==(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.
-
#===(obj) ⇒ Boolean
(also: #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.
- #begin ⇒ Object
-
#bsearch(*rest, &bloc) ⇒ Object
bsearch is internally implemented by converting a float into 64-bit integer.
- #cover?(i) ⇒ Boolean
- #each(*rest, &bloc) ⇒ RangeExtd, Enumerator
- #end ⇒ Object
-
#eql?(r) ⇒ Boolean
The same as #== but it uses eql?() as each comparison.
-
#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).
-
#exclude_begin? ⇒ Boolean
Returns true if the “begin” boundary is excluded, or false otherwise.
-
#exclude_end? ⇒ Boolean
Returns true if the “end” boundary is excluded, or false otherwise.
-
#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?.
-
#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.
-
#initialize(*inar, **hsopt) ⇒ RangeExtd
constructor
Note if you use the third form with “string_form” with the user-defined string (via RangeExtd.middle_strings=()), make 100 per cent sure you know what you are doing.
-
#inspect ⇒ String
Return eg., ‘(“a”<…“c”)’, ‘(“a”<..“c”)’, if #exclude_begin? is true, or else, identical to those for Range.
-
#is_all? ⇒ Boolean
true if self is identical to ALL (#== does not mean it at all!).
-
#is_none? ⇒ Boolean
true if self is identical to NONE.
-
#last(*rest) ⇒ Object, Array
See Range#last.
-
#max(*rest, &bloc) ⇒ Object
See #first for the definition when #exclude_begin? is true.
-
#max_by(*rest, &bloc) ⇒ Object
See #first for the definition when #exclude_begin? is true.
-
#min(*rest, &bloc) ⇒ Object
See #first for the definition when #exclude_begin? is true.
-
#min_by(*rest, &bloc) ⇒ Object
See #first for the definition when #exclude_begin? is true.
-
#minmax(*rest, &bloc) ⇒ Object
See #first for the definition when #exclude_begin? is true.
-
#minmax_by(*rest, &bloc) ⇒ Object
See #first for the definition when #exclude_begin? is true.
-
#size(*rest) ⇒ Integer, ...
Implementation of Range#size to this class.
-
#step(*rest, &bloc) ⇒ RangeExtd, Enumerator
See #each.
-
#to_s ⇒ String
Return eg., “(a<…c)”, “(a<..c)”, if #exclude_begin? is true, or else, identical to those for Range.
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
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.
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_strings ⇒ Array<String>
See RangExtd.middle_strings=() for detail.
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:
-
prefix
-
begin-inclusive
-
begin-exclusive
-
middle-string to bridge both ends
-
end-exclusive
-
end-inclusive
-
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', '<', '<=', ''] )
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
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:
-
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).
-
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.
-
#begin must be smaller than or equal to #end, that is, (#begin <=> #end) must be either -1 or 0.
-
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.
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?
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'
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 |
#begin ⇒ Object
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
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
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 |
#end ⇒ Object
415 416 417 |
# File 'lib/range_extd/range_extd.rb', line 415 def end() @rangepart.end() end |
#eql?(r) ⇒ 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)
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.
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.
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().
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 |
#inspect ⇒ String
Return eg., ‘(“a”<…“c”)’, ‘(“a”<..“c”)’, if #exclude_begin? is true, or else, identical to those for Range.
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!)
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!
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.
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, ...
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.
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.
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_s ⇒ String
Return eg., “(a<…c)”, “(a<..c)”, if #exclude_begin? is true, or else, identical to those for Range.
596 597 598 |
# File 'lib/range_extd/range_extd.rb', line 596 def to_s re_inspect_core(__method__) end |