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.
-
#overlap_ratio(other) ⇒ Float
Return the fraction of self that is covered by another interval.
-
#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.
204 205 206 207 |
# File 'lib/philiprehberger/interval/range.rb', line 204 def <=>(other) result = @start <=> other.start result.zero? ? @finish <=> other.finish : result end |
#==(other) ⇒ Boolean
210 211 212 |
# File 'lib/philiprehberger/interval/range.rb', line 210 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.
193 194 195 196 197 198 |
# File 'lib/philiprehberger/interval/range.rb', line 193 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.
137 138 139 140 141 |
# File 'lib/philiprehberger/interval/range.rb', line 137 def include?(point) left = left_closed? ? point >= @start : point > @start right = right_closed? ? point <= @finish : point < @finish left && right end |
#inspect ⇒ String
222 223 224 |
# File 'lib/philiprehberger/interval/range.rb', line 222 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.
227 228 229 |
# File 'lib/philiprehberger/interval/range.rb', line 227 def left_closed? @type == :closed || @type == :right_open end |
#overlap_ratio(other) ⇒ Float
Return the fraction of self that is covered by another interval.
Returns a Float in 0.0..1.0. Returns 0.0 if disjoint, 1.0 if self is fully contained in other. If self has zero length (a point), returns 1.0 when the point is within other, else 0.0.
124 125 126 127 128 129 130 131 |
# File 'lib/philiprehberger/interval/range.rb', line 124 def overlap_ratio(other) return other.include?(@start) ? 1.0 : 0.0 if size.zero? overlap = intersect(other) return 0.0 if overlap.nil? overlap.size.to_f / size 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.
232 233 234 |
# File 'lib/philiprehberger/interval/range.rb', line 232 def right_closed? @type == :closed || @type == :left_open end |
#scale(factor, anchor: :center) ⇒ Range
Scale interval width around an anchor point, preserving type.
156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 |
# File 'lib/philiprehberger/interval/range.rb', line 156 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.
147 148 149 |
# File 'lib/philiprehberger/interval/range.rb', line 147 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.
178 179 180 181 182 183 184 185 186 187 |
# File 'lib/philiprehberger/interval/range.rb', line 178 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
215 216 217 218 219 |
# File 'lib/philiprehberger/interval/range.rb', line 215 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 |