singe/thirdparty/binaryheap.lua/spec/binaryheap_spec.lua

835 lines
20 KiB
Lua

local bh = require('binaryheap')
local data = {
{ value = 98, payload = "pos08" }, -- 1
{ value = 28, payload = "pos05" }, -- 2
{ value = 36, payload = "pos06" }, -- 3
{ value = 48, payload = "pos09" }, -- 4
{ value = 68, payload = "pos10" }, -- 5
{ value = 58, payload = "pos13" }, -- 6
{ value = 80, payload = "pos15" }, -- 7
{ value = 46, payload = "pos04" }, -- 8
{ value = 19, payload = "pos03" }, -- 9
{ value = 66, payload = "pos11" }, -- 10
{ value = 22, payload = "pos02" }, -- 11
{ value = 60, payload = "pos12" }, -- 12
{ value = 15, payload = "pos01" }, -- 13
{ value = 83, payload = "pos14" }, -- 14
{ value = 59, payload = "pos07" }, -- 15
}
local sort = function(t)
table.sort(t, function(a,b) return (a.value < b.value) end)
return t
end
local function check(heap)
for pos = 2, #heap.values do
local parent = math.floor(pos / 2)
assert(not heap.lt(heap.values[pos], heap.values[parent]))
end
if heap.payloads then
for pos in ipairs(heap.values) do
local payload = heap.payloads[pos]
assert(heap.reverse[payload] == pos)
end
end
end
local function newheap()
-- create a heap with data
local heap = bh.minUnique()
for _, node in ipairs(data) do
heap:insert(node.value,node.payload)
check(heap)
end
-- create a sorted list with data, sorted by 'value'
local sorted = {}
for k,v in pairs(data) do sorted[k] = v end
sort(sorted)
-- create a reverse list of the sorted table; returns sorted-index, based on 'value'
local sreverse = {}
for i,v in ipairs(sorted) do
sreverse[v.value] = i
end
return heap, sorted, sreverse
end
local function testheap(heap, sorted)
while sorted[1] do
local value1, payload1
if heap.reverse then
-- it is a unique heap
payload1, value1 = heap:pop()
else
-- it is a plain heap
value1, payload1 = heap:pop()
end
local value2, payload2 = sorted[1].value, sorted[1].payload
table.remove(sorted, 1)
assert.are.equal(payload1, payload2)
assert.are.equal(value1, value2)
end
end
describe("[minUnique]", function()
it("validates order of insertion", function()
local h = newheap()
assert.are.equal(h.payloads[1], data[13].payload)
assert.are.equal(h.payloads[2], data[11].payload)
assert.are.equal(h.payloads[3], data[9].payload)
assert.are.equal(h.payloads[4], data[8].payload)
assert.are.equal(h.payloads[5], data[2].payload)
assert.are.equal(h.payloads[6], data[3].payload)
assert.are.equal(h.payloads[7], data[15].payload)
assert.are.equal(h.payloads[8], data[1].payload)
assert.are.equal(h.payloads[9], data[4].payload)
assert.are.equal(h.payloads[10], data[5].payload)
assert.are.equal(h.payloads[11], data[10].payload)
assert.are.equal(h.payloads[12], data[12].payload)
assert.are.equal(h.payloads[13], data[6].payload)
assert.are.equal(h.payloads[14], data[14].payload)
assert.are.equal(h.payloads[15], data[7].payload)
end)
it("validates order of popping", function()
testheap(newheap())
end)
it("peek()", function()
local heap, sorted = newheap()
local payload, value = heap:peek()
-- correct values?
assert.are.equal(value, sorted[1].value)
assert.are.equal(payload, sorted[1].payload)
-- are they still on the heap?
assert.are.equal(value, heap.values[1])
assert.are.equal(payload, heap.payloads[1])
end)
it("peekValue()", function()
local h = bh.minUnique()
h:insert(1, 11)
assert.equal(1, h:peekValue(11))
-- try again empty
h:pop()
assert.is_nil(h:peekValue(11))
end)
it("pop()", function()
local h = bh.minUnique()
h:insert(3, 13)
h:insert(2, 12)
h:insert(1, 11)
-- try again empty
local pl, v
pl, v = h:pop()
assert.equal(v, 1)
assert.equal(pl, 11)
pl, v = h:pop()
assert.equal(v, 2)
assert.equal(pl, 12)
pl, v = h:pop()
assert.equal(v, 3)
assert.equal(pl, 13)
pl, v = h:pop()
assert.is_nil(v)
assert.is_nil(pl)
end)
describe("remove()", function()
it("a middle item", function()
local heap, sorted = newheap()
local idx = 4
local value = sorted[idx].value
local payload = sorted[idx].payload
local v, pl = heap:remove(payload)
check(heap)
-- did we get the right ones?
assert.are.equal(value, v)
assert.are.equal(payload, pl)
assert.is.Nil(heap[payload])
-- remove from test data and compare
table.remove(sorted, idx)
testheap(heap, sorted)
end)
it("the last item (of the array)", function()
local heap, sorted = newheap()
local idx = #heap.values
local value = sorted[idx].value
local payload = sorted[idx].payload
local v, pl = heap:remove(payload)
check(heap)
-- did we get the right ones?
assert.are.equal(value, v)
assert.are.equal(payload, pl)
assert.is.Nil(heap.reverse[payload])
-- remove from test data and compare
table.remove(sorted, idx)
testheap(heap, sorted)
end)
it("non existing payload returns nil", function()
local heap = newheap()
local v, pl = heap:remove({})
assert.is_nil(v)
assert.is_nil(pl)
end)
it("nil payload returns nil", function()
local heap = newheap()
local v, pl = heap:remove(nil)
assert.is_nil(v)
assert.is_nil(pl)
end)
it("with repeated values", function()
local h = bh.minUnique()
h:insert(1, 11)
check(h)
h:insert(1, 12)
check(h)
local value, payload
value, payload = h:remove(11)
check(h)
assert.equal(1, value)
assert.equal(11, payload)
payload, value = h:peek()
assert.equal(1, value)
assert.equal(12, payload)
assert.same({1}, h.values)
assert.same({12}, h.payloads)
assert.same({[12]=1}, h.reverse)
end)
end)
describe("insert()", function()
it("a top item", function()
local heap, sorted = newheap()
local nvalue = sorted[1].value - 10
local npayload = {}
table.insert(sorted, 1, {})
sorted[1].value = nvalue
sorted[1].payload = npayload
heap:insert(nvalue, npayload)
check(heap)
testheap(heap, sorted)
end)
it("a middle item", function()
local heap, sorted = newheap()
local nvalue = 57
local npayload = {}
table.insert(sorted, { value = nvalue, payload = npayload })
sort(sorted)
heap:insert(nvalue, npayload)
check(heap)
testheap(heap, sorted)
end)
it("a last item", function()
local heap, sorted = newheap()
local nvalue = sorted[#sorted].value + 10
local npayload = {}
table.insert(sorted, {})
sorted[#sorted].value = nvalue
sorted[#sorted].payload = npayload
heap:insert(nvalue, npayload)
check(heap)
testheap(heap, sorted)
end)
it("a nil value throws an error", function()
local heap = newheap()
assert.has.error(function()
heap:insert(nil, "something")
end)
end)
it("a nil payload throws an error", function()
local heap = newheap()
assert.has.error(function()
heap:insert(15, nil)
end)
end)
it("a duplicate payload throws an error", function()
local heap = newheap()
local value = {}
heap:insert(1, value)
assert.has.error(function()
heap:insert(2, value)
end)
end)
end)
describe("update()", function()
it("a top item", function()
local heap, sorted = newheap()
local idx = 1
local payload = sorted[idx].payload
local nvalue = sorted[#sorted].value + 1 -- move to end with new value
sorted[idx].value = nvalue
sort(sorted)
heap:update(payload, nvalue)
check(heap)
testheap(heap, sorted)
end)
it("a middle item", function()
local heap, sorted = newheap()
local idx = 4
local payload = sorted[idx].payload
local nvalue = sorted[idx].value * 2
sorted[idx].value = nvalue
sort(sorted)
heap:update(payload, nvalue)
check(heap)
testheap(heap, sorted)
end)
it("a last item", function()
local heap, sorted = newheap()
local idx = #sorted
local payload = sorted[idx].payload
local nvalue = sorted[1].value - 1 -- move to top with new value
sorted[idx].value = nvalue
sort(sorted)
heap:update(payload, nvalue)
check(heap)
testheap(heap, sorted)
end)
it("a nil value throws an error", function()
local heap, sorted = newheap()
local idx = #sorted
local payload = sorted[idx].payload
assert.has.error(function()
heap:update(payload, nil)
end)
end)
it("an unknown payload throws an error", function()
local heap = newheap()
assert.has.error(function()
heap:update({}, 10)
end)
end)
end)
describe("size()", function()
it("returns number of elements", function()
local h = bh.minUnique()
assert.equal(0, h:size())
h:insert(1, -1)
assert.equal(1, h:size())
h:insert(2, -2)
assert.equal(2, h:size())
h:insert(3, -3)
assert.equal(3, h:size())
h:insert(4, -4)
assert.equal(4, h:size())
h:insert(5, -5)
assert.equal(5, h:size())
h:pop()
assert.equal(4, h:size())
h:pop()
assert.equal(3, h:size())
h:pop()
assert.equal(2, h:size())
h:pop()
assert.equal(1, h:size())
h:pop()
assert.equal(0, h:size())
end)
end)
describe("valueByPayload()", function()
it("gets value by payload", function()
local h = bh.minUnique()
h:insert(1, -1)
h:insert(2, -2)
h:insert(3, -3)
h:insert(4, -4)
h:insert(5, -5)
assert.equal(1, h:valueByPayload((-1)))
assert.equal(2, h:valueByPayload((-2)))
assert.equal(3, h:valueByPayload((-3)))
assert.equal(4, h:valueByPayload((-4)))
assert.equal(5, h:valueByPayload((-5)))
h:remove(-1)
assert.falsy(h:valueByPayload((-1)))
end)
it("non existing payload returns nil", function()
local h = bh.minUnique()
h:insert(1, -1)
h:insert(2, -2)
h:insert(3, -3)
h:insert(4, -4)
h:insert(5, -5)
assert.is_nil(h:valueByPayload({}))
end)
it("nil payload returns nil", function()
local h = bh.minUnique()
h:insert(1, -1)
h:insert(2, -2)
h:insert(3, -3)
h:insert(4, -4)
h:insert(5, -5)
assert.is_nil(h:valueByPayload(nil))
end)
end)
it("creates minUnique with custom less-than function", function()
local h = bh.minUnique(function (a, b)
return math.abs(a) < math.abs(b)
end)
h:insert(1, -1)
check(h)
h:insert(-2, 2)
check(h)
h:insert(3, -3)
check(h)
h:insert(-4, 4)
check(h)
h:insert(5, -5)
check(h)
local value, payload
payload, value = h:peek()
assert.equal(1, value)
assert.equal(-1, payload)
h:pop()
check(h)
payload, value = h:peek()
assert.equal(-2, value)
assert.equal(2, payload)
end)
end)
describe("[maxUnique]", function()
it("creates maxUnique with custom less-than function", function()
local h = bh.maxUnique(function (a, b)
return math.abs(a) > math.abs(b)
end)
h:insert(1, -1)
check(h)
h:insert(-2, 2)
check(h)
h:insert(3, -3)
check(h)
h:insert(-4, 4)
check(h)
h:insert(5, -5)
check(h)
local value, payload
payload, value = h:peek()
assert.equal(5, value)
assert.equal(-5, payload)
h:pop()
check(h)
payload, value = h:peek()
assert.equal(-4, value)
assert.equal(4, payload)
end)
end)
describe("[minHeap]", function()
it("creates minHeap", function()
local h = bh.minHeap()
check(h)
end)
describe("insert()", function()
it("a number into minHeap", function()
local h = bh.minHeap()
h:insert(42)
check(h)
end)
it("nil throws an error", function()
local h = bh.minHeap()
assert.has.error(function()
h:insert(nil)
end)
end)
end)
describe("remove()", function()
it("a position", function()
local h = bh.minHeap()
h:insert(42)
h:insert(43)
assert.equal(43, h:remove(2))
check(h)
end)
it("a bad position returns nil", function()
local h = bh.minHeap()
h:insert(42)
h:insert(43)
assert.is_nil(h:remove(0))
assert.is_nil(h:remove(3))
check(h)
end)
end)
describe("size()", function()
it("returns number of elements", function()
local h = bh.minHeap()
assert.equal(0, h:size())
h:insert(1)
assert.equal(1, h:size())
h:insert(2)
assert.equal(2, h:size())
h:insert(3)
assert.equal(3, h:size())
h:insert(4)
assert.equal(4, h:size())
h:insert(5)
assert.equal(5, h:size())
h:pop()
assert.equal(4, h:size())
h:pop()
assert.equal(3, h:size())
h:pop()
assert.equal(2, h:size())
h:pop()
assert.equal(1, h:size())
h:pop()
assert.equal(0, h:size())
end)
end)
describe("peek()", function()
it("return nil in empty minHeap", function()
local h = bh.minHeap()
assert.is_nil(h:peek())
check(h)
end)
it("minHeap of one element", function()
local h = bh.minHeap()
h:insert(42)
check(h)
local value, payload = h:peek()
assert.equal(42, value)
assert.falsy(payload)
end)
it("minHeap of two elements", function()
local h = bh.minHeap()
h:insert(42)
check(h)
h:insert(1)
check(h)
local value, payload = h:peek()
assert.equal(1, value)
assert.falsy(payload)
end)
it("minHeap of 10 elements", function()
local h = bh.minHeap()
h:insert(10)
h:insert(7)
h:insert(1)
h:insert(5)
h:insert(6)
h:insert(9)
h:insert(8)
h:insert(4)
h:insert(2)
h:insert(3)
check(h)
local value, payload = h:peek()
assert.equal(1, value)
assert.falsy(payload)
end)
it("removes peek in minHeap of 5 elements", function()
local h = bh.minHeap()
h:insert(1)
h:insert(2)
h:insert(3)
h:insert(4)
h:insert(5)
local value
value = h:pop()
check(h)
assert.equal(1, value)
value = h:peek()
assert.equal(2, value)
end)
end)
describe("update()", function()
it("in minHeap of 5 elements (pos 2 -> pos 1)", function()
local h = bh.minHeap()
h:insert(1)
h:insert(2)
h:insert(3)
h:insert(4)
h:insert(5)
check(h)
h:update(2, -100)
check(h)
local value = h:peek()
assert.equal(-100, value)
end)
it("in minHeap of 5 elements (pos 1 -> pos 2)", function()
local h = bh.minHeap()
h:insert(1)
h:insert(2)
h:insert(3)
h:insert(4)
h:insert(5)
check(h)
h:update(1, 100)
check(h)
local value = h:peek()
assert.equal(2, value)
end)
it("nil throws an error", function()
local h = bh.minHeap()
h:insert(10)
assert.has.error(function()
h:update(nil)
end)
end)
it("bad position throws an error", function()
local h = bh.minHeap()
h:insert(10)
assert.has.error(function()
h:update(0)
end)
assert.has.error(function()
h:update(2)
end)
end)
end)
it("creates minHeap with custom less-than function", function()
local h = bh.minHeap(function (a, b)
return math.abs(a) < math.abs(b)
end)
h:insert(1)
check(h)
h:insert(-2)
check(h)
h:insert(3)
check(h)
h:insert(-4)
check(h)
h:insert(5)
check(h)
assert.equal(1, h:peek())
h:pop()
check(h)
assert.equal(-2, h:peek())
end)
end)
describe("[maxHeap]", function()
it("creates maxHeap", function()
local h = bh.maxHeap()
check(h)
end)
describe("insert()", function()
it("inserts a number into maxHeap", function()
local h = bh.maxHeap()
h:insert(42)
check(h)
end)
it("nil throws an error", function()
local h = bh.maxHeap()
assert.has.error(function()
h:insert(nil)
end)
end)
end)
describe("remove()", function()
it("a position", function()
local h = bh.maxHeap()
h:insert(42)
h:insert(43)
assert.equal(42, h:remove(2))
check(h)
end)
it("a bad position returns nil", function()
local h = bh.maxHeap()
h:insert(42)
h:insert(43)
assert.is_nil(h:remove(0))
assert.is_nil(h:remove(3))
check(h)
end)
end)
describe("size()", function()
it("returns number of elements", function()
local h = bh.minHeap()
assert.equal(0, h:size())
h:insert(1)
assert.equal(1, h:size())
h:insert(2)
assert.equal(2, h:size())
h:insert(3)
assert.equal(3, h:size())
h:insert(4)
assert.equal(4, h:size())
h:insert(5)
assert.equal(5, h:size())
h:pop()
assert.equal(4, h:size())
h:pop()
assert.equal(3, h:size())
h:pop()
assert.equal(2, h:size())
h:pop()
assert.equal(1, h:size())
h:pop()
assert.equal(0, h:size())
end)
end)
describe("peek()", function()
it("return nil in empty maxHeap", function()
local h = bh.maxHeap()
assert.is_nil(h:peek())
check(h)
end)
it("maxHeap of one element", function()
local h = bh.maxHeap()
h:insert(42)
check(h)
local value = h:peek()
assert.equal(42, value)
end)
it("maxHeap of two elements", function()
local h = bh.maxHeap()
h:insert(42)
check(h)
h:insert(1)
check(h)
local value = h:peek()
assert.equal(42, value)
end)
it("maxHeap of 10 elements", function()
local h = bh.maxHeap()
h:insert(10)
h:insert(7)
h:insert(1)
h:insert(5)
h:insert(6)
h:insert(9)
h:insert(8)
h:insert(4)
h:insert(2)
h:insert(3)
check(h)
local value = h:peek()
assert.equal(10, value)
end)
it("removes peek in maxHeap of 5 elements", function()
local h = bh.maxHeap()
h:insert(1)
h:insert(2)
h:insert(3)
h:insert(4)
h:insert(5)
check(h)
local value
value = h:pop()
check(h)
assert.equal(5, value)
value = h:peek()
assert.equal(4, value)
end)
end)
describe("update()", function()
it("in maxHeap of 5 elements (pos 2 -> pos 1)", function()
local h = bh.maxHeap()
h:insert(1)
h:insert(2)
h:insert(3)
h:insert(4)
h:insert(5)
check(h)
h:update(2, 100)
check(h)
local value = h:peek()
assert.equal(100, value)
end)
it("in maxHeap of 5 elements (pos 1 -> pos 2)", function()
local h = bh.maxHeap()
h:insert(1)
h:insert(2)
h:insert(3)
h:insert(4)
h:insert(5)
check(h)
h:update(1, -100)
check(h)
local value = h:peek()
assert.equal(4, value)
end)
it("nil throws an error", function()
local h = bh.maxHeap()
h:insert(10)
assert.has.error(function()
h:update(nil)
end)
end)
it("bad position throws an error", function()
local h = bh.maxHeap()
h:insert(10)
assert.has.error(function()
h:update(0)
end)
assert.has.error(function()
h:update(2)
end)
end)
end)
it("creates maxHeap with custom greater-than function", function()
local h = bh.maxHeap(function (a, b)
return math.abs(a) > math.abs(b)
end)
h:insert(1)
check(h)
h:insert(-2)
check(h)
h:insert(3)
check(h)
h:insert(-4)
check(h)
h:insert(5)
check(h)
assert.equal(5, (h:peek()))
h:pop()
check(h)
assert.equal(-4, (h:peek()))
end)
end)