require_relative 'interval_set/version' require 'treemap-fork' # IntervalSet implements a set of sorted non-overlapping ranges. # A range's start is always interpreted as inclusive while the end is exclusive class IntervalSet include Enumerable # Builds a new IntervalSet from the supplied ranges. Overlapping ranges will be merged. # IntervalSet[] # -> [] # IntervalSet[0...1] # -> [0...1] # IntervalSet[0...1, 2...3] # -> [0...1, 2...3] # IntervalSet[0...1, 1...2] # -> [0...2] # # array = [0...1, 2...3] # IntervalSet[*array] # -> [0...1, 2...3] # # @param ranges [Range[]] a list of ranges to be added to the new Rangeset # @return [IntervalSet] a new RangeSet containing the supplied ranges. def self.[](*ranges) IntervalSet.new.tap do |interval_set| ranges.each {|range| interval_set << range} end end # Returns an empty instance of IntervalSet. # @param range_map [TreeMap] a TreeMap of ranges. For internal use only. def initialize(range_map = TreeMap.new) unless range_map.instance_of?(TreeMap) || range_map.instance_of?(TreeMap::BoundedMap) raise ArgumentError.new("invalid range_map #{range_map}") end @range_map = range_map update_bounds end # Returns +true+ if this IntervalSet contains no ranges. def empty? @range_map.empty? end # Returns the lower bound of this IntervalSet. # # IntervalSet[0...1, 2...3].min # -> 0 # IntervalSet[].min # -> nil # # @return the lower bound or +nil+ if empty. def min @min end # Returns the upper bound of this IntervalSet. # # IntervalSet[0...1, 2...3].max # -> 3 # IntervalSet[].max # -> nil # # @return the upper bound or +nil+ if empty. def max @max end # Returns the bounds of this IntervalSet. # # IntervalSet[0...1, 2...3].bounds # -> 0...3 # IntervalSet[].bounds # -> nil # # @return [Range] a range from lower to upper bound or +nil+ if empty. def bounds empty? ? nil : min...max end # Returns +true+ if two RangeSets are equal. # # IntervalSet[0...1] == IntervalSet[0...1] # -> true # IntervalSet[0...1] == IntervalSet[1...2] # -> false # # @param other [IntervalSet] the other RangeSet. def eql?(other) return false if count != other.count return false if bounds != other.bounds lhs_iter = enum_for rhs_iter = other.enum_for count.times.all? {lhs_iter.next == rhs_iter.next} end alias_method :==, :eql? # Returns +true+ if the other object represents a equal # set of ranges as this IntervalSet. # # IntervalSet[1...2].eql_set?(1...2) # -> true # IntervalSet[1...2].eql_set?(IntervalSet[1...2]) # -> true # # @param other [Range | IntervalSet] the other object. def eql_set?(other) case other when Range eql_range?(other) when IntervalSet eql?(other) else IntervalSet.unexpected_object(other) end end # Returns +true+ if this IntervalSet contains the given element. # # i = IntervalSet[0...1] # -> [0...1] # # i.include?(0) # -> true # i.include?(0.5) # -> true # i.include?(1) # -> false ; a range's end is exclusive # # @param element [Object] def include?(element) floor_entry = @range_map.floor_entry(element) !floor_entry.nil? && floor_entry.value.last > element end alias_method :===, :include? # Returns +true+ if this IntervalSet includes all elements # of the other object. # # IntervalSet[0...1] >= IntervalSet[0...1] # -> true # IntervalSet[0...2] >= IntervalSet[0...1] # -> true # IntervalSet[0...1] >= IntervalSet[0...1, 2...3] # -> false # IntervalSet[0...3] >= IntervalSet[0...1, 2...3] # -> true # # # You can also supply ranges # IntervalSet[0...2].superset?(0...1) # -> true # # @param other [Range | IntervalSet] the other object. def superset?(other) case other when Range superset_range?(other) when IntervalSet superset_interval_set?(other) else IntervalSet.unexpected_object(other) end end alias_method :>=, :superset? # Returns +true+ if all elements of this IntervalSet are # included by the other object. # # IntervalSet[0...1] <= IntervalSet[0...1] # -> true # IntervalSet[0...1] <= IntervalSet[0...1, 2...3] # -> true # IntervalSet[0...1, 2...3] <= IntervalSet[0...1] # -> false # IntervalSet[0...1, 2...3] <= IntervalSet[0...3] # -> true # # # You can also supply ranges # IntervalSet[0...1, 2...3].subset?(0...3) # -> true # # @param other [Range | IntervalSet] the other object. def subset?(other) case other when Range subset_range?(other) when IntervalSet other.superset_interval_set?(self) else IntervalSet.unexpected_object(other) end end alias_method :<=, :subset? # Returns +true+ if this IntervalSet is a proper superset of the other. # # IntervalSet[0...2] > IntervalSet[0...1] # -> true # IntervalSet[0...2] > IntervalSet[0...2] # -> false # IntervalSet[0...2] > IntervalSet[1...3] # -> false # # # Compare to ranges # IntervalSet[0...3].superset?(1...2) # -> true # # @param other [Range | IntervalSet] the other object. def proper_superset?(other) !eql_set?(other) && superset?(other) end alias_method :>, :proper_superset? # Return +true+ if this IntervalSet is a proper subset of the other. # # IntervalSet[0...1] < IntervalSet[0...2] # -> true # IntervalSet[1...3] < IntervalSet[0...2] # -> false # IntervalSet[1...3] < IntervalSet[0...2] # -> false # # # Compare to ranges # IntervalSet[1...2].subset?(0...3) # -> false # # @param other [Range | IntervalSet] the other object. def proper_subset?(other) !eql_set?(other) && subset?(other) end alias_method :<, :proper_subset? # Returns +true+ if the given range has common elements with the # bounding range of this IntervalSet. # # IntervalSet[1...2].bounds_intersected_by?(2...3) # -> false # IntervalSet[1...2, 5...6].bounds_intersected_by?(3...4) # -> true # # @param range [Range] def bounds_intersected_by?(range) return false if IntervalSet.range_empty?(range) !empty? && range.first < max && range.last > min end # Returns +true+ if the given range has common elements with the # bounding range or the bounds of this IntervalSet. # # IntervalSet[1...2].bounds_intersected_or_touched_by?(2...3) # -> true # IntervalSet[1...2].bounds_intersected_or_touched_by?(3...4) # -> false # IntervalSet[1...2, 5...6].bounds_intersected_or_touched_by?(3...4) # -> true # # @param range [Range] def bounds_intersected_or_touched_by?(range) return false if IntervalSet.range_empty?(range) !empty? && range.first <= max && range.last >= min end # Returns +true+ if the given object has any common elements with # this IntervalSet. # # i = IntervalSet[0...1] # -> [0...1] # # # Ranges only need a single common element with the range set # i.intersect?(0...1) # -> true # i.intersect?(0...2) # -> true # i.intersect?(1...2) # -> false ; the start of a range is inclusive but the end exclusive # # # The same applies for range sets # i.intersect?(IntervalSet[0...1]) # -> true # i.intersect?(IntervalSet[0...1, 2...3]) # -> true # i.intersect?(IntervalSet[2...3]) # -> false # # @param other [Range | IntervalSet | #<=>] the other object. def intersect?(other) case other when Range intersect_range?(other) when IntervalSet intersect_interval_set?(other) else IntervalSet.unexpected_object(other) end end # Counts the number of ranges contained by this IntervalSet. # # i = IntervalSet[] # -> [] # i.count # -> 0 # i << (0...1) # -> [0...1] # i.count # -> 1 # i << (2...3) # -> [0...1, 2...3] # i.count # -> 2 # i << (1...2) # -> [0...3] # i.count # -> 1 # # @return [Integer] the number of ranges. def count @range_map.count end # Adds the other object's elements to this IntervalSet. # The result is stored in this IntervalSet. # # IntervalSet.new.add(0...1) # -> [0...1] # IntervalSet.new << (0...1) # -> [0...1] # # i = IntervalSet.new # -> [] # i << (0...1) # -> [0...1] # i << (2...3) # -> [0...1, 2...3] # i << (1...2) # -> [0...3] # i << (-1...4) # -> [-1...4] # # @param other [Range, IntervalSet] the other object. # @return [IntervalSet] self. def add(other) case other when Range add_range(other) when IntervalSet add_interval_set(other) else IntervalSet.unexpected_object(other) end end alias_method :<<, :add alias_method :union!, :add # Removes the other object's elements from this IntervalSet. # The result is stored in this IntervalSet. # # i = IntervalSet[0...10] # -> [0...10] # i.remove(0...2) # -> [8...10] # i >> (2...8) # -> [0...2, 8...10] # # @param other [Range, IntervalSet] the other object. # @return [IntervalSet] self. def remove(other) case other when Range remove_range(other) when IntervalSet remove_interval_set(other) else IntervalSet.unexpected_object(other) end end alias_method :>>, :remove alias_method :difference!, :remove # Intersects the other object's elements with this IntervalSet. # The result is stored in this IntervalSet. # # i = IntervalSet[0...2, 3...5].intersect(1...5) # -> [1...2, 3...5] # i # -> [1...2, 3...5] # # @param other [Range, IntervalSet] the other object. # @return [IntervalSet] self. def intersect(other) case other when Range intersect_range(other) when IntervalSet intersect_interval_set(other) else IntervalSet.unexpected_object(other) end end alias_method :intersection!, :intersect # Intersects the other object's elements with this IntervalSet. # The result is stored in a new IntervalSet. # # IntervalSet[0...2, 3...5] & IntervalSet[1...4, 5...6] # -> [1...2, 3...4] # # @param other [Range, IntervalSet] the other object. # @return [IntervalSet] a new RangeSet containing the intersection. def intersection(other) case other when Range intersection_range(other) when IntervalSet intersection_interval_set(other) else IntervalSet.unexpected_object(other) end end alias_method :&, :intersection # Joins the other object's elements with this IntervalSet. # The result is stored in a new IntervalSet. # # IntervalSet[0...1, 2...3] | IntervalSet[1...2, 4...5] # -> [0...3, 4...5] # # Note that using +add+ or +union!+ is more efficient than # += or |=. # # @param other [Range, IntervalSet] the other object. # @return [IntervalSet] a new RangeSet containing the union. def union(other) case other when Range union_range(other) when IntervalSet union_interval_set(other) else IntervalSet.unexpected_object(other) end end alias_method :|, :union alias_method :+, :union # Subtracts the other object's elements from this IntervalSet. # The result is stored in a new IntervalSet. # # IntervalSet[0...2, 3...5] - IntervalSet[1...4, 5...6] # -> [0...1, 4...5] # # Note that using +remove+ or +difference!+ is more efficient # than -=. # # @param other [Range, IntervalSet] the other object. # @return [IntervalSet] a new RangeSet containing the difference. def difference(other) case other when Range difference_range(other) when IntervalSet difference_interval_set(other) else IntervalSet.unexpected_object(other) end end alias_method :-, :difference # Calculates a new IntervalSet which only contains elements exclusively from # either this or the given object. # # This operation is equivalent to (self | other) - (self & other) # # IntervalSet[0...1] ^ IntervalSet[1...2] # -> [0...2] # IntervalSet[0...2, 4...6] ^ IntervalSet[1...5, 7...8] # -> [0...1, 2...4, 5...6, 7...8] # IntervalSet[0...1] ^ IntervalSet[0...1] # -> [] # # @param other [Range, IntervalSet] # @return [IntervalSet] a new RangeSet containing the exclusive set. def xor(other) clone.xor!(other) end alias_method :^, :xor # Calculates the set which contains elements exclusively from # either this or the given object. The result of this operation # is stored in this set. # # The resulting set is equivalent to (self | other) - (self & other) # # IntervalSet[0...1].xor!(IntervalSet[1...2]) # -> [0...2] # IntervalSet[0...2, 4...6].xor!(IntervalSet[1...5, 7...8]) # -> [0...1, 2...4, 5...6, 7...8] # IntervalSet[0...1].xor!(IntervalSet[0...1]) # -> [] # # @param other [Range, IntervalSet] # @return [IntervalSet] a new RangeSet containing the exclusive set. def xor!(other) intersection = self & other add(other).remove(intersection) end # Convolves the other object's elements with this IntervalSet. # The result is stored in this IntervalSet. # # The result will contain all elements which can be obtained by adding # any pair of elements from both sets. A ∗ B = { a + b | a ∈ A ∧ b ∈ B } # # # Convolve with a range (effectively buffers the set) # IntervalSet[0...4].convolve!(-1...2) # -> [-1...6] # # # Convolving with empty or reversed ranges result in an empty set. # IntervalSet[0...4].convolve!(0...0) # -> [] # IntervalSet[0...4].convolve!(1...0) # -> [] # # # Convolve with a range set # IntervalSet[0...1, 10...12].convolve!(IntervalSet[-2...1, 1...2]) # -> [-2...3, 8...14] # # @param other [Range | IntervalSet] the other object. # @return [IntervalSet] self def convolve!(other) case other when Range convolve_range!(other) when IntervalSet convolve_interval_set!(other) else IntervalSet.unexpected_object(other) end end # Convolves the other object's elements with this IntervalSet. # The result is stored in a new IntervalSet. # # The result will contain all elements which can be obtained by adding # any pair of elements from both sets. A ∗ B = { a + b | a ∈ A ∧ b ∈ B } # # # Convolve with a range (effectively buffers the set) # IntervalSet[0...4] * (-1...2) # -> [-1...6] # # # Convolving with empty or reversed ranges result in an empty set. # IntervalSet[0...4] * (0...0) # -> [] # IntervalSet[0...4] * (1...0) # -> [] # # # Convolve with a range set # IntervalSet[0...1, 10...12] * IntervalSet[-2...1, 1...2] # -> [-2...3, 8...14] # # @param other [Range | IntervalSet] the other object. # @return [IntervalSet] a new RangeSet containing the convolution. def convolve(other) clone.convolve!(other) end alias_method :*, :convolve # Shifts this IntervalSet by the given amount. # The result is stored in this IntervalSet. # # IntervalSet[0...1].shift(1) # -> [1...2] # # Note that +shift(0)+ will not be optimized since IntervalSet does # not assume numbers as element type. # # @param amount [Object] # @return [IntervalSet] self. def shift!(amount) ranges = map {|range| range.first + amount...range.last + amount} clear ranges.each {|range| put(range)} update_bounds self end # Shifts this IntervalSet by the given amount. # The result is stored in a new IntervalSet. # # IntervalSet[0...1].shift!(1) # -> [1...2] # # Note that +shift!(0)+ will not be optimized since IntervalSet does # not assume numbers as element type. # # @param amount [Object] # @return [IntervalSet] a new RangeSet shifted by +amount+. def shift(amount) clone.shift!(amount) end # Buffers this IntervalSet by adding a left and right margin to each range. # The result is stored in this IntervalSet. # # IntervalSet[1...2].buffer!(1, 2) # -> [0...4] # # # negative values will shrink the ranges # IntervalSet[0...4].buffer!(-1, -2) # -> [1...2] # IntervalSet[1...2].buffer!(-0.5, -0.5) # -> [] # # @param left [Object] margin added to the left side of each range. # @param right [Object] margin added to the right side of each range. # @return [IntervalSet] self. def buffer!(left, right) ranges = map do |range| range.first - left...range.last + right end.select do |range| range.first < range.last end clear ranges.each {|r| add_range(r)} self end # Buffers this IntervalSet by adding a left and right margin to each range. # The result is stored in a new IntervalSet. # # IntervalSet[1...2].buffer(1, 2) # -> [0...4] # # # negative values will shrink the ranges # IntervalSet[0...4].buffer(-1, -2) # -> [1...2] # IntervalSet[1...2].buffer(-0.5, -0.5) # -> [] # # @param left [Object] margin added to the left side of each range. # @param right [Object] margin added to the right side of each range. # @return [IntervalSet] a new RangeSet containing the buffered ranges. def buffer(left, right) clone.buffer!(left, right) end # Removes all elements from this IntervalSet. # @return [IntervalSet] self. def clear @range_map.clear @min = nil @max = nil self end # Iterates over all ranges of this set in ascending order. # @yield all ranges. # @yieldparam [Range] range. # @return [void] def each @range_map.each_node {|node| yield node.value} end # Returns a new IntervalSet instance containing all ranges of this IntervalSet. # @return [IntervalSet] the clone. def clone IntervalSet.new.copy(self) end # Replaces the content of this IntervalSet by the content of the given IntervalSet. # @param interval_set [IntervalSet] the other RangeSet to be copied # @return [IntervalSet] self. def copy(interval_set) clear interval_set.each {|range| put(range)} @min = interval_set.min @max = interval_set.max self end # Returns a String representation of this IntervalSet. # # IntervalSet[].to_s # -> "[]" # IntervalSet[0...1].to_s # -> "[0...1]" # IntervalSet[0...1, 2...3].to_s # -> "[0...1, 2...3]" # # @return [String] the String representation. def to_s string_io = StringIO.new last_index = count - 1 string_io << '[' each_with_index do |range, i| string_io << range string_io << ', ' if i < last_index end string_io << ']' string_io.string end alias_method :inspect, :to_s protected def range_map @range_map end def put(range) @range_map.put(range.first, IntervalSet.normalize_range(range)) end def put_and_update_bounds(range) put(range) if @min.nil? && @max.nil? @min = range.first @max = range.last else @min = [range.first, @min].min @max = [range.last, @max].max end end def superset_range?(range) return true if IntervalSet.range_empty?(range) return false if empty? || !bounds_intersected_by?(range) # left.min <= range.first left_entry = @range_map.floor_entry(range.first) # left.max >= range.last !left_entry.nil? && left_entry.value.last >= range.last end def superset_interval_set?(interval_set) return true if interval_set == self || interval_set.empty? return false if empty? || !interval_set.bounds_intersected_by?(bounds) interval_set.all? {|range| superset_range?(range)} end def subset_range?(range) return true if empty? return false if IntervalSet.range_empty?(range) empty? || (range.first <= min && range.last >= max) end def intersect_range?(range) return false unless bounds_intersected_by?(range) # left.min < range.last left_entry = @range_map.lower_entry(range.last) # left.max > range.first !left_entry.nil? && left_entry.value.last > range.first end def intersect_interval_set?(interval_set) return false if empty? || !bounds_intersected_by?(interval_set.bounds) sub_set(interval_set.bounds).any? {|range| intersect_range?(range)} end def eql_range?(range) return true if empty? && IntervalSet.range_empty?(range) count == 1 && bounds == range end def sub_set(range) # left.min < range.first left_entry = @range_map.lower_entry(range.first) # left.max > range.first include_left = !left_entry.nil? && left_entry.value.last > range.first bound_min = include_left ? left_entry.value.first : range.first sub_map = @range_map.sub_map(bound_min, range.last) IntervalSet.new(sub_map) end def head_set(value) head_map = @range_map.head_map(value) IntervalSet.new(head_map) end def tail_set(value) # left.min < value left_entry = @range_map.lower_entry(value) # left.max > value include_left = !left_entry.nil? && left_entry.value.last > value bound_min = include_left ? left_entry.value.first : value tail_map = @range_map.tail_map(bound_min) IntervalSet.new(tail_map) end def add_range(range) # ignore empty or reversed ranges return self if IntervalSet.range_empty?(range) # short cut unless bounds_intersected_or_touched_by?(range) put_and_update_bounds(range) return self end # short cut if subset_range?(range) clear put_and_update_bounds(range) return self end # range.first <= core.min <= range.last core = @range_map.sub_map(range.first, true, range.last, true) # short cut if range already included if !core.empty? && core.first_entry == core.last_entry core_range = core.first_entry.value return self if core_range.first == range.first && core_range.last == range.last end # left.min < range.first left_entry = @range_map.lower_entry(range.first) # right.min <= range.last right_entry = core.empty? ? left_entry : core.last_entry # determine boundaries # left.max >= range.first include_left = !left_entry.nil? && left_entry.value.last >= range.first # right.max > range.last include_right = !right_entry.nil? && right_entry.value.last > range.last left_boundary = include_left ? left_entry.key : range.first right_boundary = include_right ? right_entry.value.last : range.last @range_map.remove(left_boundary) if include_left core.keys.each {|key| @range_map.remove(key)} # add range if !include_left && !include_right put_and_update_bounds(range) else put_and_update_bounds(left_boundary...right_boundary) end self end def add_interval_set(interval_set) return self if interval_set == self || interval_set.empty? interval_set.each {|range| add_range(range)} self end def remove_range(range) return self unless bounds_intersected_by?(range) # range.first <= core.min <= range.last core = @range_map.sub_map(range.first, true, range.last, false) # left.min < range.first left_entry = @range_map.lower_entry(range.first) # right.min < range.last right_entry = core.empty? ? left_entry : core.last_entry # left.max > range.to include_left = !left_entry.nil? && left_entry.value.last > range.first # right.max > range.last include_right = !right_entry.nil? && right_entry.value.last > range.last core.keys.each {|key| @range_map.remove(key)} # right first since right might be same as left put(range.last...right_entry.value.last) if include_right put(left_entry.key...range.first) if include_left update_bounds self end def remove_interval_set(interval_set) if interval_set == self clear else interval_set.each {|range| remove_range(range)} end self end def intersect_range(range) unless bounds_intersected_by?(range) clear return self end return self if subset_range?(range) # left_map.min < range.first left_map = @range_map.head_map(range.first, false) # right_map.min >= range.last right_map = @range_map.tail_map(range.last, true) # left.min < range.first left_entry = left_map.last_entry # right.min < range.last right_entry = @range_map.lower_entry(range.last) # left.max > range.first include_left = !left_entry.nil? && left_entry.value.last > range.first # right.max > right.max include_right = !right_entry.nil? && right_entry.value.last > range.last left_map.keys.each {|key| @range_map.remove(key)} right_map.keys.each {|key| @range_map.remove(key)} put(range.first...[left_entry.value.last, range.last].min) if include_left put([right_entry.key, range.first].max...range.last) if include_right update_bounds self end def intersect_interval_set(interval_set) return self if interval_set == self if interval_set.empty? || !bounds_intersected_by?(interval_set.bounds) clear return self end intersection = interval_set.sub_set(bounds).map do |range| IntervalSet.new.tap do |interval_set_item| interval_set_item.add_interval_set(sub_set(range)) interval_set_item.intersect_range(range) end end.reduce do |acc, interval_set_item| acc.add_interval_set(interval_set_item); acc end @range_map = intersection.range_map @min = intersection.min @max = intersection.max self end def union_range(range) new_interval_set = IntervalSet.new new_interval_set.add_interval_set(self) unless subset_range?(range) new_interval_set.add_range(range) end def union_interval_set(interval_set) new_interval_set = clone new_interval_set.add_interval_set(interval_set) end def difference_range(range) new_interval_set = IntervalSet.new return new_interval_set if subset_range?(range) return new_interval_set.copy(self) unless bounds_intersected_by?(range) unless IntervalSet.range_empty?(range) new_interval_set.add_interval_set(head_set(range.first)) new_interval_set.add_interval_set(tail_set(range.last)) new_interval_set.remove_range(range) end end def difference_interval_set(interval_set) new_interval_set = IntervalSet.new return new_interval_set if interval_set == self || empty? new_interval_set.copy(self) new_interval_set.remove_interval_set(interval_set) if !interval_set.empty? && bounds_intersected_by?(interval_set.bounds) new_interval_set end def intersection_range(range) new_interval_set = IntervalSet.new return new_interval_set unless bounds_intersected_by?(range) return new_interval_set.copy(self) if subset_range?(range) new_interval_set.add(sub_set(range)) new_interval_set.intersect_range(range) end def intersection_interval_set(interval_set) new_interval_set = IntervalSet.new return new_interval_set if interval_set.empty? || !bounds_intersected_by?(interval_set.bounds) new_interval_set.add_interval_set(self) new_interval_set.intersect_interval_set(interval_set.sub_set(bounds)) end def convolve_range!(range) if IntervalSet.range_empty?(range) clear else buffer!(-range.first, range.last) end end def convolve_interval_set!(interval_set) interval_sets = interval_set.map {|range| clone.convolve_range!(range)} clear interval_sets.each {|rs| add_interval_set(rs)} self end private def update_bounds if empty? @min = nil @max = nil else @min = @range_map.first_entry.value.first @max = @range_map.last_entry.value.last end end def self.range_empty?(range) range.first >= range.last end def self.normalize_range(range) range.exclude_end? ? range : range.first...range.last end def self.unexpected_object(object) raise ArgumentError.new("unexpected object #{object}") end end