diff --git a/lib/sorted_containers/sorted_list.rb b/lib/sorted_containers/sorted_list.rb index 7ae985a..5fb489a 100644 --- a/lib/sorted_containers/sorted_list.rb +++ b/lib/sorted_containers/sorted_list.rb @@ -1,13 +1,18 @@ # frozen_string_literal: true +# The SortedContainers module provides data structures for sorted collections. module SortedContainers class Error < StandardError; end + # The SortedList class is a sorted list implementation. class SortedList DEFAULT_LOAD_FACTOR = 1000 - + attr_reader :size - + + # Initializes a new SortedList object. + # + # @param iterable [Enumerable] An optional iterable object to initialize the list with. def initialize(iterable = []) @lists = [] @maxes = [] @@ -15,7 +20,10 @@ def initialize(iterable = []) @size = 0 update(iterable) end - + + # Adds a value to the sorted list. + # + # @param value [Object] The value to add. def add(value) i = bisect_right(@maxes, value) if i == @maxes.size @@ -30,95 +38,138 @@ def add(value) @size += 1 end + # Adds a value to the sorted list using the << operator. + # + # @param value [Object] The value to add. def <<(value) add(value) end - + + # Removes a value from the sorted list. + # + # @param value [Object] The value to remove. def remove(value) i = bisect_left(@maxes, value) return if i == @maxes.size - + idx = @lists[i].index(value) raise "Value not found: #{value}" unless idx - + internal_delete(i, idx) end + # Retrieves the value at the specified index. + # + # @param index [Integer] The index of the value to retrieve. + # @return [Object] The value at the specified index. def [](index) - raise "Index out of range" if index < 0 || index >= @size + raise "Index out of range" if index.negative? || index >= @size @lists.each do |sublist| - if index < sublist.size - return sublist[index] - else - index -= sublist.size - end + return sublist[index] if index < sublist.size + + index -= sublist.size end end + # Deletes the value at the specified index. + # + # @param index [Integer] The index of the value to delete. def delete_at(index) - raise "Index out of range" if index < 0 || index >= @size + raise "Index out of range" if index.negative? || index >= @size - @lists.each do |sublist| + deleted = false + @lists.each_with_index do |sublist, sublist_index| if index < sublist.size - internal_delete(@lists.index(sublist), index) - return + internal_delete(sublist_index, index) + deleted = true + break else index -= sublist.size end end + + raise "Index out of range" unless deleted end + # Clears the sorted list, removing all values. def clear @lists.clear @maxes.clear @size = 0 end + # Checks if the sorted list contains a value. + # + # @param value [Object] The value to check. + # @return [Boolean] True if the value is found, false otherwise. def contains(value) i = bisect_left(@maxes, value) return false if i == @maxes.size sublist = @lists[i] idx = bisect_left(sublist, value) - return idx < sublist.size && sublist[idx] == value + idx < sublist.size && sublist[idx] == value end + # Converts the sorted list to an array. + # + # @return [Array] An array representation of the sorted list. def to_a @lists.flatten end - + private - + + # Performs a left bisect on the array. + # + # @param array [Array] The array to bisect. + # @param value [Object] The value to bisect with. + # @return [Integer] The index where the value should be inserted. def bisect_left(array, value) array.bsearch_index { |x| x >= value } || array.size end - + + # Performs a right bisect on the array. + # + # @param array [Array] The array to bisect. + # @param value [Object] The value to bisect with. + # @return [Integer] The index where the value should be inserted. def bisect_right(array, value) array.bsearch_index { |x| x > value } || array.size end - - def expand(i) - list = @lists[i] - if list.size > (@load_factor * 2) - half = list.slice!(@load_factor, list.size - @load_factor) - @lists.insert(i + 1, half) - @maxes[i] = @lists[i].last - @maxes.insert(i + 1, half.last) - end + + # Expands a sublist if it exceeds the load factor. + # + # @param sublist_index [Integer] The index of the sublist to expand. + def expand(sublist_index) + sublist = @lists[sublist_index] + return unless sublist.size > (@load_factor * 2) + + half = sublist.slice!(@load_factor, sublist.size - @load_factor) + @lists.insert(sublist_index + 1, half) + @maxes[sublist_index] = @lists[sublist_index].last + @maxes.insert(sublist_index + 1, half.last) end - - def internal_delete(i, idx) - @lists[i].delete_at(idx) - if @lists[i].empty? - @lists.delete_at(i) - @maxes.delete_at(i) + + # Deletes a value from a sublist. + # + # @param sublist_index [Integer] The index of the sublist. + # @param idx [Integer] The index of the value to delete. + def internal_delete(sublist_index, idx) + @lists[sublist_index].delete_at(idx) + if @lists[sublist_index].empty? + @lists.delete_at(sublist_index) + @maxes.delete_at(sublist_index) else - @maxes[i] = @lists[i].last + @maxes[sublist_index] = @lists[sublist_index].last end @size -= 1 end - + + # Updates the sorted list with values from an iterable object. + # + # @param iterable [Enumerable] The iterable object to update the list with. def update(iterable) iterable.each { |item| add(item) } end