sokol-luajit/lua/binaryheap.lua

406 lines
12 KiB
Lua
Raw Normal View History

-------------------------------------------------------------------
-- Binary heap implementation
--
-- A binary heap (or binary tree) is a [sorting algorithm](http://en.wikipedia.org/wiki/Binary_heap).
--
-- The 'plain binary heap' is managed by positions. Which are hard to get once
-- an element is inserted. It can be anywhere in the list because it is re-sorted
-- upon insertion/deletion of items. The array with values is stored in field
-- `values`:
--
-- `peek = heap.values[1]`
--
-- A 'unique binary heap' is where the payload is unique and the payload itself
-- also stored (as key) in the heap with the position as value, as in;
-- `heap.reverse[payload] = [pos]`
--
-- Due to this setup the reverse search, based on payload, is now a
-- much faster operation because instead of traversing the list/heap,
-- you can do;
-- `pos = heap.reverse[payload]`
--
-- This means that deleting elements from a 'unique binary heap' is
-- faster than from a plain heap.
--
-- All management functions in the 'unique binary heap' take `payload`
-- instead of `pos` as argument.
-- Note that the value of the payload must be unique!
--
-- Fields of heap object:
--
-- * values - array of values
-- * payloads - array of payloads (unique binary heap only)
-- * reverse - map from payloads to indices (unique binary heap only)
local assert = assert
local floor = math.floor
local _ENV = nil
local M = {}
--================================================================
-- basic heap sorting algorithm
--================================================================
--- Basic heap.
-- This is the base implementation of the heap. Under regular circumstances
-- this should not be used, instead use a _Plain heap_ or _Unique heap_.
-- @section baseheap
--- Creates a new binary heap.
-- This is the core of all heaps, the others
-- are built upon these sorting functions.
-- @param swap (function) `swap(heap, idx1, idx2)` swaps values at
-- `idx1` and `idx2` in the heaps `heap.values` and `heap.payloads` lists (see
-- return value below).
-- @param erase (function) `swap(heap, position)` raw removal
-- @param lt (function) in `lt(a, b)` returns `true` when `a < b` (for a min-heap)
-- @return table with two methods; `heap:bubbleUp(pos)` and `heap:sinkDown(pos)`
-- that implement the sorting algorithm and two fields; `heap.values` and
-- `heap.payloads` being lists, holding the values and payloads respectively.
M.binaryHeap = function(swap, erase, lt)
local heap = {
values = {}, -- list containing values
erase = erase,
swap = swap,
lt = lt,
}
function heap:bubbleUp(pos)
local values = self.values
while pos>1 do
local parent = floor(pos/2)
if not lt(values[pos], values[parent]) then
break
end
swap(self, parent, pos)
pos = parent
end
end
function heap:sinkDown(pos)
local values = self.values
local last = #values
while true do
local min = pos
local child = 2 * pos
for c = child, child + 1 do
if c <= last and lt(values[c], values[min]) then min = c end
end
if min == pos then break end
swap(self, pos, min)
pos = min
end
end
return heap
end
--================================================================
-- plain heap management functions
--================================================================
--- Plain heap.
-- A plain heap carries a single piece of information per entry. This can be
-- any type (except `nil`), as long as the comparison function used to create
-- the heap can handle it.
-- @section plainheap
do end -- luacheck: ignore
-- the above is to trick ldoc (otherwise `update` below disappears)
local update
--- Updates the value of an element in the heap.
-- @function heap:update
-- @param pos the position which value to update
-- @param newValue the new value to use for this payload
update = function(self, pos, newValue)
assert(newValue ~= nil, "cannot add 'nil' as value")
assert(pos >= 1 and pos <= #self.values, "illegal position")
self.values[pos] = newValue
if pos > 1 then self:bubbleUp(pos) end
if pos < #self.values then self:sinkDown(pos) end
end
local remove
--- Removes an element from the heap.
-- @function heap:remove
-- @param pos the position to remove
-- @return value, or nil if a bad `pos` value was provided
remove = function(self, pos)
local last = #self.values
if pos < 1 then
return -- bad pos
elseif pos < last then
local v = self.values[pos]
self:swap(pos, last)
self:erase(last)
self:bubbleUp(pos)
self:sinkDown(pos)
return v
elseif pos == last then
local v = self.values[pos]
self:erase(last)
return v
else
return -- bad pos: pos > last
end
end
local insert
--- Inserts an element in the heap.
-- @function heap:insert
-- @param value the value used for sorting this element
-- @return nothing, or throws an error on bad input
insert = function(self, value)
assert(value ~= nil, "cannot add 'nil' as value")
local pos = #self.values + 1
self.values[pos] = value
self:bubbleUp(pos)
end
local pop
--- Removes the top of the heap and returns it.
-- @function heap:pop
-- @return value at the top, or `nil` if there is none
pop = function(self)
if self.values[1] ~= nil then
return remove(self, 1)
end
end
local peek
--- Returns the element at the top of the heap, without removing it.
-- @function heap:peek
-- @return value at the top, or `nil` if there is none
peek = function(self)
return self.values[1]
end
local size
--- Returns the number of elements in the heap.
-- @function heap:size
-- @return number of elements
size = function(self)
return #self.values
end
local function swap(heap, a, b)
heap.values[a], heap.values[b] = heap.values[b], heap.values[a]
end
local function erase(heap, pos)
heap.values[pos] = nil
end
--================================================================
-- plain heap creation
--================================================================
local function plainHeap(lt)
local h = M.binaryHeap(swap, erase, lt)
h.peek = peek
h.pop = pop
h.size = size
h.remove = remove
h.insert = insert
h.update = update
return h
end
--- Creates a new min-heap, where the smallest value is at the top.
-- @param lt (optional) comparison function (less-than), see `binaryHeap`.
-- @return the new heap
M.minHeap = function(lt)
if not lt then
lt = function(a,b) return (a < b) end
end
return plainHeap(lt)
end
--- Creates a new max-heap, where the largest value is at the top.
-- @param gt (optional) comparison function (greater-than), see `binaryHeap`.
-- @return the new heap
M.maxHeap = function(gt)
if not gt then
gt = function(a,b) return (a > b) end
end
return plainHeap(gt)
end
--================================================================
-- unique heap management functions
--================================================================
--- Unique heap.
-- A unique heap carries 2 pieces of information per entry.
--
-- 1. The `value`, this is used for ordering the heap. It can be any type (except
-- `nil`), as long as the comparison function used to create the heap can
-- handle it.
-- 2. The `payload`, this can be any type (except `nil`), but it MUST be unique.
--
-- With the 'unique heap' it is easier to remove elements from the heap.
-- @section uniqueheap
do end -- luacheck: ignore
-- the above is to trick ldoc (otherwise `update` below disappears)
local updateU
--- Updates the value of an element in the heap.
-- @function unique:update
-- @param payload the payoad whose value to update
-- @param newValue the new value to use for this payload
-- @return nothing, or throws an error on bad input
function updateU(self, payload, newValue)
return update(self, self.reverse[payload], newValue)
end
local insertU
--- Inserts an element in the heap.
-- @function unique:insert
-- @param value the value used for sorting this element
-- @param payload the payload attached to this element
-- @return nothing, or throws an error on bad input
function insertU(self, value, payload)
assert(self.reverse[payload] == nil, "duplicate payload")
local pos = #self.values + 1
self.reverse[payload] = pos
self.payloads[pos] = payload
return insert(self, value)
end
local removeU
--- Removes an element from the heap.
-- @function unique:remove
-- @param payload the payload to remove
-- @return value, payload or nil if not found
function removeU(self, payload)
local pos = self.reverse[payload]
if pos ~= nil then
return remove(self, pos), payload
end
end
local popU
--- Removes the top of the heap and returns it.
-- When used with timers, `pop` will return the payload that is due.
--
-- Note: this function returns `payload` as the first result to prevent
-- extra locals when retrieving the `payload`.
-- @function unique:pop
-- @return payload, value, or `nil` if there is none
function popU(self)
if self.values[1] then
local payload = self.payloads[1]
local value = remove(self, 1)
return payload, value
end
end
local peekU
--- Returns the element at the top of the heap, without removing it.
-- @function unique:peek
-- @return payload, value, or `nil` if there is none
peekU = function(self)
return self.payloads[1], self.values[1]
end
local peekValueU
--- Returns the element at the top of the heap, without removing it.
-- @function unique:peekValue
-- @return value at the top, or `nil` if there is none
-- @usage -- simple timer based heap example
-- while true do
-- sleep(heap:peekValue() - gettime()) -- assume LuaSocket gettime function
-- coroutine.resume((heap:pop())) -- assumes payload to be a coroutine,
-- -- double parens to drop extra return value
-- end
peekValueU = function(self)
return self.values[1]
end
local valueByPayload
--- Returns the value associated with the payload
-- @function unique:valueByPayload
-- @param payload the payload to lookup
-- @return value or nil if no such payload exists
valueByPayload = function(self, payload)
return self.values[self.reverse[payload]]
end
local sizeU
--- Returns the number of elements in the heap.
-- @function heap:size
-- @return number of elements
sizeU = function(self)
return #self.values
end
local function swapU(heap, a, b)
local pla, plb = heap.payloads[a], heap.payloads[b]
heap.reverse[pla], heap.reverse[plb] = b, a
heap.payloads[a], heap.payloads[b] = plb, pla
swap(heap, a, b)
end
local function eraseU(heap, pos)
local payload = heap.payloads[pos]
heap.reverse[payload] = nil
heap.payloads[pos] = nil
erase(heap, pos)
end
--================================================================
-- unique heap creation
--================================================================
local function uniqueHeap(lt)
local h = M.binaryHeap(swapU, eraseU, lt)
h.payloads = {} -- list contains payloads
h.reverse = {} -- reverse of the payloads list
h.peek = peekU
h.peekValue = peekValueU
h.valueByPayload = valueByPayload
h.pop = popU
h.size = sizeU
h.remove = removeU
h.insert = insertU
h.update = updateU
return h
end
--- Creates a new min-heap with unique payloads.
-- A min-heap is where the smallest value is at the top.
--
-- *NOTE*: All management functions in the 'unique binary heap'
-- take `payload` instead of `pos` as argument.
-- @param lt (optional) comparison function (less-than), see `binaryHeap`.
-- @return the new heap
M.minUnique = function(lt)
if not lt then
lt = function(a,b) return (a < b) end
end
return uniqueHeap(lt)
end
--- Creates a new max-heap with unique payloads.
-- A max-heap is where the largest value is at the top.
--
-- *NOTE*: All management functions in the 'unique binary heap'
-- take `payload` instead of `pos` as argument.
-- @param gt (optional) comparison function (greater-than), see `binaryHeap`.
-- @return the new heap
M.maxUnique = function(gt)
if not gt then
gt = function(a,b) return (a > b) end
end
return uniqueHeap(gt)
end
return M