Class: Range

Inherits:
Object show all
Defined in:
lib/range_extd/range.rb

Overview

Summary

Modifies #== and add methods of #valid?, #empty?, #null?, #is_none? and #is_all?.

Direct Known Subclasses

RangeExtd

Instance Method Summary collapse

Instance Method Details

#==(r) ⇒ Object

It is extended to handle RangeExtd objects. For each element, that is, Range#begin and Range#end, this uses their method of ==().

As long as the comparison is limited within Range objects, the returned value of this method has unchanged.

A note of caution is, some ranges which the built-in Range accepts, are now regarded as NOT valid, such as, (1…1) and (nil..nil) (the latter was not permitted in Ruby 1.8), though you can still use them;

(1...1).valid?   # => false

On the other hand, RangeExtd class does not accept or create any invalid range; for any RangeExtd object, RangeExtd#valid? returns true. For example, there is no RangeExtd object that is expressed as (1…1) (See #valid? for detail).

For that reason, when those non-valid Range objects are compared with a RangeExtd object, the returned value may not be what you would expect. For example,

(1...1) == RangeExtd(1, 1, true, true)  # => false.

The former is an invalid range, while the latter is a rigidly-defined empty range.

Consult #valid? and RangeExtd#== for more detail.



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

def ==(r)
  _equal_core(r, :==, :equal_prerangeextd?)
end

#empty?Boolean?

Note:

#empty? returns nil when the object is invalid, and hence invalid objects may appear to be not empty. If you want to get true when the object is either empty or invalid, use #null? instead.

Returns true if self is empty. Returns nil if self is not valid (nb., any RangeExtd instance is valid.) Otherwise false.

The definition of what is empty is as follow.

  1. the range must be valid: #valid? => true

  2. if it is either a beginless or endless Range, returns false.

  3. if the range id discrete, that is, #begin has #succ method, there must be no member within the range: returns Range#to_a.empty?

  4. if the range is continuous, that is, #begin does not have #succ method, #begin and #end must be equal ((#begin <=> #end) => 0) and both the boundaries must be excluded: (RangeExtd#exclude_begin? && #exclude_end?) == true. Note that ranges with equal #begin and #end with inconsistent two exclude status are not valid, and the built-in Range always has the “begin-exclude” status of false.

In these conditions, none of Range instance would return true in #empty?.

See #valid? and RangeExtd.valid? for the definition of the validity.

Examples:

(nil..nil).empty?  # => false
(nil..3).empty?    # => false
(true..true).empty?# => nil
(1...1).empty?     # => nil
(1..1).empty?      # => false
RangeExtd(1...1,   true).empty? # => true
RangeExtd(1...2,   true).empty? # => true
RangeExtd(1.0...2, true).empty? # => false
RangeExtd(?a...?b, true).empty? # => true
RangeExtd::NONE.empty?          # => true

Returns:

  • (Boolean, nil)


161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
# File 'lib/range_extd/range.rb', line 161

def empty?
  # This is basically for the sake of sub-classes, as any built-in Range instance
  # always returns either nil or false.

  if !valid?
    return nil
  elsif respond_to?(:is_none?) && is_none?
    # RangeExtd::NONE
    return true
  elsif self.begin.nil? || self.end.nil?
    return false
  end

  t = (self.begin() <=> self.end())
  case t
  when -1
    if (defined?(self.exclude_begin?)) &&
        exclude_begin? &&
        exclude_end? &&
        defined?(self.begin().succ) &&
        (self.begin().succ == self.end())
      true  # e.g., ("a"<..."b")
    else
      false
    end
  when 0
    if defined?(self.boundary) && self.boundary.nil?
      # RangeExtd::NONE or RangeExtd::All
      if self.exclude_end?
        true  # RangeExtd::NONE, though this should have been already recognized.
      else
        false # RangeExtd::ALL
      end
    else
      if defined?(self.exclude_begin?)
        t2 = self.exclude_begin?
      else
        t2 = false  # == return false
      end
      (t2 && exclude_end?)
    end
  when 1
    nil # redundant, as it should not be valid in the first place.
  else
    nil # redundant, as it should not be valid in the first place.
  end
end

#equal_prerangeextd?Object

No overwriting.



10
# File 'lib/range_extd/range.rb', line 10

alias_method :equal_prerangeextd?, :==

#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:

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

Parameters:

Returns:

  • (Boolean)


274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
# File 'lib/range_extd/range.rb', line 274

def equiv?(other)
  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.exclude_begin? # The other is RangeExtd with exclude_begin?==true.
      if self.begin != other.begin.succ
        return false
      else
        # Pass
      end
    elsif (self.begin != other.begin)
      return false
    end

    # 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 (      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

#equiv_all?Boolean

true if self is equivalent to RangeExtd::ALL

Examples:

(RangeExtd::Infinity::NEGATIVE..RangeExtd::Infinity::POSITIVE).equiv_all?  # => true
(nil..nil).equiv_all?  # => true
(nil...nil).equiv_all? # => false

Returns:

  • (Boolean)


249
250
251
252
253
254
255
256
# File 'lib/range_extd/range.rb', line 249

def equiv_all?
  return false if respond_to?(:is_none?) && is_none?  # Essential! (b/c RangeExtd::NONE.is_all? looks like (nil..nil))
  return false if exclude_end?
  return false if respond_to?(:exclude_begin?) && exclude_begin?

  (self.begin == RangeExtd::Infinity::NEGATIVE || self.begin.nil? || self.begin == -Float::INFINITY) &&
  (self.end   == RangeExtd::Infinity::POSITIVE || self.end.nil?   || self.end   ==  Float::INFINITY)
end

#is_all?Boolean

true only if self is eql? to RangeExtd::ALL

true if self is identical (eql?) to RangeExtd::ALL

(This is different from #==.)

Examples:

(RangeExtd(RangeExtd::Infinity::NEGATIVE..RangeExtd::Infinity::POSITIVE).is_all?
  # => false because it is NOT RangeExtd

Returns:

  • (Boolean)


235
236
237
238
239
240
241
# File 'lib/range_extd/range.rb', line 235

def is_all?
  return false if !respond_to?(:exclude_begin?)  # Must be RangeExtd
  return false if exclude_begin? || exclude_end?
  return false if is_none?  # Essential! (b/c RangeExtd::NONE.is_all? looks like (nil..nil))

  (self.begin.eql?(RangeExtd::Infinity::NEGATIVE) && self.end.eql?(RangeExtd::Infinity::POSITIVE))
end

#is_none?FalseClass

This method is overwritten in RangeExtd

Returns:

  • (FalseClass)


222
223
224
# File 'lib/range_extd/range.rb', line 222

def is_none?
  false
end

#null?Boolean

Returns true if it is either empty or invalid, or false otherwise.

See #empty? and #valid?.

Even RangeExtd (with RangeExtd#is_none? being false) can be null.

Returns:

  • (Boolean)


215
216
217
# File 'lib/range_extd/range.rb', line 215

def null?
  (! valid?) || empty?
end

#size(*rest) ⇒ Integer, NilClass

RangeExtd::Infinity objects are considered

Other than those, identical to the original #size

Size is tricky. For example, (nil..).size should be nil according to the specification https://ruby-doc.org/core-3.1.2/Range.html#method-i-size but it returns Float::INFINITY (in Ruby-3.1)

See RangeExtd#size for more in-depth discussion.

Returns:

Raises:

  • (FloatDomainError)

    (Infinity..Infinity).size (as in Ruby-3.1, though it used to be 0 in Ruby-2.1)



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/range_extd/range.rb', line 63

def size(*rest)
  rbeg = self.begin
  rend = self.end

  # Both sides are (general) Infinity
  if (rbeg.respond_to?(:infinity?) && rbeg.infinity? && 
      rend.respond_to?(:infinity?) && rend.infinity?)
    if    rbeg.negative? && rend.positive? 
      # should be nil according to the specification
      #  https://ruby-doc.org/core-3.1.2/Range.html#method-i-size
      # but this returns Float::INFINITY (in Ruby-3.1)
      return (nil..).size
    elsif rbeg.positive? && rend.negative? 
      return (Float::INFINITY..(-Float::INFINITY)).size
    else
      ## NOTE:
      # (Infinity..Infinity) => 0                 (as in Ruby 2.1)
      # (Infinity..Infinity) => FloatDomainError  (as in Ruby 3.1)
      return (Float::INFINITY..Float::INFINITY).size
    end
  end

  # Checking Infinities.
  #
  if    rbeg.respond_to?(:infinity?) && rbeg.infinity?  # but not self.end!
    return (..rend).size
  elsif rend.respond_to?(:infinity?) && rend.infinity?  # but not self.begin!
    return (rbeg..).size
  end

  size_prerangeextd?(*rest)
end

#size_prerangeextd?Object

Same as #==, but the comparison is made with eql?() method. def eql?®

_equal_core(r, :eql?, :eql_prerangeextd?)

end



49
# File 'lib/range_extd/range.rb', line 49

alias_method :size_prerangeextd?, :size

#valid?Boolean

Note:

By definition, all the RangeExtd instances are valid, because RangeExtd#initialize (RangeExtd.new) checks the validity.

Returns true if self is valid as a comparable range.

See RangeExtd.valid? for the definition of what is valid and more examples.

See #empty? and #null?, too.

Examples:

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

Returns:

  • (Boolean)


116
117
118
# File 'lib/range_extd/range.rb', line 116

def valid?
  RangeExtd.valid?(self)
end