# frozen_string_literal: true require "rbtree" require_relative "range_list/version" require_relative "range_list/errors" class RangeList include Enumerable def initialize @tree = RBTree.new end # Add range into current range list. # @param range [Array] the range, first element is range start, second is range end. # Range end need be greater or equal than range start. # @return [RangeList] # @raise [ArgumentError] when argement invalid def add(range) validate_range!(range) # Return when range is empty. return self if empty_range?(range) # Get real range start. start_floor_entry = tree.upper_bound(range[0]) range_start = if !start_floor_entry.nil? && start_floor_entry[1] >= range[0] start_floor_entry[0] else range[0] end # Get real range end. end_floor_entry = tree.upper_bound(range[1]) range_end = if !end_floor_entry.nil? && end_floor_entry[1] >= range[1] end_floor_entry[1] else range[1] end # Insert or replace new range. tree[range_start] = range_end # Remove keys between range, exclude start, include end. between_maps = tree.bound(range[0] + 1, range[1]) between_maps.to_a.each { |key, _value| tree.delete(key) } self end # Remove range from current range list. # @param (see #add) # @return [RangeList] # @raise (see #add) def remove(range) validate_range!(range) # Return when range is empty. return self if empty_range?(range) # Insert end lower entry end_lower_entry = tree.upper_bound(range[1] - 1) if !end_lower_entry.nil? && end_lower_entry[1] > range[1] tree[range[1]] = end_lower_entry[1] end # Relace start lower entry start_lower_entry = tree.upper_bound(range[0] - 1) if !start_lower_entry.nil? && start_lower_entry[1] > range[0] tree[start_lower_entry[0]] = range[0] end # Remove keys between range, include start, exclude end between_maps = tree.bound(range[0], range[1] - 1) between_maps.to_a.each { |key, _value| tree.delete(key) } self end # Print current range list. # @return [void] def print puts map { |k, v| "[#{k}, #{v})" }.join(" ") end # Returns true if current ranges contains all elements of the range. # @param (see #add) # @return [Boolean] # @note return false if the argument range is empty # @raise (see #add) def contains_all?(range) validate_range!(range) # Return false when range is empty. return false if empty_range?(range) start_floor_entry = tree.upper_bound(range[0]) !start_floor_entry.nil? && start_floor_entry[1] >= range[1] end # Returns true if current ranges contains any element of the range. # @param (see #add) # @return [Boolean] # @note return false if the argument range is empty # @raise (see #add) def contains_any?(range) validate_range!(range) # Return false when range is empty. return false if empty_range?(range) end_lower_entry = tree.upper_bound(range[1] - 1) !end_lower_entry.nil? && end_lower_entry[1] > range[0] end # Returns true if current ranges contains the element. # @param element [Integer] the range element # @return [Boolean] # @raise (see #add) def contains?(element) raise ArgumentError, "`element` should be `Integer` type." unless element.is_a?(Integer) floor_entry = tree.upper_bound(element) !floor_entry.nil? && floor_entry[1] > element end # Give RangeList iterative ability. # @yield [range_start, range_end] give the range element to the block def each(&block) if block tree.each(&block) else enum_for(:each) end end # @!visibility private def inspect to_a.inspect end private attr_reader :tree def validate_range!(range) raise ArgumentError, "`range` should be `Array` type." unless range.is_a?(Array) raise ArgumentError, "`range` size should be equal to 2." unless range.size == 2 raise ArgumentError, "All elements of `range` should be `Integer`." unless range.all? { |item| item.is_a?(Integer) } end def empty_range?(range) range[0] >= range[1] end end