# class Interval

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]

### Public Class Methods

[]( *args ) click to toggle source

    # File lib/facets/interval.rb, line 40
    def self.[]( *args )
      self.new( *args )
    end

new(first, last, exclude_first=false, exclude_last=false ) click to toggle source

    # 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

### Public Instance Methods

+@() click to toggle source

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

-@() click to toggle source

    # File lib/facets/interval.rb, line 112
    def -@ ; Interval.new(first, last, false, true) ; end

===(x)
Alias for: include?
begin()

(IMHO) these should be deprectated

Alias for: first
closed() click to toggle source

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

degenerate?() click to toggle source

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

direction() click to toggle source

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

distance() click to toggle source

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

Also aliased as: length, size
each(n=nil, d=nil) { || ... } click to toggle source

    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

end()
Alias for: last
eql?(other) click to toggle source

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

exclude_begin?()
Alias for: exclude_first?
exclude_end?()
Alias for: exclude_last?
exclude_first?() click to toggle source

    # File lib/facets/interval.rb, line 67
    def exclude_first? ; @exclude_first ; end

Also aliased as: exclude_begin?
exclude_last?() click to toggle source

    # File lib/facets/interval.rb, line 68
    def exclude_last? ; @exclude_last ; end

Also aliased as: exclude_end?
first() click to toggle source

Returns the first or last sentinal of the interval.

    # File lib/facets/interval.rb, line 63
    def first ; @first ; end

Also aliased as: begin
first_closed() click to toggle source

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

first_opened() click to toggle source

    # File lib/facets/interval.rb, line 106
    def first_opened ; Interval.new(@first, @last, true, false) ; end

half_closed(e=false) click to toggle source

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

include?(x) click to toggle source

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

Also aliased as: ===, member?
last() click to toggle source

    # File lib/facets/interval.rb, line 64
    def last ; @last ; end

Also aliased as: end
last_closed() click to toggle source

    # File lib/facets/interval.rb, line 105
    def last_closed  ; Interval.new(@first, @last, true, false) ; end

last_opened() click to toggle source

    # File lib/facets/interval.rb, line 107
    def last_opened  ; Interval.new(@first, @last, false, true) ; end

length()
Alias for: distance
max() click to toggle source

Returns the greater of the first and last sentinals.

    # File lib/facets/interval.rb, line 145
    def max
      ((@first <=> @last) == 1) ? @first : @last
    end

member?(x)
Alias for: include?
min() click to toggle source

Returns the lesser of the first and last sentinals.

    # File lib/facets/interval.rb, line 140
    def min
      ((@first <=> @last) == -1) ? @first : @last
    end

null?() click to toggle source

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

opened() click to toggle source

Returns a new interval exclusive of both sentinels.

    # File lib/facets/interval.rb, line 94
    def opened; Interval.new(@first, @last, true, true) ; end

reversed() click to toggle source

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

sentinels() click to toggle source

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

size()
Alias for: distance
step(n=1, d=nil) { || ... } click to toggle source

    # 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

~() click to toggle source

    # File lib/facets/interval.rb, line 113
    def ~ ; Interval.new(first, last, true, true) ; end