Class: Interval

Inherits:
Object show all
Includes:
Argumentable, Multiton
Defined in:
lib/standard/facets/interval.rb

Overview

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]

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Multiton

#_dump, #clone, #dup

Methods included from Multiton::Inclusive

#included

Constructor Details

#initialize(first, last, exclude_first = false, exclude_last = false) ⇒ Interval

Returns a new instance of Interval.

Raises:

  • (ArgumentError)

45
46
47
48
49
50
51
52
# File 'lib/standard/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

Class Method Details

.[](*args) ⇒ Object


40
41
42
# File 'lib/standard/facets/interval.rb', line 40

def self.[]( *args )
  self.new( *args )
end

Instance Method Details

#[email protected]Object

Unary shorthands. These return a new interval exclusive of first, last or both sentinels, repectively.


111
# File 'lib/standard/facets/interval.rb', line 111

def [email protected] ; Interval.new(first, last, true, false) ; end

#[email protected]Object


112
# File 'lib/standard/facets/interval.rb', line 112

def [email protected] ; Interval.new(first, last, false, true) ; end

#closedObject

Returns a new interval inclusive of of both sentinels.


91
# File 'lib/standard/facets/interval.rb', line 91

def closed; Interval.new(@first, @last, true, true) ; end

#degenerate?Boolean

Returns true if the start and end sentinels are equal and the interval is closed; otherwise false.

Returns:

  • (Boolean)

77
# File 'lib/standard/facets/interval.rb', line 77

def degenerate? ; @direction == 0 and ! (@exclusive_first or @exclusive_last) ;  end

#directionObject

Returns the direction of the interval indicated by +1, 0 or -1.

(1..5).direction  #=> 1
(5..1).direction  #=> -1
(1..1).direction  #=> 0

88
# File 'lib/standard/facets/interval.rb', line 88

def direction ; @direction ; end

#distanceObject Also known as: length, size

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.


128
129
130
131
132
133
134
135
# File 'lib/standard/facets/interval.rb', line 128

def distance
  @last - @first
  #if @last.respond_to?( :distance )
  #  @last.distance( @first )
  #else
  #  #self.to_a.length
  #end
end

#each(n = nil, d = nil) ⇒ Object

TODO:

Deprecate arguments and simplify each definition accordingly.

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

193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
# File 'lib/standard/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

#eql?(other) ⇒ Boolean

Compares two intervals to see if they are equal

Returns:

  • (Boolean)

269
270
271
272
273
274
275
# File 'lib/standard/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_first?Boolean Also known as: exclude_begin?

Returns:

  • (Boolean)

67
# File 'lib/standard/facets/interval.rb', line 67

def exclude_first? ; @exclude_first ; end

#exclude_last?Boolean Also known as: exclude_end?

Returns:

  • (Boolean)

68
# File 'lib/standard/facets/interval.rb', line 68

def exclude_last? ; @exclude_last ; end

#firstObject Also known as: begin

Returns the first or last sentinal of the interval.


63
# File 'lib/standard/facets/interval.rb', line 63

def first ; @first ; end

#first_closedObject

Returns a new interval with one of the two sentinels opened or closed


104
# File 'lib/standard/facets/interval.rb', line 104

def first_closed ; Interval.new(@first, @last, false, true) ; end

#first_openedObject


106
# File 'lib/standard/facets/interval.rb', line 106

def first_opened ; Interval.new(@first, @last, true, false) ; end

#half_closed(e = false) ⇒ Object

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.


99
100
101
# File 'lib/standard/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) ⇒ Boolean Also known as: ===, member?

Returns true or false if the element is part of the interval.

Returns:

  • (Boolean)

150
151
152
153
154
155
# File 'lib/standard/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

#lastObject Also known as: end


64
# File 'lib/standard/facets/interval.rb', line 64

def last ; @last ; end

#last_closedObject


105
# File 'lib/standard/facets/interval.rb', line 105

def last_closed  ; Interval.new(@first, @last, true, false) ; end

#last_openedObject


107
# File 'lib/standard/facets/interval.rb', line 107

def last_opened  ; Interval.new(@first, @last, false, true) ; end

#maxObject

Returns the greater of the first and last sentinals.


145
146
147
# File 'lib/standard/facets/interval.rb', line 145

def max
  ((@first <=> @last) == 1) ? @first : @last
end

#minObject

Returns the lesser of the first and last sentinals.


140
141
142
# File 'lib/standard/facets/interval.rb', line 140

def min
  ((@first <=> @last) == -1) ? @first : @last
end

#null?Boolean

Returns true if the start and end sentinels are equal and the interval is open; otherwise false.

Returns:

  • (Boolean)

80
# File 'lib/standard/facets/interval.rb', line 80

def null? ; @direction == 0 and @exclusive_first and @exclusive_last ; end

#openedObject

Returns a new interval exclusive of both sentinels.


94
# File 'lib/standard/facets/interval.rb', line 94

def opened; Interval.new(@first, @last, true, true) ; end

#reversedObject

Returns a new interval with the sentinels reversed.

(0..10).reversed  #=> 10..0

119
120
121
# File 'lib/standard/facets/interval.rb', line 119

def reversed
  Interval.new(@last, @first, true, true)
end

#sentinelsObject

Returns a two element array of first and last sentinels.

(0..10).sentinels   #=> [0,10]

58
59
60
# File 'lib/standard/facets/interval.rb', line 58

def sentinels
  return [@first, @last]
end

#step(n = 1, d = nil) ⇒ Object

:yield:


230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
# File 'lib/standard/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

#[email protected]Object


113
# File 'lib/standard/facets/interval.rb', line 113

def [email protected] ; Interval.new(first, last, true, true) ; end