# frozen_string_literal: true require_relative "range_list/errors" require_relative "range_list/avl_tree/rbtree_adapter" class RangeList include Enumerable def initialize @avl_tree = AvlTree::RBTreeAdapter.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 self if empty_range?(range) # Get real range start. start_lower_range = avl_tree.lower_entry(range[0]) range_start = if !start_lower_range.nil? && start_lower_range[1] >= range[0] start_lower_range[0] else range[0] end # Get real range end. end_lower_range = avl_tree.lower_entry(range[1]) range_end = if !end_lower_range.nil? && end_lower_range[1] >= range[1] end_lower_range[1] else range[1] end # Insert or replace new range. avl_tree.put(range_start, range_end) # Remove keys between range, exclude start, include end. sub_range = avl_tree.sub_map(range[0] + 1, range[1]) sub_range.each { |range_start, _range_end| avl_tree.remove(range_start) } self end # Remove range from current range list. # @param (see #add) # @return [RangeList] # @raise (see #add) def remove(range) validate_range!(range) return self if empty_range?(range) # Insert end lower range, `-1` means not include. end_lower_range = avl_tree.lower_entry(range[1] - 1) if !end_lower_range.nil? && end_lower_range[1] > range[1] avl_tree.put(range[1], end_lower_range[1]) end # Relace start lower range, `-1` means not include. start_lower_range = avl_tree.lower_entry(range[0] - 1) if !start_lower_range.nil? && start_lower_range[1] > range[0] avl_tree.put(start_lower_range[0], range[0]) end # Remove keys between range, include start, exclude end sub_range = avl_tree.sub_map(range[0], range[1] - 1) sub_range.each { |range_start, _range_end| avl_tree.remove(range_start) } self end # Print current range list. # @return [void] def print info = map { |k, v| "[#{k}, #{v})" }.join(" ") puts info 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 if empty_range?(range) start_lower_range = avl_tree.lower_entry(range[0]) !start_lower_range.nil? && start_lower_range[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 if empty_range?(range) # `-1` means not include. end_lower_range = avl_tree.lower_entry(range[1] - 1) !end_lower_range.nil? && end_lower_range[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) validate_element!(element) lower_entry = avl_tree.lower_entry(element) !lower_entry.nil? && lower_entry[1] > element end # Give RangeList iterative ability. # @return [RangeList, Enumerator] # @yield [range_start, range_end] give the range element to the block def each(&block) if block avl_tree.each(&block) self else enum_for(:each) end end # @!visibility private def inspect data = map { |k, v| "[#{k}, #{v})" }.join(", ") "[#{data}]" end # @!visibility private alias_method :to_s, :inspect private attr_reader :avl_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 validate_element!(element) raise ArgumentError, "`element` should be `Integer` type." unless element.is_a?(Integer) end def empty_range?(range) range[0] >= range[1] end end