While Ruby support the Range class out of the box, is does not quite fullfil the role od a real Interval class. For instance, it does not support excluding the front sentinel. This is because Range also tries to do triple duty as a simple sequence and as a simple tuple-pair, thus limiting its potential as an Interval. The Interval class remedies the situation by commiting to interval behavior, and then extends the class’ capabilites beyond that of the standard Range in ways that naturally fall out of that.
Range depends on two methods: succ and #<=>. If numeric ranges were the only concern, those could just as well be #+ and #<=>, but esoteric forms make that unfeasible --the obvious example being a String range. But a proper Interval class requires mathematical continuation, thus the Interval depends on #+ and #<=>, as well as #- as the inverse of #+.
i = Interval.new(1,5) i.to_a #=> [1,2,3,4,5] i = Interval[0,5] i..step(2).to_a #=> [0,2,4] i = Interval[1,5] i.step(-1).to_a #=> [5,4,3,2,1] i = Interval[1,3] i.step(1,2).to_a #=> [1.0,1.5,2.0,2.5,3.0]
# File lib/facets/interval.rb, line 40 def self.[]( *args ) self.new( *args ) end
# File lib/facets/interval.rb, line 45 def initialize(first, last, exclude_first=false, exclude_last=false ) raise ArgumentError, "bad value for interval" if first.class != last.class @first = first @last = last @exclude_first = exclude_first @exclude_last = exclude_last @direction = (@last <=> @first) end
Unary shorthands. These return a new interval exclusive of first, last or both sentinels, repectively.
# File lib/facets/interval.rb, line 111 def +@ ; Interval.new(first, last, true, false) ; end
# File lib/facets/interval.rb, line 112 def -@ ; Interval.new(first, last, false, true) ; end
Returns a new interval inclusive of of both sentinels.
# File lib/facets/interval.rb, line 91 def closed; Interval.new(@first, @last, true, true) ; end
Returns true if the start and end sentinels are equal and the interval is closed; otherwise false.
# File lib/facets/interval.rb, line 77 def degenerate? ; @direction == 0 and ! (@exclusive_first or @exclusive_last) ; end
Returns the direction of the interval indicated by +1, 0 or -1.
(1..5).direction #=> 1 (5..1).direction #=> -1 (1..1).direction #=> 0
# File lib/facets/interval.rb, line 88 def direction ; @direction ; end
Returns the length of the interval as the difference between the first and last elements. Returns nil if the sentinal objects do not support distance comparison (distance).
TODO: Add n parameter to count segmentations like those produced by each.
# File lib/facets/interval.rb, line 128 def distance @last - @first #if @last.respond_to?( :distance ) # @last.distance( @first ) #else # #self.to_a.length #end end
def include?(x) tf = exclude_first? ? 1 : 0 tl = exclude_last? ? -1 : 0 # if other classes handled Infinity in their <=> method # (which probably they should) this clause would not be required if first.kind_of?(InfinityClass) ft = ((first <=> x) <= tf) else ft = (x <=> first) >= tf end if last.kind_of?(InfinityClass) fl = ((last <=> x) >= tl) else fl = (x <=> last) <= tl end ft && fl end
Iterates over the interval, passing each _n_th element to the block. If n is not given then n defaults to 1. Each _n_th step is determined by invoking +++ or \- n, depending on the direction of the interval. If n is negative the iteration is preformed in reverse form end sentinal to front sentinal. A second parameter, d, can be given in which case the applied step is calculated as a fraction of the interval’s length times n / d. This allows iteration over the whole interval in equal sized segments.
1..5.each { |e| ... } #=> 1 2 3 4 5 1..5.each(2) { |e| ... } #=> 1 3 5 1..5.each(1,2) { |e| ... } #=> 1.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0
@todo Deprecate arguments and simplify each definition accordingly.
# File lib/facets/interval.rb, line 193 def each(n=nil, d=nil) # :yield: if n warn "FACETS: `interval.each(n,d){...}` will be deprecated.\n" + "Use `interval.step(n,d).each{...}` instead." else n = 1 end return (n < 0 ? @last : @first) if degenerate? # is this right for all values of n ? s = d ? self.length.to_f * (n.to_f / d.to_f) : n.abs raise "Cannot iterate over zero length steps." if s == 0 s = s * @direction if n < 0 e = @exclude_last ? @last - s : @last #e = @exclude_last ? @last.pred(s) : @last t = @exclude_last ? 1 : 0 #while e.cmp(@first) >= t while (e <=> @first) >= t yield(e) e -= s #e = e.pred(s) end else e = @exclude_first ? @first + s : @first #e = @exclude_first ? @first.succ(s) : @first t = @exclude_last ? -1 : 0 #while e.cmp(@last) <= t while (e <=> @last) <= t yield(e) e += s #e = e.succ(s) end end end
Compares two intervals to see if they are equal
# File lib/facets/interval.rb, line 269 def eql?(other) return false unless @first == other.first return false unless @last == other.last return false unless @exclude_first == other.exclude_first? return false unless @exclude_last == other.exclude_last? true end
# File lib/facets/interval.rb, line 67 def exclude_first? ; @exclude_first ; end
# File lib/facets/interval.rb, line 68 def exclude_last? ; @exclude_last ; end
Returns the first or last sentinal of the interval.
# File lib/facets/interval.rb, line 63 def first ; @first ; end
Returns a new interval with one of the two sentinels opened or closed
# File lib/facets/interval.rb, line 104 def first_closed ; Interval.new(@first, @last, false, true) ; end
# File lib/facets/interval.rb, line 106 def first_opened ; Interval.new(@first, @last, true, false) ; end
Returns a new interval with either the first or the last sentinel exclusive. If the parameter is false, the deafult, then the first sentinel is excluded; if the parameter is true, the last sentinel is excluded.
# File lib/facets/interval.rb, line 99 def half_closed(e=false) e ? Interval.new(@first, @last, true, false) : Interval.new(@first, @last, false, true) end
Returns true or false if the element is part of the interval.
# File lib/facets/interval.rb, line 150 def include?(x) # todo: infinity? tf = exclude_first? ? 1 : 0 tl = exclude_last? ? -1 : 0 (x <=> first) >= tf and (x <=> last) <= tl end
# File lib/facets/interval.rb, line 64 def last ; @last ; end
# File lib/facets/interval.rb, line 105 def last_closed ; Interval.new(@first, @last, true, false) ; end
# File lib/facets/interval.rb, line 107 def last_opened ; Interval.new(@first, @last, false, true) ; end
Returns the greater of the first and last sentinals.
# File lib/facets/interval.rb, line 145 def max ((@first <=> @last) == 1) ? @first : @last end
Returns the lesser of the first and last sentinals.
# File lib/facets/interval.rb, line 140 def min ((@first <=> @last) == -1) ? @first : @last end
Returns true if the start and end sentinels are equal and the interval is open; otherwise false.
# File lib/facets/interval.rb, line 80 def null? ; @direction == 0 and @exclusive_first and @exclusive_last ; end
Returns a new interval exclusive of both sentinels.
# File lib/facets/interval.rb, line 94 def opened; Interval.new(@first, @last, true, true) ; end
Returns a new interval with the sentinels reversed.
(0..10).reversed #=> 10..0
# File lib/facets/interval.rb, line 119 def reversed Interval.new(@last, @first, true, true) end
Returns a two element array of first and last sentinels.
(0..10).sentinels #=> [0,10]
# File lib/facets/interval.rb, line 58 def sentinels return [@first, @last] end
# File lib/facets/interval.rb, line 230 def step(n=1, d=nil) # :yield: return (n < 0 ? @last : @first) if degenerate? # is this right for all values of n ? if block_given? s = d ? self.length.to_f * (n.to_f / d.to_f) : n.abs raise "Cannot iterate over zero length steps." if s == 0 s = s * @direction if n < 0 e = @exclude_last ? @last - s : @last #e = @exclude_last ? @last.pred(s) : @last t = @exclude_last ? 1 : 0 #while e.cmp(@first) >= t while (e <=> @first) >= t yield(e) e -= s #e = e.pred(s) end else e = @exclude_first ? @first + s : @first #e = @exclude_first ? @first.succ(s) : @first t = @exclude_last ? -1 : 0 #while e.cmp(@last) <= t while (e <=> @last) <= t yield(e) e += s #e = e.succ(s) end end else Enumerator.new(self, :step, n, d) end end
Generated with the Darkfish Rdoc Generator 2.