commit 46ebc4396c26412fa272778854665bef3b8b0417 Author: george Date: Thu Jun 9 12:09:35 2022 +0100 init diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..2052d24 --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module git.wens.org.uk/heapqueue + +go 1.18 + +require golang.org/x/exp v0.0.0-20220602145555-4a0574d9293f diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..caddb82 --- /dev/null +++ b/go.sum @@ -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= diff --git a/pkg/heap/heap.go b/pkg/heap/heap.go new file mode 100644 index 0000000..f571adf --- /dev/null +++ b/pkg/heap/heap.go @@ -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 +} diff --git a/pkg/queue/queue.go b/pkg/queue/queue.go new file mode 100644 index 0000000..aa4ea5f --- /dev/null +++ b/pkg/queue/queue.go @@ -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 +} diff --git a/test/heap_test.go b/test/heap_test.go new file mode 100644 index 0000000..7c7659b --- /dev/null +++ b/test/heap_test.go @@ -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 + } + } +} diff --git a/test/queue_test.go b/test/queue_test.go new file mode 100644 index 0000000..d42aea7 --- /dev/null +++ b/test/queue_test.go @@ -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) + } +}