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 =
To conrol how the RangeExtd should be displayed or set (in one form). It can be read and reset by middle_strings and middle_strings= Default is [”, ”, ‘<’, ‘..’, ‘.’, ”, ”]
[]
Class Method Summary collapse
-
.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.
127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 |
# File 'lib/range_extd/range_extd.rb', line 127 def initialize(*inar, **hsopt) # **k expression from Ruby 1.9? if inar[4] == :Constant # Special case to create two Constants super(*inar[0..2]) @rangepart = (inar[2] ? (inar[0]...inar[1]) : (inar[0]..inar[1])) @exclude_end, @exclude_begin = inar[2..3] return end # Note: the order of exclude_begin? and end? is reversed from the input! arout = RangeExtd.send(:_get_init_args, *inar, hsopt) # == [RangeBeginValue, RangeEndValue, exclude_end?, exclude_begin?] ### The following routine is obsolete. ### Users, if they wish, should call RangeExtd::Infinity.overwrite_compare() beforehand. ### Or better, design their class properly in the first place! ### See the document in Object#<=> in this code for detail. # # # Modify (<=>) method for the given object, so that # # it becomes comparable with RangeExtd::Infinity, # # if the object is already Comparable. # # # # This must come first. # # Otherwise it may raise ArgumentError "bad value for range", # # because the native Range does not accept # # (Obj.new..RangeExtd::Infinity::POSITIVE) # # # boundary = nil # aroutid0 = arout[0].object_id # aroutid1 = arout[1].object_id # if aroutid0 == RangeExtd::Infinity::NEGATIVE.object_id || # aroutid0 == RangeExtd::Infinity::POSITIVE.object_id # boundary = arout[1] # elsif aroutid1 == RangeExtd::Infinity::NEGATIVE.object_id || # aroutid1 == RangeExtd::Infinity::POSITIVE.object_id # boundary = arout[0] # end # if (! boundary.nil?) && !defined?(boundary.infinity?) # RangeExtd::Infinity.overwrite_compare(boundary) # To modify (<=>) method for the given object. # # Infinity::CLASSES_ACCEPTABLE ... # end if ! RangeExtd.valid?(*arout) raise RangeError, "the combination of the arguments does not constitute a valid RangeExtd instance." end @exclude_begin = arout.pop @exclude_end = arout[-1] @rangepart = Range.new(*arout) super(*arout) end |
Class Method Details
.middle_strings ⇒ Array<String>
See RangExtd.middle_strings=() for detail.
1205 1206 1207 |
# File 'lib/range_extd/range_extd.rb', line 1205 def self.middle_strings() @@middle_strings end |
.middle_strings=(ary) ⇒ Array, Symbol
Set the class variable to be used in #to_s and #inspect to configure the format of their returned values.
The parameters should be given as an Array with 7 elements of string in principle, which gives the characters for each index:
-
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', '<', '<=', ''] )
1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 |
# File 'lib/range_extd/range_extd.rb', line 1179 def self.middle_strings=(ary) case ary when :default @@middle_strings = ['', '', '<', '..', '.', '', ''] when :math @@middle_strings = ['', '<=', '<', 'x', '<', '<=', ''] else begin if ary.size == 7 _dummy = 'a' + ary[6] @@middle_strings = ary if (ary[1] == ary[2]) || (ary[4] == ary[5]) warn "warning: some middle_strings are indistinguishable." end else raise end rescue raise ArgumentError, "invalid argument" end end end |
.new(range, [exclude_begin = false, [exclude_end=false]]) ⇒ Boolean .new(obj_begin, obj_end, [exclude_begin = false, [exclude_end=false]]) ⇒ Boolean
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 later) 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.
1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 |
# File 'lib/range_extd/range_extd.rb', line 1089 def self.valid?(*inar) (vbeg, vend, exc_beg, exc_end) = _get_init_args(*inar) if defined?(inar[0].is_none?) && inar[0].is_none? && exc_beg && exc_end return true end begin t = (vbeg <=> vend) begin if vend.nil? begin _ = (vbeg..nil) # Endless Range introduced in Ruby 2.6 return vbeg.class.method_defined?(:<=) rescue ArgumentError # Before Ruby 2.6 return false end end return false if t != -1*(vend <=> vbeg) # false if not commutative, or possibly exception (such as -1*nil). rescue NoMethodError, TypeError if (Float === vend && defined?(vbeg.infinity?) && vbeg.infinity?) || (Float === vbeg && defined?(vend.infinity?) && vend.infinity?) warn self.const_get(:ERR_MSGS)[:infinity_compare] if !$VERBOSE.nil? # one of the tests comes here. end return false # return end rescue # NoMethodError false # return else case t when -1 true when 0 if defined?(vbeg.<=) && defined?(vend.<=) # Comparable? ((true && exc_beg) ^! exc_end) # True if single value or empty, false if eg, (1...1) else false # Not Comparable end when 1 false else if (Float === vend && defined?(vbeg.infinity?) && vbeg.infinity?) || (Float === vbeg && defined?(vend.infinity?) && vend.infinity?) warn self.const_get(:ERR_MSGS)[:infinity_compare] if !$VERBOSE.nil? # not tested so far? end false # Not Comparable. end # case t # All statements of return above. end end |
Instance Method Details
#==(r) ⇒ Boolean
Like Range, returns true only if both of them are Range (or its subclasses), and in addition if both #exclude_begin? and #exclude_end? match (==) between the two objects. For the empty ranges they are somewhat different. In short, when both of them are empty and they belong to the same Class or have common ancestors (apart from Object and BasicObject, excluding all the included modules), this returns true, regardless of their boundary values. And any empty range is equal to RangeExtd::Infinity::NONE.
Note the last example will return false for #eql? – see #eql?
See #eql?
234 235 236 |
# File 'lib/range_extd/range_extd.rb', line 234 def ==(r) re_equal_core(r, :==) end |
#===(obj) ⇒ Boolean Also known as: include?, member?
If the object is open-ended to the negative (Infinity), this returns nil in default, unless the given object is Numeric (and comparable of Real), in which case this calls #cover?, or if self is ALL and the object is Comparable.
In the standard Range, this checks whether the given object is a member, hence,
(?D..?z) === ?c # => true
(?a..?z) === "cc" # => false
In the case of the former, after finite trials of [#succ] from ?c, it reaches the end (?z). In the latter, after finit trials of [#succ] from the begin ?a, it reaches the end (?z). Therefore it is theoretically possible to prove it (n.b., the actual algorithm of built-in Range#include? is different and cheating! See below.).
However, in the case of
(?D..Infinity) === ?c
it can never prove ?c is a member after infinite trials of [#succ], whether it starts the trials from the begin (?D) or the object (?c).
For anything but Numeric, use #cover? instead.
Note
(?B..?z) === 'dd' # => false
as Ruby’s Range knows the algorithm of String#succ and String#<=> and specifically checks with it, before using Enumerable#include?. https://github.com/ruby/ruby/blob/trunk/range.c
Therefore, even if you change the definition of String#succ so that ‘B’.succ => ‘dd’, ‘dd’.succ => ‘z’, as follows,
class String
alias :succ_orig :succ
def succ
if self == 'B'
'dd'
elsif self == 'dd'
'z'
else
:succ_orig
end
end
end
the resutl of Range#=== will unchange;
(?B..?z) === 'dd' # => false
(?B..?z).to_a # => ["B", "dd", "z"]
Similarly Range treats String differently;
(?X..?z).each do |i| print i;end # => "XYZ[\]^_`abcdefghijklmnopqrstuvwxyz"
?Z.succ # => 'AA'
307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 |
# File 'lib/range_extd/range_extd.rb', line 307 def ===(obj) # ("a".."z")===("cc") # => false return false if is_none? # No need of null?(), supposedly! begin 1.0+(obj) # OK if Numeric. rescue TypeError # obj is not Numeric, hence runs brute-force check. beg = self.begin() if defined?(beg.infinity?) && beg.infinity? || beg == -Infinity::FLOAT_INFINITY return nil # raise TypeError "can't iterate from -Infinity" end each do |ei| if ei == obj return true end end false else cover?(obj) end end |
#begin ⇒ Object
411 412 413 |
# File 'lib/range_extd/range_extd.rb', line 411 def begin() @rangepart.begin() end |
#bsearch(*rest, &bloc) ⇒ Object
bsearch is internally implemented by converting a float into 64-bit integer. The following examples demonstrate what is going on.
ary = [0, 4, 7, 10, 12]
(3...4).bsearch{ |i| ary[i] >= 11} # => nil
(3...5).bsearch{ |i| ary[i] >= 11} # => 4 (Integer)
(3..5.1).bsearch{ |i| ary[i] >= 11} # => 4.0 (Float)
(3.6..4).bsearch{ |i| ary[i] >= 11} # => 4.0 (Float)
(3.6...4).bsearch{ |i| ary[i] >= 11} # => nil
(3.6...4.1).bsearch{|i| ary[i] >= 11} # => 4.0 (Float)
class Special
def [](f)
(f>3.5 && f<4) ? true : false
end
end
sp = Special.new
(3..4).bsearch{ |i| sp[i]} # => nil
(3...4).bsearch{ |i| sp[i]} # => nil
(3.0...4).bsearch{|i| sp[i]} # => 3.5000000000000004
(3...4.0).bsearch{|i| sp[i]} # => 3.5000000000000004
(3.3..4).bsearch{ |i| sp[i]} # => 3.5000000000000004
(Rational(36,10)..5).bsearch{|i| ary[i] >= 11} => # TypeError: can't do binary search for Rational (Ruby 2.1)
(3..Rational(61,10)).bsearch{|i| ary[i] >= 11} => # TypeError: can't do binary search for Fixnum (Ruby 2.1)
In short, bsearch works only with Integer and/or Float (as in Ruby 2.1). If either of begin and end is an Float, the search is conducted in Float and the returned value will be Float, unless nil. If Float, it searches on the binary plane. If Integer, the search is conducted on the descrete Integer points only, and no search will be made in between the adjascent integers.
Given that, #bsearch follows basically the same, even when exclude_begin? is true. If either end is Float, it searches between begin*(1+Float::EPSILON) and end. If both are Integer, it searches from begin+1. When #exclude_begin? is false, #bsearch is identical to Range#bsearch.
457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 |
# File 'lib/range_extd/range_extd.rb', line 457 def bsearch(*rest, &bloc) if is_none? # No need of null?(), supposedly! raise TypeError, "can't do binary search for NONE range" end if @exclude_begin if ((Float === self.begin()) || (Integer === self.begin()) && (Float === self.end())) #NOTE: Range#bsearch accepts Infinity, whether it makes sense or not. # if Infinity::FLOAT_INFINITY == self.begin() # raise TypeError, "can't do binary search from -Infinity" # else Range.new(self.begin()*(Float::EPSILON+1.0), self.end, exclude_end?).send(__method__, *rest, &bloc) # @note Technically, if begin is Rational, there is no strong reason it should not work. # However Range#bsearch does not accept Rational (at Ruby 2.1), hence this code. # Users should give a RangeExtd with begin being Rational.to_f in that case. # end elsif (defined? self.begin().succ) # Both non-Float Range.new(self.begin().succ, self.end, exclude_end?).send(__method__, *rest, &bloc) # In practice it will not raise an Exception, only when both are Integer. else @rangepart.send(__method__, *rest, &bloc) # It will raise an exception anyway! Such as, (Rational..Rational) end else @rangepart.send(__method__, *rest, &bloc) end end |
#cover?(i) ⇒ Boolean
486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 |
# File 'lib/range_extd/range_extd.rb', line 486 def cover?(i) # ("a".."z").cover?("cc") # => true # (?B..?z).cover?('dd') # => true (though 'dd'.succ would never reach ?z) return false if is_none? # No need of null?(), supposedly! if @exclude_begin if self.begin == i false else @rangepart.send(__method__, i) end else @rangepart.send(__method__, i) end end |
#each(*rest, &bloc) ⇒ RangeExtd, Enumerator
508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 |
# File 'lib/range_extd/range_extd.rb', line 508 def each(*rest, &bloc) # (1...3.5).each{|i|print i} # => '123' to STDOUT # (1.3...3.5).each # => #<Enumerator: 1.3...3.5:each> # (1.3...3.5).each{|i|print i} # => TypeError: can't iterate from Float # Note: If the block is not given and if @exclude_begin is true, the self in the returned Enumerator is not the same as self here. if @exclude_begin # including RangeExtd::NONE if defined? self.begin.succ ret = Range.new(self.begin.succ,self.end,exclude_end?).send(__method__, *rest, &bloc) if block_given? self else ret end elsif is_none? raise TypeError, "can't iterate for NONE range" else raise TypeError, "can't iterate from "+self.begin.class.name end else @rangepart.send(__method__, *rest, &bloc) end end |
#end ⇒ Object
416 417 418 |
# File 'lib/range_extd/range_extd.rb', line 416 def end() @rangepart.end() end |
#eql?(r) ⇒ Boolean
251 252 253 |
# File 'lib/range_extd/range_extd.rb', line 251 def eql?(r) re_equal_core(r, :eql?) end |
#equiv?(other) ⇒ Boolean
Return true if self and the other are equivalent; if [#to_a] is defined, it is similar to
(self.to_a == other.to_a)
(though the ends are checked more rigorously), and if not, equivalent to
(self == other)
353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 |
# File 'lib/range_extd/range_extd.rb', line 353 def equiv?(other) # This routine is very similar to Range#equiv? except # exclude_begin? in this object is always defined, hence # a more thorough check is needed. t_or_f = (defined?(self.begin.succ) && defined?(other.begin.succ) && defined?(other.end) && defined?(other.exclude_end?)) if ! t_or_f return(self == other) # succ() for begin is not defined. else # Checking the begins. if defined?(other.exclude_begin?) other_excl_beg = other.exclude_begin? else other_excl_beg = false end if (self.begin == other.begin) if (exclude_begin? ^! other_excl_beg) # Pass else return false end else if (exclude_begin? ^! other_excl_beg) return false elsif (exclude_begin? && (self.begin.succ == other.begin)) || (other_excl_beg && (self.begin == other.begin.succ)) # Pass else return false end end # if (self.begin == other.begin) # else # Now, the begins agreed. Checking the ends. if (self.end == other.end) if (exclude_end? ^! other.exclude_end?) return true else return false end else # if (self.end == other.end) if (exclude_end? ^! other.exclude_end?) return false # elsif defined?(other.last) && (self.last(1) == other.last(1)) # Invalid for Ruby 1.8 or earlier # This is not good - eg., in this case, (1..5.5).equiv?(1..5.4) would return true. # return true elsif ( exclude_end? && defined?(other.end.succ) && (self.end == other.end.succ)) || (other.exclude_end? && defined?( self.end.succ) && (self.end.succ == other.end)) return true else return false end end # if (self.end == other.end) end # if ! t_or_f end |
#exclude_begin? ⇒ Boolean
Returns true if the “begin” boundary is excluded, or false otherwise.
202 203 204 |
# File 'lib/range_extd/range_extd.rb', line 202 def exclude_begin? @exclude_begin end |
#exclude_end? ⇒ Boolean
Returns true if the “end” boundary is excluded, or false otherwise.
207 208 209 |
# File 'lib/range_extd/range_extd.rb', line 207 def exclude_end? @exclude_end end |
#first(*rest) ⇒ Object, Array
Like Range#last, if no argument is given, it behaves like #begin(), that is, it returns the initial value, regardless of #exclude_begin?. However, if an argument is given (nb., acceptable since Ruby 1.9) when #exclude_begin? is true, it returns the array that starts from #begin().succ().
538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 |
# File 'lib/range_extd/range_extd.rb', line 538 def first(*rest) # (1...3.1).last # => 3.1 # (1...3.1).last(1) # => [3] if ! @exclude_begin # hence, not NONE. @rangepart.first(*rest) else case rest.size when 0 self.begin when 1 if (RUBY_VERSION < "1.9.1") && (1 == rest[0]) # Range#first() does not accept an argument in Ruby 1.8. raise ArgumentError, "wrong number of arguments (#{rest.size} for 0) (Use Ruby 1.9.2 or later)." end ## Check the argument. Array.new[ rest[0] ] # Check Type of rest[0] (if invalid, it should raise TypeError) begin if rest[0] < 0 raise ArgumentError, "negative array size (or size too big)" end rescue NoMethodError # Should not happen, but just to play safe. end ## Main if ! defined? self.begin.succ raise TypeError, "can't iterate from "+self.begin.class.name end Range.new(self.begin.succ, self.end, exclude_end?).send(__method__, *rest) else raise ArgumentError, "wrong number of arguments (#{rest.size} for 0..1)" end end # if ! @exclude_begin end |
#hash(*rest) ⇒ Object
When #exclude_begin? is true, the returned value is not strictly guaranteed to be unique, though in pracrtice it is most likely to be so.
578 579 580 581 582 583 584 |
# File 'lib/range_extd/range_extd.rb', line 578 def hash(*rest) if @exclude_begin @rangepart.send(__method__, *rest) - 1 else @rangepart.send(__method__, *rest) end end |
#inspect ⇒ String
Return eg., ‘(“a”<…“c”)’, ‘(“a”<..“c”)’, if #exclude_begin? is true, or else, identical to those for Range.
590 591 592 |
# File 'lib/range_extd/range_extd.rb', line 590 def inspect re_inspect_core(__method__) end |
#is_all? ⇒ Boolean
true if self is identical to ALL (#== does not mean it at all!)
196 197 198 |
# File 'lib/range_extd/range_extd.rb', line 196 def is_all? self.begin.object_id == Infinity::NEGATIVE.object_id && self.end.object_id == Infinity::POSITIVE.object_id && !@exclude_begin && !@exclude_end # Direct comparison with object_id should not work for this one!! (because users can create an identical one.) end |
#is_none? ⇒ Boolean
true if self is identical to NONE. This is different from #== method!
189 190 191 |
# File 'lib/range_extd/range_extd.rb', line 189 def is_none? self.begin.nil? && self.end.nil? && @exclude_begin && @exclude_end # Direct comparison with object_id should be OK? end |
#last(*rest) ⇒ Object, Array
See Range#last. If either (let alone both) side of the edge is Infinity, you can not give an argument in practice, the number of the members of the returned array.
609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 |
# File 'lib/range_extd/range_extd.rb', line 609 def last(*rest) return nil if null? nSize = rest.size case nSize when 0 self.end when 1 if (RUBY_VERSION < "1.9.1") && (1 == rest[0]) # Range#first() does not accept an argument in Ruby 1.8. raise ArgumentError, "wrong number of arguments (#{rest.size} for 0) (Use Ruby 1.9.2 or later)." end if defined?(self.begin.infinity?) && self.begin.infinity? || self.begin == -Infinity::FLOAT_INFINITY raise TypeError, "can't iterate from "+self.begin.to_s elsif defined?(self.end.infinity?) && self.end.infinity? || self.end == Infinity::FLOAT_INFINITY raise TypeError, "can't get elements to "+self.end.to_s elsif ! defined? self.begin.succ raise TypeError, "can't iterate from "+self.begin.class.name else @rangepart.send(__method__, *rest) end else raise ArgumentError, "wrong number of arguments (#{rest.size} for 0..1)" end end |
#max(*rest, &bloc) ⇒ Object
See #first for the definition when #exclude_begin? is true.
670 671 672 |
# File 'lib/range_extd/range_extd.rb', line 670 def max(*rest, &bloc) re_min_max_core(__method__, *rest, &bloc) end |
#max_by(*rest, &bloc) ⇒ Object
See #first for the definition when #exclude_begin? is true.
676 677 678 |
# File 'lib/range_extd/range_extd.rb', line 676 def max_by(*rest, &bloc) re_min_max_core(__method__, *rest, &bloc) end |
#min(*rest, &bloc) ⇒ Object
See #first for the definition when #exclude_begin? is true.
637 638 639 |
# File 'lib/range_extd/range_extd.rb', line 637 def min(*rest, &bloc) re_min_max_core(__method__, *rest, &bloc) end |
#min_by(*rest, &bloc) ⇒ Object
See #first for the definition when #exclude_begin? is true.
643 644 645 |
# File 'lib/range_extd/range_extd.rb', line 643 def min_by(*rest, &bloc) re_min_max_core(__method__, *rest, &bloc) end |
#minmax(*rest, &bloc) ⇒ Object
See #first for the definition when #exclude_begin? is true.
650 651 652 653 654 655 656 |
# File 'lib/range_extd/range_extd.rb', line 650 def minmax(*rest, &bloc) # (0...3.5).minmax # => [0, 3] # (1.3...5).minmax # => TypeError: can't iterate from Float # Note that max() for the same Range raises an exception. # In that sense, it is inconsistent! re_min_max_core(__method__, *rest, &bloc) end |
#minmax_by(*rest, &bloc) ⇒ Object
See #first for the definition when #exclude_begin? is true.
660 661 662 663 664 665 |
# File 'lib/range_extd/range_extd.rb', line 660 def minmax_by(*rest, &bloc) # (0...3.5).minmax # => [0, 3] # Note that max() for the same Range raises an exception. # In that sense, it is inconsistent! re_min_max_core(__method__, *rest, &bloc) end |
#size(*rest) ⇒ Integer, ...
When both ends n are the same INFINITY (of the same parity), (n..n).size used to be 0. As of Ruby 2.6, it is FloatDomainError: NaN. This routine follows what Ruby produces, depending on Ruby’s version it is run on.
Implementation of Range#size to this class.
It is essentially the same, but the behaviour when #exclude_begin? is true may not always be natural. See #first for the definition when #exclude_begin? is true.
Range#size only works for Numeric ranges. And in Range#size, the value is calculated when the initial value is non-Integer, by stepping by 1.0 from the #begin value, and the returned value is an integer. For example,
(1.4..2.6).size == 2
because both 1.4 and 2.4 (== 1.4+1.0) are included in the Range.
That means you had better be careful with the uncertainty (error) of floating-point. For example, at least in an environment,
4.8 - 4.5 # => 0.2999999999999998
(2.5...4.5000000000000021).size => 2
(2.8...4.8000000000000021).size => 3
(2.8..4.8).size => 3
In #size, the principle is the same. If the #begin value has the method [#succ] defined, the object is regarded to consist of discrete values. If not, it is a range with continuous elements. This dinstinguishment affects the behavious seriously in some cases when #exclude_begin? is true. For example, the following two cases may seem unnatural.
RangeExtd(1..5, true, true) == RangeExtd(Rational(1,1), 5, true, true)
RangeExtd(1..5, true, true).size != RangeExtd(Rational(1,1), 5, true, true).size
Although those two objects are equal by [#==], they are different in nature, as far as Range and RangeExtd are concerned, and that is why they work differently;
RangeExtd(1..5, true, true).eql?(RangeExtd(Rational(1,1), 5, true, true)) # => false
RangeExtd(1..5, true, true).to_a # => [2, 3, 4]
RangeExtd(1..5, true, true).to_a.size # => 3
RangeExtd(Rational(1,1)..5).to_a # => TypeError
Also, the floating-point uncertainties in Float can more often be problematic; for example, in an environment,
4.4 - 2.4 # => 2.0000000000000004
4.8 - 2.8 # => 2.0
RangeExtd(2.4..4.4, true, true).size # => 3
RangeExtd(2.8..4.8, true, true).size # => 2
The last example is what you would naively expect, because both
2.8+a(lim a->0) and 3.8+a(lim a->0) are
in the range whereas 4.8 is not in the range by definition, but not the example right above.
Ruby 2.6 Endless Range and Infinity.
Before RangeExtd Ver.1.1, if a RangeExtd object contains Infinity objects for either begin or end, #size used to be always Float::INFINITY
no matter what the other object is (except when the other object is also a Infinity object). However, since the introduction of the endless Range in Ruby 2.6, Ruby returns as follows:
(5..).size # => Float::INFINITY
(?a..).size # => nil
Accordingly, this class RangeExtd now behaves the same as Ruby (2.6 or later).
Similarly,
(Float::INFINITY..Float::INFINITY).size
has changed (I do not know in which Ruby version)! It used to be 0. However, As of Ruby 2.6, it is FloatDomainError: NaN Again this class now follows Ruby’s default (RangeExtd Ver.1.0 or later).
761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 |
# File 'lib/range_extd/range_extd.rb', line 761 def size(*rest) # (1..5).size # => 5 # (1...5).size # => 4 # (0.8...5).size # => 5 # Why??? # (1.2...5).size # => 4 # Why??? # (1.2..5).size # => 4 # Why??? # (Rational(3,2)...5).size # => 3 # (1.5...5).size # => 4 # Why not 3?? # (1.5...4.9).size # => 4 # Why not 3?? # (1.5...4.5).size # => 3 # (0...Float::INFINITY).size # => Infinity if is_none? # No need of null?(), supposedly! return 0 ### (Infinity..Infinity) => 0 (as in Ruby 2.1) # elsif self.begin().infinity? || self.end().infinity? # return Infinity::FLOAT_INFINITY # Checking Infinity. # Note (Infinity..Infinity) => 0 (Range as in Ruby 2.1) # however, elsif (defined?(self.begin.infinity?) && self.begin.infinity? || self.begin == -Infinity::FLOAT_INFINITY) || (defined?(self.end.infinity?) && self.end.infinity? || self.end == Infinity::FLOAT_INFINITY) || (self.end.nil?) # RangeExtd#end can be nil only for Ruby-2.6 if self.begin == self.end # This varies, depending on Ruby's version! It used to be 0. As of Ruby 2.6, it is FloatDomainError: NaN. return (Float::INFINITY..Float::INFINITY).size # return 0 elsif self.end.nil? # Behaves as Ruby does - # Infinity::FLOAT_INFINITY for Numeric and nil for any other return (self.begin..nil).size else return Infinity::FLOAT_INFINITY end elsif @exclude_begin begin _dummy = 1.0 + self.begin() # _dummy to suppress warning: possibly useless use of + in void context # Numeric if defined? (self.begin().succ) Range.new(self.begin().succ, self.end, exclude_end?).send(__method__, *rest) else size_no_exclude = Range.new(self.begin, self.end).send(__method__, *rest) # exclude_end? == true, ie., Range with both ends inclusinve. diff = self.end - self.begin if diff.to_i == diff # Integer difference return size_no_exclude - 1 # At least exclude_begin?==true (so exclude_end? does not matter) else return size_no_exclude end end rescue TypeError # Non-Numeric if defined? self.begin().succ Range.new(self.begin().succ, self.end, exclude_end?).send(__method__, *rest) # => nil in Ruby 2.1 else nil # See the line above. # raise TypeError, "can't iterate from "+self.begin.class.name end end else @rangepart.send(__method__, *rest) end end |
#step(*rest, &bloc) ⇒ RangeExtd, Enumerator
See #each.
837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 |
# File 'lib/range_extd/range_extd.rb', line 837 def step(*rest, &bloc) # (1...3.5).each{|i|print i} # => '123' to STDOUT # (1.3...3.5).each # => #<Enumerator: 1.3...3.5:each> # (1.3...3.5).each{|i|print i} # => TypeError: can't iterate from Float # Note: If the block is not given and if exclude_begin?() is true, the self in the returned Enumerator is not the same as self here. if @exclude_begin # including RangeExtd::NONE if defined? self.begin.succ ret = Range.new(self.begin.succ,self.end,exclude_end?).send(__method__, *rest, &bloc) if block_given? self else ret end elsif is_none? # No need of null?(), supposedly! raise TypeError, "can't iterate for NONE range" else raise TypeError, "can't iterate from "+self.begin.class.name end else @rangepart.send(__method__, *rest, &bloc) end end |
#to_s ⇒ String
Return eg., “(a<…c)”, “(a<..c)”, if #exclude_begin? is true, or else, identical to those for Range.
597 598 599 |
# File 'lib/range_extd/range_extd.rb', line 597 def to_s re_inspect_core(__method__) end |