Various improvements.

* Expand README.md, provide benchmark results.
* Add docs, benchmarks for binheap and circ packages.
* Add methods Len() and Capacity().
* Change *sync.Cond to sync.Cond.
* TryRecv() and TrySend() distinguish empty and closed errors.
* Improve test coverage.
* Add basic Makefile.
* Fix documentation mistakes.
This commit is contained in:
2023-03-02 21:35:17 -08:00
parent b3b491d9a9
commit b00fe25128
12 changed files with 313 additions and 104 deletions

View File

@@ -1,103 +1,115 @@
// Package binheap implements a binary max-heap.
//
// # Implementation
//
// [H] is parameterized over two types, one for the priority levels, one for
// the elements. Internally, there are two equally-sized buffers for these
// types. Re-heaping operations swap corresponding entries in these buffers
// in lock-step.
package binheap
import "golang.org/x/exp/constraints"
// H is a binary max-heap.
//
// `I` is the type of the priority IDs, and `E` the type of the elements.
type H[I constraints.Ordered, E any] struct {
heap []I
elems []E
len int
// `P` is the type of the priority levels, and `E` the type of the elements.
type H[P constraints.Ordered, E any] struct {
prs []P
els []E
len int
}
// Make creates a new heap.
func Make[I constraints.Ordered, E any](cap int) H[I, E] {
heap := make([]I, cap)
elems := make([]E, cap)
h := H[I, E]{heap: heap, elems: elems}
func Make[P constraints.Ordered, E any](cap int) H[P, E] {
priorities := make([]P, cap)
elements := make([]E, cap)
h := H[P, E]{prs: priorities, els: elements}
return h
}
// Capacity returns the total capacity of the heap.
func (h *H[I, E]) Capacity() int {
return cap(h.heap)
func (h *H[P, E]) Capacity() int {
return cap(h.prs)
}
// Len returns the number of items in the heap.
func (h *H[I, E]) Len() int {
func (h *H[P, E]) Len() int {
return h.len
}
// CanExtract returns true if the heap has any item, otherwise false.
func (h *H[I, E]) CanExtract() bool {
func (h *H[P, E]) CanExtract() bool {
return h.len != 0
}
// CanInsert returns true if the heap has unused capacity, otherwise false.
func (h *H[I, E]) CanInsert() bool {
return cap(h.heap)-h.len != 0
func (h *H[P, E]) CanInsert() bool {
return cap(h.prs)-h.len != 0
}
// Extract returns the current heap root, then performs a heap-down pass.
//
// If the heap is empty, it panics.
func (h *H[I, E]) Extract() (I, E) {
func (h *H[P, E]) Extract() (P, E) {
if !h.CanExtract() {
panic("heap is empty")
}
id := h.heap[0]
elem := h.elems[0]
var emptyId I
// extract root
priority := h.prs[0]
element := h.els[0]
// move last entry to root position
h.prs[0] = h.prs[h.len-1]
h.els[0] = h.els[h.len-1]
// clear the former last entry position,
// so as not to hold onto garbage
var emptyPriority P
var emptyElem E
h.heap[0] = h.heap[h.len-1]
h.elems[0] = h.elems[h.len-1]
h.heap[h.len-1] = emptyId
h.elems[h.len-1] = emptyElem
h.prs[h.len-1] = emptyPriority
h.els[h.len-1] = emptyElem
// heap-down
h.len--
idx := 0
for {
left := idx*2 + 1
right := idx*2 + 2
left := idx<<1 + 1
right := idx<<1 + 2
largest := idx
if left < h.len && h.heap[left] > h.heap[largest] {
if left < h.len && h.prs[left] > h.prs[largest] {
largest = left
}
if right < h.len && h.heap[right] > h.heap[largest] {
if right < h.len && h.prs[right] > h.prs[largest] {
largest = right
}
if largest == idx {
break
}
h.heap[idx], h.heap[largest] = h.heap[largest], h.heap[idx]
h.elems[idx], h.elems[largest] = h.elems[largest], h.elems[idx]
h.prs[idx], h.prs[largest] = h.prs[largest], h.prs[idx]
h.els[idx], h.els[largest] = h.els[largest], h.els[idx]
idx = largest
}
return id, elem
return priority, element
}
// Insert adds an item to the heap, then performs a heap-up pass.
//
// If the heap is full, it panics.
func (h *H[I, E]) Insert(id I, elem E) {
func (h *H[P, E]) Insert(priority P, elem E) {
if !h.CanInsert() {
panic("heap is full")
}
// insert new item into last position
idx := h.len
h.heap[idx] = id
h.elems[idx] = elem
h.prs[idx] = priority
h.els[idx] = elem
// heap-up
h.len++
for {
parent := (idx - 1) / 2
if parent == idx || h.heap[parent] >= h.heap[idx] {
parent := (idx - 1) >> 1
if parent < 0 || h.prs[parent] >= h.prs[idx] {
break
}
h.heap[parent], h.heap[idx] = h.heap[idx], h.heap[parent]
h.elems[parent], h.elems[idx] = h.elems[idx], h.elems[parent]
h.prs[parent], h.prs[idx] = h.prs[idx], h.prs[parent]
h.els[parent], h.els[idx] = h.els[idx], h.els[parent]
idx = parent
}
}

View File

@@ -82,3 +82,50 @@ func TestRandomized(t *testing.T) {
}
}
}
func BenchmarkInsert(b *testing.B) {
h := binheap.Make[int, int](b.N)
rs := rand.NewSource(0)
r := rand.New(rs)
items := make([]int, b.N)
for i := 0; i < b.N; i++ {
items[i] = r.Int()
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
h.Insert(items[i], items[i])
}
}
func BenchmarkExtract(b *testing.B) {
h := binheap.Make[int, int](b.N)
rs := rand.NewSource(0)
r := rand.New(rs)
for i := 0; i < b.N; i++ {
n := r.Int()
h.Insert(n, n)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
h.Extract()
}
}
func BenchmarkRepeatedInsertExtract(b *testing.B) {
h := binheap.Make[int, int](128)
rs := rand.NewSource(0)
r := rand.New(rs)
items := make([]int, b.N)
for i := 0; i < h.Capacity()-1; i++ {
n := r.Int()
h.Insert(n, n)
}
for i := 0; i < b.N; i++ {
items[i] = r.Int()
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
h.Insert(items[i], items[i])
h.Extract()
}
}