Class: Philiprehberger::Interval::Range

Inherits:
Object
  • Object
show all
Includes:
Comparable
Defined in:
lib/philiprehberger/interval/range.rb

Overview

Represents an interval over any Comparable type. Supports closed [a, b], open (a, b), left-open (a, b], and right-open [a, b) boundaries.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(start, finish, type: :closed) ⇒ Range

Create a new interval.

Parameters:

  • start (Comparable)

    the start value

  • finish (Comparable)

    the end value

  • type (Symbol) (defaults to: :closed)

    boundary type (:closed, :open, :left_open, :right_open)

Raises:

  • (Error)

    if start > finish

  • (Error)

    if type is invalid



28
29
30
31
32
33
34
35
# File 'lib/philiprehberger/interval/range.rb', line 28

def initialize(start, finish, type: :closed)
  raise Error, 'start must be <= finish' if start > finish
  raise Error, "invalid interval type: #{type}" unless VALID_TYPES.include?(type)

  @start = start
  @finish = finish
  @type = type
end

Instance Attribute Details

#finishComparable (readonly)

Returns the end of the interval.

Returns:

  • (Comparable)

    the end of the interval



16
17
18
# File 'lib/philiprehberger/interval/range.rb', line 16

def finish
  @finish
end

#startComparable (readonly)

Returns the start of the interval.

Returns:

  • (Comparable)

    the start of the interval



13
14
15
# File 'lib/philiprehberger/interval/range.rb', line 13

def start
  @start
end

#typeSymbol (readonly)

Returns the interval type (:closed, :open, :left_open, :right_open).

Returns:

  • (Symbol)

    the interval type (:closed, :open, :left_open, :right_open)



19
20
21
# File 'lib/philiprehberger/interval/range.rb', line 19

def type
  @type
end

Instance Method Details

#<=>(other) ⇒ Integer

Compare intervals by start then finish.

Parameters:

Returns:

  • (Integer)


187
188
189
190
# File 'lib/philiprehberger/interval/range.rb', line 187

def <=>(other)
  result = @start <=> other.start
  result.zero? ? @finish <=> other.finish : result
end

#==(other) ⇒ Boolean

Returns:

  • (Boolean)


193
194
195
# File 'lib/philiprehberger/interval/range.rb', line 193

def ==(other)
  other.is_a?(self.class) && @start == other.start && @finish == other.finish && @type == other.type
end

#adjacent?(other) ⇒ Boolean

Check if this interval is adjacent to another (touching but not overlapping).

Parameters:

  • other (Range)

    the other interval

Returns:

  • (Boolean)


71
72
73
# File 'lib/philiprehberger/interval/range.rb', line 71

def adjacent?(other)
  @finish == other.start || other.finish == @start
end

#clamp(value) ⇒ Comparable

Clamp a value to the interval bounds.

Parameters:

  • value (Comparable)

    the value to clamp

Returns:

  • (Comparable)

    the clamped value



176
177
178
179
180
181
# File 'lib/philiprehberger/interval/range.rb', line 176

def clamp(value)
  return @start if value < @start
  return @finish if value > @finish

  value
end

#contains?(other) ⇒ Boolean

Check if this interval fully contains another.

Parameters:

  • other (Range)

    the other interval

Returns:

  • (Boolean)


63
64
65
# File 'lib/philiprehberger/interval/range.rb', line 63

def contains?(other)
  @start <= other.start && @finish >= other.finish
end

#include?(point) ⇒ Boolean

Check if a point is within the interval.

Parameters:

  • point (Comparable)

    the point to check

Returns:

  • (Boolean)


120
121
122
123
124
# File 'lib/philiprehberger/interval/range.rb', line 120

def include?(point)
  left = left_closed? ? point >= @start : point > @start
  right = right_closed? ? point <= @finish : point < @finish
  left && right
end

#inspectString

Returns:

  • (String)


205
206
207
# File 'lib/philiprehberger/interval/range.rb', line 205

def inspect
  "#<#{self.class} #{self}>"
end

#intersect(other) ⇒ Range?

Return the intersection of two overlapping intervals.

Parameters:

  • other (Range)

    the other interval

Returns:

  • (Range, nil)

    the intersection, or nil if no overlap



79
80
81
82
83
# File 'lib/philiprehberger/interval/range.rb', line 79

def intersect(other)
  return nil unless overlaps?(other)

  self.class.new([@start, other.start].max, [@finish, other.finish].min)
end

#left_closed?Boolean

Returns true if the left endpoint is included.

Returns:

  • (Boolean)

    true if the left endpoint is included



210
211
212
# File 'lib/philiprehberger/interval/range.rb', line 210

def left_closed?
  @type == :closed || @type == :right_open
end

#overlaps?(other) ⇒ Boolean

Check if this interval overlaps with another.

Parameters:

  • other (Range)

    the other interval

Returns:

  • (Boolean)


41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/philiprehberger/interval/range.rb', line 41

def overlaps?(other)
  return false if empty? || other.empty?

  left_ok = if right_closed? && other.left_closed?
              @finish >= other.start
            else
              @finish > other.start
            end

  right_ok = if other.right_closed? && left_closed?
               other.finish >= @start
             else
               other.finish > @start
             end

  left_ok && right_ok
end

#right_closed?Boolean

Returns true if the right endpoint is included.

Returns:

  • (Boolean)

    true if the right endpoint is included



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

def right_closed?
  @type == :closed || @type == :left_open
end

#scale(factor, anchor: :center) ⇒ Range

Scale interval width around an anchor point, preserving type.

Parameters:

  • factor (Numeric)

    the scale factor

  • anchor (Symbol) (defaults to: :center)

    the anchor point (:center, :left, :right)

Returns:

  • (Range)

    a new scaled interval



139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/philiprehberger/interval/range.rb', line 139

def scale(factor, anchor: :center)
  current_size = size
  new_size = current_size * factor

  case anchor
  when :left
    self.class.new(@start, @start + new_size, type: @type)
  when :right
    self.class.new(@finish - new_size, @finish, type: @type)
  when :center
    center = @start + (current_size / 2.0)
    half = new_size / 2.0
    self.class.new(center - half, center + half, type: @type)
  else
    raise Error, "invalid anchor: #{anchor}"
  end
end

#shift(delta) ⇒ Range

Return a new interval shifted by delta, preserving type.

Parameters:

  • delta (Numeric)

    the amount to shift

Returns:

  • (Range)

    a new shifted interval



130
131
132
# File 'lib/philiprehberger/interval/range.rb', line 130

def shift(delta)
  self.class.new(@start + delta, @finish + delta, type: @type)
end

#sizeNumeric

Return the size (length) of the interval.

Returns:

  • (Numeric)

    the difference between finish and start



112
113
114
# File 'lib/philiprehberger/interval/range.rb', line 112

def size
  @finish - @start
end

#split(n) ⇒ Array<Range>

Split interval into n equal sub-intervals, preserving type.

Parameters:

  • n (Integer)

    number of sub-intervals

Returns:

  • (Array<Range>)

    array of n equal sub-intervals

Raises:



161
162
163
164
165
166
167
168
169
170
# File 'lib/philiprehberger/interval/range.rb', line 161

def split(n)
  raise Error, 'n must be positive' if n < 1

  step = size.to_f / n
  Array.new(n) do |i|
    sub_start = @start + (step * i)
    sub_finish = @start + (step * (i + 1))
    self.class.new(sub_start, sub_finish, type: @type)
  end
end

#subtract(other) ⇒ Array<Range>

Subtract another interval from this one.

Parameters:

  • other (Range)

    the interval to subtract

Returns:

  • (Array<Range>)

    zero, one, or two remaining intervals



99
100
101
102
103
104
105
106
107
# File 'lib/philiprehberger/interval/range.rb', line 99

def subtract(other)
  return [self.class.new(@start, @finish)] unless overlaps?(other)
  return [] if other.contains?(self)

  result = []
  result << self.class.new(@start, other.start) if @start < other.start
  result << self.class.new(other.finish, @finish) if other.finish < @finish
  result
end

#to_sString

Returns:

  • (String)


198
199
200
201
202
# File 'lib/philiprehberger/interval/range.rb', line 198

def to_s
  left_bracket = left_closed? ? '[' : '('
  right_bracket = right_closed? ? ']' : ')'
  "#{left_bracket}#{@start}, #{@finish}#{right_bracket}"
end

#union(other) ⇒ Range?

Return the union of two overlapping or adjacent intervals.

Parameters:

  • other (Range)

    the other interval

Returns:

  • (Range, nil)

    the union, or nil if not overlapping/adjacent



89
90
91
92
93
# File 'lib/philiprehberger/interval/range.rb', line 89

def union(other)
  return nil unless overlaps?(other) || adjacent?(other)

  self.class.new([@start, other.start].min, [@finish, other.finish].max)
end