module CoreExtensions::Range::Operations::ClassMethods
Public Instance Methods
Merge the given ranges together.
@param [Array<Range>] ranges The ranges objects to merge.
@return [Array<Range>] An array of potentially merged ranges.
@example Merge two ranges.
Range.merge(1..5, 2..6) #=> [1..6]
@example Merge an endless range with a non-endless range.
Range.merge(1..5, 2..) #=> [1..]
@example Merge two endless ranges.
Range.merge(1.., 2..) #=> [1..]
@example Can’t merge an open range with a range whose start is the same as the open range’s end.
Range.merge(1...3, 3..5) #=> [1...3, 3..5]
# File lib/core_extensions/range/operations.rb, line 74 def merge(*ranges) ranges.sort_by!(&:begin).inject([]) do |merged, range| if merged.empty? || !range.overlaps?(merged.last) merged.append(range) else last_range = merged.pop merged.append(merge_ranges(last_range, range)) end end end
Subtract an array of ranges from the given original range.
@param [Range] original_range The range to subtract from.
@param [Array<Range>] subtracted_ranges The ranges to subtract from ‘original_range`
@param delta The amount which should be added to the begin of a resulting subrange in
the event that the previous subrange end was open.
@example Subtraction of closed, finite ranges.
Range.subtract(1..20, 2..3, 4..6, 7..12) #=> [1...2, 13..20]
@example Subtraction of a mixture of closed and open finite ranges.
Range.subtract(1..20, 2..3, 4...6, 7..12) #=> [1...2, 6...7, 13..20]
@example Subtraction of finite ranges from an endless range
Range.subtract(1.., 2..3, 4...6, 7..12) #=> [1...2, 4...7, 13..]
@example Subtraction of two endless ranges
Range.subtract(1.., 10..) #=> [1...10] Range.subtract(10.., 1..) #=> []
# File lib/core_extensions/range/operations.rb, line 107 def subtract(original_range, *subtracted_ranges, delta: 1) subtracted_ranges.sort_by!(&:begin).inject([original_range]) do |result, subtracted_range| if result.last&.overlaps?(subtracted_range) result.append(*subtract_overlapping_range(result.pop, subtracted_range, delta: delta)) else result end end end
Private Instance Methods
# File lib/core_extensions/range/operations.rb, line 119 def merge_ranges(a, b) exclude_end = should_merged_exclude_end?(a, b) ::Range.new(a.begin, merged_range_end(a, b), exclude_end) end
# File lib/core_extensions/range/operations.rb, line 146 def merged_range_end(a, b) if a.endless? || b.endless? nil else a.end > b.end ? a.end : b.end end end
# File lib/core_extensions/range/operations.rb, line 124 def should_merged_exclude_end?(a, b) return true if a.exclude_end? && b.exclude_end? # Deal with endless ranges if a.endless? || b.endless? # If `a.end` is `nil` and `a.exclude_end?` is `true`, then `b.exclude_end?` can't # be `true`, so it suffices to check if `b.end` is not `nil`. If `b.end` is not `nil`, # then the result is an endless range that excludes the end. If `b.end` *is* `nil`, # then the result is an endless range that does *not* exclude the end (because # we know from above that `b.exclude_end?` cannot be `true`. # # The same holds for the inverse statement (i.e. the RHS of the `||` below). # # The goal is to maintain logical consistency for for `exclude_end` even in # the event that the result is an endless range. (a.exclude_end? && a.endless? && !b.endless?) || (b.exclude_end? && b.endless? && !a.endless?) else (a.exclude_end? && b.end < a.end) || (b.exclude_end? && a.end < b.end) end end
Subtract range ‘b` from range `a` and return the resulting range(s)
# File lib/core_extensions/range/operations.rb, line 155 def subtract_overlapping_range(a, b, delta: 1) return [] if a == b result = [] result << (a.begin...b.begin) if a.begin < b.begin # If `b` is endless then the entire rest of `a` will be gone. return result if b.endless? other_begin = if b.exclude_end? b.end else delta.is_a?(Proc) ? delta.call(b.end) : b.end + delta end result << ::Range.new(other_begin, a.end, a.exclude_end?) if a.endless? || other_begin < a.end result end