init
commit
46ebc4396c
@ -0,0 +1,5 @@
|
||||
module git.wens.org.uk/heapqueue
|
||||
|
||||
go 1.18
|
||||
|
||||
require golang.org/x/exp v0.0.0-20220602145555-4a0574d9293f
|
@ -0,0 +1,2 @@
|
||||
golang.org/x/exp v0.0.0-20220602145555-4a0574d9293f h1:KK6mxegmt5hGJRcAnEDjSNLxIRhZxDcgwMbcO/lMCRM=
|
||||
golang.org/x/exp v0.0.0-20220602145555-4a0574d9293f/go.mod h1:yh0Ynu2b5ZUe3MQfp2nM0ecK7wsgouWTDN0FNeJuIys=
|
@ -0,0 +1,127 @@
|
||||
package heap
|
||||
|
||||
import "golang.org/x/exp/constraints"
|
||||
|
||||
type HeapType bool
|
||||
|
||||
const (
|
||||
MINHEAP HeapType = false
|
||||
MAXHEAP HeapType = true
|
||||
)
|
||||
|
||||
// Heap object for generic orderable (i.e. can be compared with < or >) data types
|
||||
type Heap[Orderable constraints.Ordered] struct {
|
||||
data []Orderable
|
||||
heapSize int
|
||||
}
|
||||
|
||||
// Parent index is returned for an input index
|
||||
func Parent(index int) (parent int) {
|
||||
return (index - 1) / 2
|
||||
}
|
||||
|
||||
// Left index is returned for an input index
|
||||
func Left(index int) (left int) {
|
||||
return 2*index + 1
|
||||
}
|
||||
|
||||
// Right index is returned for an input index
|
||||
func Right(index int) (right int) {
|
||||
return 2*index + 2
|
||||
}
|
||||
|
||||
// exchange two items within the context of the heap. requires max/min heapify afterwards
|
||||
func exchange[Orderable constraints.Ordered](h *Heap[Orderable], x, y int) {
|
||||
h.data[x], h.data[y] = h.data[y], h.data[x]
|
||||
}
|
||||
|
||||
// IsMaxHeap returns true when the heap satisfies the max heap property i.e. holds a valid structure for a max heap
|
||||
func IsMaxHeap[Orderable constraints.Ordered](h *Heap[Orderable]) bool {
|
||||
for index, val := range h.data {
|
||||
if h.data[Parent(index)] < val {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// IsMinHeap returns true when the heap satisfies the min heap property i.e. holds a valid structure for a min heap
|
||||
func IsMinHeap[Orderable constraints.Ordered](h *Heap[Orderable]) bool {
|
||||
for index, val := range h.data {
|
||||
if h.data[Parent(index)] > val {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
// maxHeapify reorders into a min heap starting at the input index i
|
||||
func maxHeapify[Orderable constraints.Ordered](h *Heap[Orderable], i int) {
|
||||
l, r := Left(i), Right(i)
|
||||
|
||||
var largest int
|
||||
if l < h.heapSize && h.data[l] > h.data[i] {
|
||||
largest = l
|
||||
} else {
|
||||
largest = i
|
||||
}
|
||||
if r < h.heapSize && h.data[r] > h.data[largest] {
|
||||
largest = r
|
||||
}
|
||||
|
||||
if largest != i {
|
||||
exchange(h, i, largest)
|
||||
maxHeapify(h, largest)
|
||||
}
|
||||
}
|
||||
|
||||
// minHeapify reorders into a min heap starting at the input index i
|
||||
func minHeapify[Orderable constraints.Ordered](h *Heap[Orderable], i int) {
|
||||
l, r := Left(i), Right(i)
|
||||
|
||||
var smallest int
|
||||
if l < h.heapSize && h.data[l] < h.data[i] {
|
||||
smallest = l
|
||||
} else {
|
||||
smallest = i
|
||||
}
|
||||
if r < h.heapSize && h.data[r] < h.data[smallest] {
|
||||
smallest = r
|
||||
}
|
||||
|
||||
if smallest != i {
|
||||
exchange(h, i, smallest)
|
||||
minHeapify(h, smallest)
|
||||
}
|
||||
}
|
||||
|
||||
// BuildHeap takes a generic slice (any type that can be compared with < and >) and the type of heap and returns a pointer to a heap structure
|
||||
func BuildHeap[Orderable constraints.Ordered](d []Orderable, maxHeap HeapType) *Heap[Orderable] {
|
||||
heap := &Heap[Orderable]{d, len(d)}
|
||||
for i := len(d) / 2; i >= 0; i-- {
|
||||
if maxHeap {
|
||||
maxHeapify(heap, i)
|
||||
} else {
|
||||
minHeapify(heap, i)
|
||||
}
|
||||
}
|
||||
return heap
|
||||
|
||||
}
|
||||
|
||||
// HeapSort takes a generic slice (any type that can be compared with < and >) and returns the sorted slice
|
||||
func HeapSort[Orderable constraints.Ordered](d []Orderable, maxHeap HeapType) []Orderable {
|
||||
heap := BuildHeap(d, maxHeap)
|
||||
|
||||
for i := len(d) - 1; i > 0; i-- {
|
||||
exchange(heap, 0, i)
|
||||
heap.heapSize = heap.heapSize - 1
|
||||
if maxHeap {
|
||||
maxHeapify(heap, 0)
|
||||
} else {
|
||||
minHeapify(heap, 0)
|
||||
}
|
||||
}
|
||||
return heap.data
|
||||
}
|
@ -0,0 +1,155 @@
|
||||
package queue
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"git.wens.org.uk/heapqueue/pkg/heap"
|
||||
"golang.org/x/exp/constraints"
|
||||
)
|
||||
|
||||
//QueueType is an enum for min/max queue
|
||||
type QueueType bool
|
||||
|
||||
const (
|
||||
MINQUEUE QueueType = false
|
||||
MAXQUEUE QueueType = true
|
||||
)
|
||||
|
||||
// Queue data type, holding a slice of generic queue objects
|
||||
type Queue[Orderable constraints.Ordered, T any] struct {
|
||||
store []QueueObject[Orderable, T]
|
||||
queueSize int
|
||||
}
|
||||
|
||||
// QueueObject holds generic types for key (comparable with > and < only), and any for value
|
||||
type QueueObject[Orderable constraints.Ordered, T any] struct {
|
||||
Key Orderable
|
||||
Value T
|
||||
}
|
||||
|
||||
// BuildQueue takes an input slice of queue objects and the type of queue, returning a pointer to the constructed priority queue
|
||||
func BuildQueue[Orderable constraints.Ordered, T any](d []QueueObject[Orderable, T], qType QueueType) *Queue[Orderable, T] {
|
||||
q := &Queue[Orderable, T]{d, len(d)}
|
||||
for i := len(d) / 2; i >= 0; i-- {
|
||||
if qType == MAXQUEUE {
|
||||
maxHeapify(q, i)
|
||||
} else {
|
||||
minHeapify(q, i)
|
||||
}
|
||||
}
|
||||
return q
|
||||
}
|
||||
|
||||
// exchange two items within the context of the queue. requires max/min heapify afterwards
|
||||
func exchange[Orderable constraints.Ordered, T any](q *Queue[Orderable, T], x, y int) {
|
||||
q.store[x], q.store[y] = q.store[y], q.store[x]
|
||||
}
|
||||
|
||||
// maxHeapify reorders into a max heap starting at the input index i
|
||||
func maxHeapify[Orderable constraints.Ordered, T any](q *Queue[Orderable, T], i int) {
|
||||
l, r := heap.Left(i), heap.Right(i)
|
||||
|
||||
var largest int
|
||||
if l < q.queueSize && q.store[l].Key > q.store[i].Key {
|
||||
largest = l
|
||||
} else {
|
||||
largest = i
|
||||
}
|
||||
if r < q.queueSize && q.store[r].Key > q.store[largest].Key {
|
||||
largest = r
|
||||
}
|
||||
|
||||
if largest != i {
|
||||
exchange(q, i, largest)
|
||||
maxHeapify(q, largest)
|
||||
}
|
||||
}
|
||||
|
||||
// minHeapify reorders into a min heap starting at the input index i
|
||||
func minHeapify[Orderable constraints.Ordered, T any](q *Queue[Orderable, T], i int) {
|
||||
l, r := heap.Left(i), heap.Right(i)
|
||||
|
||||
var smallest int
|
||||
if l < q.queueSize && q.store[l].Key < q.store[i].Key {
|
||||
smallest = l
|
||||
} else {
|
||||
smallest = i
|
||||
}
|
||||
if r < q.queueSize && q.store[r].Key < q.store[smallest].Key {
|
||||
smallest = r
|
||||
}
|
||||
|
||||
if smallest != i {
|
||||
exchange(q, i, smallest)
|
||||
minHeapify(q, smallest)
|
||||
}
|
||||
}
|
||||
|
||||
// Maximum returns the QueueObject with the highest priority
|
||||
func Maximum[Orderable constraints.Ordered, T any](q *Queue[Orderable, T]) (ret QueueObject[Orderable, T], e error) {
|
||||
if q.queueSize < 1 {
|
||||
e = fmt.Errorf("heap underflow")
|
||||
return
|
||||
}
|
||||
|
||||
ret = q.store[0]
|
||||
return
|
||||
}
|
||||
|
||||
// ExtractMaximum returns the QueueObject with the highest priority and removes it from the queue, reordering afterwards
|
||||
func ExtractMaximum[Orderable constraints.Ordered, T any](q *Queue[Orderable, T]) (ret QueueObject[Orderable, T], e error) {
|
||||
ret, e = Maximum(q)
|
||||
if e != nil {
|
||||
return
|
||||
}
|
||||
|
||||
q.store[0] = q.store[q.queueSize-1]
|
||||
q.queueSize--
|
||||
q.store = q.store[0:q.queueSize]
|
||||
|
||||
maxHeapify(q, 0)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// IncraseKey raises the priority of a given queue item and reorders the queue
|
||||
func IncreaseKey[Orderable constraints.Ordered, T any](q *Queue[Orderable, T], item QueueObject[Orderable, T], newKey Orderable) error {
|
||||
if newKey < item.Key {
|
||||
return fmt.Errorf("new key %v is smaller than current key %v", newKey, item.Key)
|
||||
}
|
||||
|
||||
var i int
|
||||
for idx, qObj := range q.store {
|
||||
if reflect.DeepEqual(qObj, item) {
|
||||
q.store[idx] = QueueObject[Orderable, T]{Key: newKey, Value: qObj.Value}
|
||||
i = idx
|
||||
}
|
||||
}
|
||||
|
||||
for q.store[heap.Parent(i)].Key < q.store[i].Key {
|
||||
exchange(q, i, heap.Parent(i))
|
||||
i = heap.Parent(i)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Insert adds a new item to the queue
|
||||
func Insert[Orderable constraints.Ordered, T any](q *Queue[Orderable, T], item QueueObject[Orderable, T]) {
|
||||
q.queueSize++
|
||||
k := item.Key
|
||||
insert := QueueObject[Orderable, T]{Value: item.Value}
|
||||
q.store = append(q.store, insert)
|
||||
IncreaseKey(q, insert, k)
|
||||
}
|
||||
|
||||
// IsMaxQueue returns true when the queue satisfies the max queue property i.e. holds a valid structure for a max queue
|
||||
func IsMaxQueue[Orderable constraints.Ordered, T any](q *Queue[Orderable, T]) bool {
|
||||
for index, val := range q.store {
|
||||
if q.store[heap.Parent(index)].Key < val.Key {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"math"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"git.wens.org.uk/heapqueue/pkg/heap"
|
||||
)
|
||||
|
||||
func TestHeap(t *testing.T) {
|
||||
input := []int{1,2,3,4,5,6,7,8,9}
|
||||
h := heap.BuildHeap(input, heap.MAXHEAP)
|
||||
if !heap.IsMaxHeap(h) {
|
||||
t.Errorf("Failed to build max heap. Heap object: %v\n", &h)
|
||||
}
|
||||
if heap.IsMinHeap(h) {
|
||||
t.Errorf("Max heap reporting as min heap. Heap object: %v\n", &h)
|
||||
}
|
||||
h = heap.BuildHeap(input, heap.MINHEAP)
|
||||
if !heap.IsMinHeap(h) {
|
||||
t.Errorf("Failed to build min heap. Heap object: %v\n", &h)
|
||||
}
|
||||
if heap.IsMaxHeap(h) {
|
||||
t.Errorf("Min heap reporting as max heap. Heap object: %v\n", &h)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSort(t *testing.T) {
|
||||
Ints := []int{1, 3, 5, 7, 9, 2, 4, 6, 8, 0}
|
||||
Floats := []float64{1.0, math.Pi, 1.00000001, 0.0, 3.9999999999, 4.0}
|
||||
Strings := []string{"zeppelin", "aardvark", "meat", "abacus", "academic", "ANTI", "自分", "かこいい"}
|
||||
IntsReverse := append([]int{}, Ints...)
|
||||
|
||||
heap.HeapSort(Ints, heap.MAXHEAP)
|
||||
heap.HeapSort(IntsReverse, heap.MINHEAP)
|
||||
heap.HeapSort(Floats, heap.MAXHEAP)
|
||||
heap.HeapSort(Strings, heap.MAXHEAP)
|
||||
|
||||
if !sort.IntsAreSorted(Ints) {
|
||||
t.Errorf("Failed to sort int slice. Order: %v\n", Ints)
|
||||
}
|
||||
if !sort.Float64sAreSorted(Floats) {
|
||||
t.Errorf("Failed to sort float64 slice. Order: %v\n", Floats)
|
||||
}
|
||||
if !sort.StringsAreSorted(Strings) {
|
||||
t.Errorf("Failed to sort strings slice. Order: %v\n", Strings)
|
||||
}
|
||||
|
||||
for i := 0; i < len(IntsReverse) - 1; i++ {
|
||||
if IntsReverse[0] < IntsReverse[i + 1] {
|
||||
t.Errorf("Failed to sort int slice in reverse order. Order: %v\n", IntsReverse)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"git.wens.org.uk/heapqueue/pkg/queue"
|
||||
)
|
||||
|
||||
func TestQueue(t *testing.T) {
|
||||
// building
|
||||
input := []queue.QueueObject[int, string]{{Key: 0, Value: "task a"}, {Key: 5, Value: "task b"}}
|
||||
q := queue.BuildQueue(input, queue.MAXQUEUE)
|
||||
if !queue.IsMaxQueue(q) {
|
||||
t.Fatalf("Failed to build max queue. Queue object: %v\n", &q)
|
||||
}
|
||||
// maximum key
|
||||
max, err := queue.Maximum(q)
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
}
|
||||
if max.Key != 5 || max.Value != "task b" {
|
||||
t.Errorf("Unexpected value of %v for max. Expected {5, \"task b\"}\n", max.Key)
|
||||
}
|
||||
// increasing key to new maximum
|
||||
queue.IncreaseKey(q, queue.QueueObject[int, string]{Key: 0, Value: "task a"}, 10)
|
||||
max, err = queue.Maximum(q)
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
}
|
||||
if max.Key != 10 || max.Value != "task a" {
|
||||
t.Errorf("Failed to increase key to 10\n")
|
||||
}
|
||||
// inserting in between
|
||||
queue.Insert(q, queue.QueueObject[int, string]{Key: 7, Value: "task c"})
|
||||
// extracting maximum
|
||||
maxExtracted, err := queue.ExtractMaximum(q)
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
}
|
||||
if !reflect.DeepEqual(max, maxExtracted) {
|
||||
t.Errorf("Extracted maximum not equal to peeked maximum, %v and %v\n", max, maxExtracted)
|
||||
}
|
||||
// check new maximum is the inserted value
|
||||
max, err = queue.ExtractMaximum(q)
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
}
|
||||
if max.Key != 7 || max.Value != "task c" {
|
||||
t.Errorf("Unexpected maximum after extraction, expected {7 \"task c\"}, got %v\n", max)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue