# frozen_string_literal: true require "treemap" require_relative "range_list/version" require_relative "range_list/errors" class RangeList include Enumerable def initialize @tree = TreeMap.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.floor_entry(range[0]) range_start = if !start_floor_entry.nil? && start_floor_entry.value >= range[0] start_floor_entry.key else range[0] end # Get real range end. end_floor_entry = tree.floor_entry(range[1]) range_end = if !end_floor_entry.nil? && end_floor_entry.value >= range[1] end_floor_entry.value else range[1] end # Insert or replace new range. tree.put(range_start, range_end) # Remove keys between range, exclude start, include end. between_maps = tree.sub_map(range[0], false, range[1], true) between_maps.keys.each { |key| tree.remove(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.lower_entry(range[1]) if !end_lower_entry.nil? && end_lower_entry.value > range[1] tree.put(range[1], end_lower_entry.value) end # Relace start lower entry start_lower_entry = tree.lower_entry(range[0]) if !start_lower_entry.nil? && start_lower_entry.value > range[0] tree.put(start_lower_entry.key, range[0]) end # Remove keys between range, include start, exclude end between_maps = tree.sub_map(range[0], true, range[1], false) between_maps.keys.each { |key| tree.remove(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.floor_entry(range[0]) !start_floor_entry.nil? && start_floor_entry.value >= 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.lower_entry(range[1]) !end_lower_entry.nil? && end_lower_entry.value > 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.floor_entry(element) !floor_entry.nil? && floor_entry.value > element end # Give RangeList iterative ability. # @yield [range_start, range_end] give the range element to the block def each(&block) if block # Iterating an empty tree will raise an error, # see https://github.com/davidkellis/treemap/issues/1 tree.empty? ? self : 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