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)