Class: Philiprehberger::Interval::Range
- Inherits:
-
Object
- Object
- Philiprehberger::Interval::Range
- 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
-
#finish ⇒ Comparable
readonly
The end of the interval.
-
#start ⇒ Comparable
readonly
The start of the interval.
-
#type ⇒ Symbol
readonly
The interval type (:closed, :open, :left_open, :right_open).
Instance Method Summary collapse
-
#<=>(other) ⇒ Integer
Compare intervals by start then finish.
- #==(other) ⇒ Boolean
-
#adjacent?(other) ⇒ Boolean
Check if this interval is adjacent to another (touching but not overlapping).
-
#clamp(value) ⇒ Comparable
Clamp a value to the interval bounds.
-
#contains?(other) ⇒ Boolean
Check if this interval fully contains another.
-
#include?(point) ⇒ Boolean
Check if a point is within the interval.
-
#initialize(start, finish, type: :closed) ⇒ Range
constructor
Create a new interval.
- #inspect ⇒ String
-
#intersect(other) ⇒ Range?
Return the intersection of two overlapping intervals.
-
#left_closed? ⇒ Boolean
True if the left endpoint is included.
-
#overlaps?(other) ⇒ Boolean
Check if this interval overlaps with another.
-
#right_closed? ⇒ Boolean
True if the right endpoint is included.
-
#scale(factor, anchor: :center) ⇒ Range
Scale interval width around an anchor point, preserving type.
-
#shift(delta) ⇒ Range
Return a new interval shifted by delta, preserving type.
-
#size ⇒ Numeric
Return the size (length) of the interval.
-
#split(n) ⇒ Array<Range>
Split interval into n equal sub-intervals, preserving type.
-
#subtract(other) ⇒ Array<Range>
Subtract another interval from this one.
- #to_s ⇒ String
-
#union(other) ⇒ Range?
Return the union of two overlapping or adjacent intervals.
Constructor Details
#initialize(start, finish, type: :closed) ⇒ Range
Create a new interval.
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
#finish ⇒ Comparable (readonly)
Returns the end of the interval.
16 17 18 |
# File 'lib/philiprehberger/interval/range.rb', line 16 def finish @finish end |
#start ⇒ Comparable (readonly)
Returns the start of the interval.
13 14 15 |
# File 'lib/philiprehberger/interval/range.rb', line 13 def start @start end |
#type ⇒ Symbol (readonly)
Returns 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.
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
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).
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.
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.
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.
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 |
#inspect ⇒ 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.
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.
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.
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.
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.
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.
130 131 132 |
# File 'lib/philiprehberger/interval/range.rb', line 130 def shift(delta) self.class.new(@start + delta, @finish + delta, type: @type) end |
#size ⇒ Numeric
Return the size (length) of the interval.
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.
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.
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_s ⇒ 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.
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 |