# frozen_string_literal: true
require "d_heap/d_heap"
require "d_heap/version"
# A fast _d_-ary heap implementation for ruby, useful in priority queues and graph
# algorithms.
#
# The _d_-ary heap data structure is a generalization of the binary heap, in which
# the nodes have _d_ children instead of 2. This allows for "decrease priority"
# operations to be performed more quickly with the tradeoff of slower delete
# minimum. Additionally, _d_-ary heaps can have better memory cache behavior than
# binary heaps, allowing them to pop more quickly in practice despite slower
# worst-case time complexity.
#
# Although _d_ can be configured when creating the heap, it's usually best to
# keep the default value of 4, because d=4 gives the smallest coefficient for
# (d + 1) log n / log d result. As always, use benchmarks for your
# particular use-case.
#
# @example Basic push, peek, and pop
# # create some example objects to place in our heap
# Task = Struct.new(:id, :time) do
# def to_f; time.to_f end
# end
# t1 = Task.new(1, Time.now + 5*60)
# t2 = Task.new(2, Time.now + 50)
# t3 = Task.new(3, Time.now + 60)
# t4 = Task.new(4, Time.now + 5)
#
# # create the heap
# require "d_heap"
# heap = DHeap.new
#
# # push with an explicit score (which might be extrinsic to the value)
# heap.push t1, t1.to_f
#
# # the score will be implicitly cast with Float, so any object with #to_f
# heap.push t2, t2
#
# # if the object has an intrinsic score via #to_f, "<<" is the simplest API
# heap << t3 << t4
#
# # pop returns the lowest scored item, and removes it from the heap
# heap.pop # => #
# heap.pop # => #
#
# # peek returns the lowest scored item, without removing it from the heap
# heap.peek # => #
# heap.pop # => #
#
# # pop_lte handles the common "h.pop if h.peek_score < max" pattern
# heap.pop_lte(Time.now + 65) # => nil
#
# # the heap size can be inspected with size and empty?
# heap.empty? # => false
# heap.size # => 1
# heap.pop # => #
# heap.empty? # => true
# heap.size # => 0
#
# # popping from an empty heap returns nil
# heap.pop # => nil
#
class DHeap
alias deq pop
alias shift pop
alias next pop
alias pop_all_lt pop_all_below
alias pop_below pop_lt
alias enq push
alias first peek
alias length size
alias count size
# Initialize a _d_-ary min-heap.
#
# @param d [Integer] Number of children for each parent node.
# Higher values generally speed up push but slow down pop.
# If all pushes are popped, the default is probably best.
# @param capacity [Integer] initial capacity of the heap.
def initialize(d: DEFAULT_D, capacity: DEFAULT_CAPA) # rubocop:disable Naming/MethodParameterName
__init_without_kw__(d, capacity, false)
end
# Consumes the heap by popping each minumum value until it is empty.
#
# If you want to iterate over the heap without consuming it, you will need to
# first call +#dup+
#
# @param with_score [Boolean] if scores shoul also be yielded
#
# @yieldparam value [Object] each value that would be popped
# @yieldparam score [Numeric] each value's score, if +with_scores+ is true
#
# @return [Enumerator] if no block is given
# @return [nil] if a block is given
def each_pop(with_scores: false)
return to_enum(__method__, with_scores: with_scores) unless block_given?
if with_scores
yield(*pop_with_score) until empty?
else
yield pop until empty?
end
nil
end
if defined?(Map)
# Unlike {DHeap}, an object can only be added into a {DHeap::Map} once. Any
# subsequent addition simply rescores the already existing member. Objects
# are identified by their {#hash} value, just like with Hash.
class Map
alias score :[]
alias rescore :[]=
alias update :[]=
# Initialize a _d_-ary min-heap which can map objects to scores.
#
# @param d [Integer] Number of children for each parent node.
# Higher values generally speed up push but slow down pop.
# If all pushes are popped, the default is probably best.
# @param capacity [Integer] initial capacity of the heap.
def initialize(d: DEFAULT_D, capacity: DEFAULT_CAPA) # rubocop:disable Naming/MethodParameterName
__init_without_kw__(d, capacity, true)
end
end
end
end