Module: FatCore::Range

Includes:
Comparable
Included in:
Range
Defined in:
lib/fat_core/range.rb

Defined Under Namespace

Modules: ClassMethods

Operations collapse

Queries collapse

Sorting collapse

Instance Method Details

#<=>(other) ⇒ -1, ...

Compare this range with other first by min values, then by max values.

This causes a sort of Ranges with Comparable elements to sort from left to right on the number line, then for Ranges that start on the same number, from smallest to largest.

Examples:

(4..8) <=> (5..7) #=> -1
(4..8) <=> (4..7) #=> 1
(4..8) <=> (4..8) #=> 0

Parameters:

  • other (Range)

    range to compare self with

Returns:

  • (-1, 0, 1)

    if self is less, equal, or greater than other

[View source]

367
368
369
# File 'lib/fat_core/range.rb', line 367

def <=>(other)
  [min, max] <=> [other.min, other.max]
end

#contiguous?(other) ⇒ Boolean

Is self contiguous to other either on the left or on the right? First, the two ranges are sorted by their min values, and the range with the lowest min value is considered to be on the "left" and the other range on the "right". Whether one range is "contiguous" to another then has two cases:

  1. If the max element of the Range on the left respond to the #succ method (that is, its value is a discrete value such as Integer or Date) test whether the succ to the max value of the Range on the left is equal to the min value of the Range on the right.
  2. If the max element of the Range on the left does not respond to the #succ method (that is, its values are continuous values such as Floats) test whether the max value of the Range on the left is equal to the min value of the Range on the right

Examples:

(0..10).contiguous?((11..20))           #=> true
(11..20).contiguous?((0..10))           #=> true, right_contiguous
(0..10).contiguous?((15..20))           #=> false
(3.145..12.3).contiguous?((0.5..3.145)) #=> true
(3.146..12.3).contiguous?((0.5..3.145)) #=> false

Parameters:

  • other (Range)

    other range to test for contiguity

Returns:

  • (Boolean)

    is self contiguous with other

[View source]

265
266
267
# File 'lib/fat_core/range.rb', line 265

def contiguous?(other)
  left_contiguous?(other) || right_contiguous?(other)
end

#difference(other) ⇒ Object Also known as: -

The difference method, -, removes the overlapping part of the other argument from self. Because in the case where self is a superset of the other range, this will result in the difference being two non-contiguous ranges, this returns an array of ranges. If there is no overlap or if self is a subset of the other range, return an array of self

[View source]

154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/fat_core/range.rb', line 154

def difference(other)
  unless max.respond_to?(:succ) && min.respond_to?(:pred) &&
         other.max.respond_to?(:succ) && other.min.respond_to?(:pred)
    raise 'Range difference requires objects have pred and succ methods'
  end
  if subset_of?(other)
    # (4..7) - (0..10)
    []
  elsif proper_superset_of?(other)
    # (4..7) - (5..5) -> [(4..4), (6..7)]
    [(min..other.min.pred), (other.max.succ..max)]
  elsif overlaps?(other) && other.min <= min
    # (4..7) - (2..5) -> (6..7)
    [(other.max.succ..max)]
  elsif overlaps?(other) && other.max >= max
    # (4..7) - (6..10) -> (4..5)
    [(min..other.min.pred)]
  else
    [self]
  end
end

#gaps(ranges) ⇒ Array<Range>

If this range is not spanned by the ranges collectively, return an Array of ranges representing the gaps in coverage. The ranges can over-cover this range on the left or right without affecting the result, that is, each range in the returned array of gap ranges will always be subsets of this range.

If the ranges span this range, return an empty array.

Examples:

(0..10).gaps([(0..3), (5..6), (9..10)])  #=> [(4..4), (7..8)]
(0..10).gaps([(-4..3), (5..6), (9..15)]) #=> [(4..4), (7..8)]
(0..10).gaps([(-4..3), (4..6), (7..15)]) #=> [] ranges span this one
(0..10).gaps([(-4..-3), (11..16), (17..25)]) #=> [(0..10)] no overlap
(0..10).gaps([])                             #=> [(0..10)] no overlap

Parameters:

Returns:

[View source]

53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/fat_core/range.rb', line 53

def gaps(ranges)
  if ranges.empty?
    [clone]
  elsif spanned_by?(ranges)
    []
  else
    # TODO: does not work unless min and max respond to :succ
    ranges = ranges.sort_by(&:min)
    gaps = []
    cur_point = min
    ranges.each do |rr|
      break if rr.min > max
      if rr.min > cur_point
        start_point = cur_point
        end_point = rr.min.pred
        gaps << (start_point..end_point)
        cur_point = rr.max.succ
      elsif rr.max >= cur_point
        cur_point = rr.max.succ
      end
    end
    gaps << (cur_point..max) if cur_point <= max
    gaps
  end
end

#intersection(other) ⇒ Range? Also known as: &

Return a Range that represents the intersection between this range and the other range. If there is no intersection, return nil.

Examples:

(0..10) & (5..20)             #=> (5..10)
(0..10).intersection((5..20)) #=> (5..10)
(0..10) & (15..20)            #=> nil

Parameters:

  • other (Range)

    the Range self is intersected with

Returns:

  • (Range, nil)

    a Range representing the intersection

[View source]

126
127
128
129
# File 'lib/fat_core/range.rb', line 126

def intersection(other)
  return nil unless overlaps?(other)
  ([min, other.min].max..[max, other.max].min)
end

#join(other) ⇒ Range?

Return a range that concatenates this range with other if it is contiguous with this range on the left or right; return nil if the ranges are not contiguous.

Examples:

(0..3).join(4..8) #=> (0..8)

Parameters:

  • other (Range)

    the Range to join to this range

Returns:

  • (Range, nil)

    this range joined to other

See Also:

[View source]

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

def join(other)
  if left_contiguous?(other)
    ::Range.new(min, other.max)
  elsif right_contiguous?(other)
    ::Range.new(other.min, max)
  end
end

#left_contiguous?(other) ⇒ Boolean

Is self on the left of and contiguous to other? Whether one range is "contiguous" to another has two cases:

  1. If the elements of the Range on the left respond to the #succ method (that is, its values are discrete values such as Integers or Dates) test whether the succ to the max value of the Range on the left is equal to the min value of the Range on the right.
  2. If the elements of the Range on the left do not respond to the #succ method (that is, its values are continuous values such as Floats) test whether the max value of the Range on the left is equal to the min value of the Range on the right

Examples:

(0..10).left_contiguous((11..20))           #=> true
(11..20).left_contiguous((0..10))           #=> false, but right_contiguous
(0.5..3.145).left_contiguous((3.145..18.4)) #=> true
(0.5..3.145).left_contiguous((3.146..18.4)) #=> false

Parameters:

  • other (Range)

    other range to test for contiguity

Returns:

  • (Boolean)

    is self left_contiguous with other

[View source]

206
207
208
209
210
211
212
# File 'lib/fat_core/range.rb', line 206

def left_contiguous?(other)
  if max.respond_to?(:succ)
    max.succ == other.min
  else
    max == other.min
  end
end

#overlaps(ranges) ⇒ Array<Range>

Within this range return an Array of Ranges representing the overlaps among the given Array of Ranges ranges. If there are no overlaps, return an empty array. Don't consider overlaps in the ranges that occur outside of self.

Examples:

(0..10).overlaps([(-4..4), (2..7), (5..12)]) => [(2..4), (5..7)]

Parameters:

  • ranges (Array<Range>)

    ranges to search for overlaps

Returns:

  • (Array<Range>)

    overlaps with ranges but inside this Range

[View source]

89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/fat_core/range.rb', line 89

def overlaps(ranges)
  if ranges.empty? || spanned_by?(ranges)
    []
  else
    ranges = ranges.sort_by(&:min)
    overlaps = []
    cur_point = nil
    ranges.each do |rr|
      # Skip ranges outside of self
      next if rr.max < min || rr.min > max
      # Initialize cur_point to max of first range
      if cur_point.nil?
        cur_point = rr.max
        next
      end
      # We are on the second or later range
      if rr.min < cur_point
        start_point = rr.min
        end_point = cur_point
        overlaps << (start_point..end_point)
      end
      cur_point = rr.max
    end
    overlaps
  end
end

#overlaps?(other) ⇒ Boolean

Return whether self overlaps with other Range.

Parameters:

  • other (Range)

    range to test for overlap with self

Returns:

  • (Boolean)

    is there an overlap?

[View source]

309
310
311
312
# File 'lib/fat_core/range.rb', line 309

def overlaps?(other)
  (cover?(other.min) || cover?(other.max) ||
   other.cover?(min) || other.cover?(max))
end

#overlaps_among?(ranges) ⇒ Boolean

Return whether any of the ranges that overlap self have overlaps among one another.

This does the same thing as Range.overlaps_among?, except that it filters the ranges to only those overlapping self before testing for overlaps among them.

Parameters:

  • ranges (Array<Range>)

    ranges to test for overlaps

Returns:

  • (Boolean)

    were there overlaps among ranges?

[View source]

323
324
325
326
# File 'lib/fat_core/range.rb', line 323

def overlaps_among?(ranges)
  iranges = ranges.select { |r| overlaps?(r) }
  ::Range.overlaps_among?(iranges)
end

#proper_subset_of?(other) ⇒ Boolean

Return whether self is contained within other range, without their boundaries touching.

Parameters:

  • other (Range)

    the containing range

Returns:

  • (Boolean)

    is self wholly within other

[View source]

283
284
285
# File 'lib/fat_core/range.rb', line 283

def proper_subset_of?(other)
  min > other.min && max < other.max
end

#proper_superset_of?(other) ⇒ Boolean

Return whether self contains other range, without their boundaries touching.

Parameters:

  • other (Range)

    the contained range

Returns:

  • (Boolean)

    does self wholly contain other

[View source]

301
302
303
# File 'lib/fat_core/range.rb', line 301

def proper_superset_of?(other)
  min < other.min && max > other.max
end

#right_contiguous?(other) ⇒ Boolean

Is self on the right of and contiguous to other? Whether one range is "contiguous" to another has two cases:

  1. If the elements of the Range on the left respond to the #succ method (that is, its values are discrete values such as Integers or Dates) test whether the succ to the max value of the Range on the left is equal to the min value of the Range on the right.
  2. If the elements of the Range on the left do not respond to the #succ method (that is, its values are continuous values such as Floats) test whether the max value of the Range on the left is equal to the min value of the Range on the right

Examples:

(11..20).right_contiguous((0..10))           #=> true
(0..10).right_contiguous((11..20))           #=> false, but left_contiguous
(3.145..12.3).right_contiguous((0.5..3.145)) #=> true
(3.146..12.3).right_contiguous((0.5..3.145)) #=> false

Parameters:

  • other (Range)

    other range to test for contiguity

Returns:

  • (Boolean)

    is self right_contiguous with other

[View source]

234
235
236
237
238
239
240
# File 'lib/fat_core/range.rb', line 234

def right_contiguous?(other)
  if other.max.respond_to?(:succ)
    other.max.succ == min
  else
    other.max == min
  end
end

#spanned_by?(ranges) ⇒ Boolean

Return true if the given ranges collectively cover this range without overlaps and without gaps.

Parameters:

Returns:

  • (Boolean)
[View source]

333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
# File 'lib/fat_core/range.rb', line 333

def spanned_by?(ranges)
  joined_range = nil
  ranges.sort.each do |r|
    unless joined_range
      joined_range = r
      next
    end
    joined_range = joined_range.join(r)
    break if joined_range.nil?
  end
  if !joined_range.nil?
    joined_range.min <= min && joined_range.max >= max
  else
    false
  end
end

#subset_of?(other) ⇒ Boolean

Return whether self is contained within other range, even if their boundaries touch.

Parameters:

  • other (Range)

    the containing range

Returns:

  • (Boolean)

    is self within other

[View source]

274
275
276
# File 'lib/fat_core/range.rb', line 274

def subset_of?(other)
  min >= other.min && max <= other.max
end

#superset_of?(other) ⇒ Boolean

Return whether self contains other range, even if their boundaries touch.

Parameters:

  • other (Range)

    the contained range

Returns:

  • (Boolean)

    does self contain other

[View source]

292
293
294
# File 'lib/fat_core/range.rb', line 292

def superset_of?(other)
  min <= other.min && max >= other.max
end

#tex_quoteString

Allow erb or erubis documents to directly interpolate a Range.

Returns:

[View source]

180
181
182
# File 'lib/fat_core/range.rb', line 180

def tex_quote
  to_s
end

#union(other) ⇒ Range? Also known as: +

Return a Range that represents the union between this range and the other range. If there is no overlap and self is not contiguous with other, return nil.

Examples:

(0..10) + (5..20)       #=> (0..20)
(0..10).union((5..20))  #=> (0..20)
(0..10) + (15..20)      #=> nil

Parameters:

  • other (Range)

    the Range self is union-ed with

Returns:

  • (Range, nil)

    a Range representing the union

[View source]

143
144
145
146
# File 'lib/fat_core/range.rb', line 143

def union(other)
  return nil unless overlaps?(other) || contiguous?(other)
  ([min, other.min].min..[max, other.max].max)
end