Skip to content

Commit

Permalink
tempfork/heap: add copy of Go's container/heap but using generics
Browse files Browse the repository at this point in the history
From Go commit 0a48e5cbfabd679e, then with some generics sprinkled
about.

Updates tailscale/corp#7354

Signed-off-by: Brad Fitzpatrick <[email protected]>
  • Loading branch information
bradfitz committed Jul 6, 2023
1 parent 0c427f2 commit cb53846
Show file tree
Hide file tree
Showing 2 changed files with 337 additions and 0 deletions.
121 changes: 121 additions & 0 deletions tempfork/heap/heap.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package heap provides heap operations for any type that implements
// heap.Interface. A heap is a tree with the property that each node is the
// minimum-valued node in its subtree.
//
// The minimum element in the tree is the root, at index 0.
//
// A heap is a common way to implement a priority queue. To build a priority
// queue, implement the Heap interface with the (negative) priority as the
// ordering for the Less method, so Push adds items while Pop removes the
// highest-priority item from the queue. The Examples include such an
// implementation; the file example_pq_test.go has the complete source.
//
// This package is a copy of the Go standard library's
// container/heap, but using generics.
package heap

import "sort"

// The Interface type describes the requirements
// for a type using the routines in this package.
// Any type that implements it may be used as a
// min-heap with the following invariants (established after
// Init has been called or if the data is empty or sorted):
//
// !h.Less(j, i) for 0 <= i < h.Len() and 2*i+1 <= j <= 2*i+2 and j < h.Len()
//
// Note that Push and Pop in this interface are for package heap's
// implementation to call. To add and remove things from the heap,
// use heap.Push and heap.Pop.
type Interface[V any] interface {
sort.Interface
Push(x V) // add x as element Len()
Pop() V // remove and return element Len() - 1.
}

// Init establishes the heap invariants required by the other routines in this package.
// Init is idempotent with respect to the heap invariants
// and may be called whenever the heap invariants may have been invalidated.
// The complexity is O(n) where n = h.Len().
func Init[V any](h Interface[V]) {
// heapify
n := h.Len()
for i := n/2 - 1; i >= 0; i-- {
down(h, i, n)
}
}

// Push pushes the element x onto the heap.
// The complexity is O(log n) where n = h.Len().
func Push[V any](h Interface[V], x V) {
h.Push(x)
up(h, h.Len()-1)
}

// Pop removes and returns the minimum element (according to Less) from the heap.
// The complexity is O(log n) where n = h.Len().
// Pop is equivalent to Remove(h, 0).
func Pop[V any](h Interface[V]) V {
n := h.Len() - 1
h.Swap(0, n)
down(h, 0, n)
return h.Pop()
}

// Remove removes and returns the element at index i from the heap.
// The complexity is O(log n) where n = h.Len().
func Remove[V any](h Interface[V], i int) V {
n := h.Len() - 1
if n != i {
h.Swap(i, n)
if !down(h, i, n) {
up(h, i)
}
}
return h.Pop()
}

// Fix re-establishes the heap ordering after the element at index i has changed its value.
// Changing the value of the element at index i and then calling Fix is equivalent to,
// but less expensive than, calling Remove(h, i) followed by a Push of the new value.
// The complexity is O(log n) where n = h.Len().
func Fix[V any](h Interface[V], i int) {
if !down(h, i, h.Len()) {
up(h, i)
}
}

func up[V any](h Interface[V], j int) {
for {
i := (j - 1) / 2 // parent
if i == j || !h.Less(j, i) {
break
}
h.Swap(i, j)
j = i
}
}

func down[V any](h Interface[V], i0, n int) bool {
i := i0
for {
j1 := 2*i + 1
if j1 >= n || j1 < 0 { // j1 < 0 after int overflow
break
}
j := j1 // left child
if j2 := j1 + 1; j2 < n && h.Less(j2, j1) {
j = j2 // = 2*i + 2 // right child
}
if !h.Less(j, i) {
break
}
h.Swap(i, j)
i = j
}
return i > i0
}
216 changes: 216 additions & 0 deletions tempfork/heap/heap_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package heap

import (
"math/rand"
"testing"

"golang.org/x/exp/constraints"
)

type myHeap[T constraints.Ordered] []T

func (h *myHeap[T]) Less(i, j int) bool {
return (*h)[i] < (*h)[j]
}

func (h *myHeap[T]) Swap(i, j int) {
(*h)[i], (*h)[j] = (*h)[j], (*h)[i]
}

func (h *myHeap[T]) Len() int {
return len(*h)
}

func (h *myHeap[T]) Pop() (v T) {
*h, v = (*h)[:h.Len()-1], (*h)[h.Len()-1]
return
}

func (h *myHeap[T]) Push(v T) {
*h = append(*h, v)
}

func (h myHeap[T]) verify(t *testing.T, i int) {
t.Helper()
n := h.Len()
j1 := 2*i + 1
j2 := 2*i + 2
if j1 < n {
if h.Less(j1, i) {
t.Errorf("heap invariant invalidated [%d] = %v > [%d] = %v", i, h[i], j1, h[j1])
return
}
h.verify(t, j1)
}
if j2 < n {
if h.Less(j2, i) {
t.Errorf("heap invariant invalidated [%d] = %v > [%d] = %v", i, h[i], j1, h[j2])
return
}
h.verify(t, j2)
}
}

func TestInit0(t *testing.T) {
h := new(myHeap[int])
for i := 20; i > 0; i-- {
h.Push(0) // all elements are the same
}
Init[int](h)
h.verify(t, 0)

for i := 1; h.Len() > 0; i++ {
x := Pop[int](h)
h.verify(t, 0)
if x != 0 {
t.Errorf("%d.th pop got %d; want %d", i, x, 0)
}
}
}

func TestInit1(t *testing.T) {
h := new(myHeap[int])
for i := 20; i > 0; i-- {
h.Push(i) // all elements are different
}
Init[int](h)
h.verify(t, 0)

for i := 1; h.Len() > 0; i++ {
x := Pop[int](h)
h.verify(t, 0)
if x != i {
t.Errorf("%d.th pop got %d; want %d", i, x, i)
}
}
}

func Test(t *testing.T) {
h := new(myHeap[int])
h.verify(t, 0)

for i := 20; i > 10; i-- {
h.Push(i)
}
Init[int](h)
h.verify(t, 0)

for i := 10; i > 0; i-- {
Push[int](h, i)
h.verify(t, 0)
}

for i := 1; h.Len() > 0; i++ {
x := Pop[int](h)
if i < 20 {
Push[int](h, 20+i)
}
h.verify(t, 0)
if x != i {
t.Errorf("%d.th pop got %d; want %d", i, x, i)
}
}
}

func TestRemove0(t *testing.T) {
h := new(myHeap[int])
for i := 0; i < 10; i++ {
h.Push(i)
}
h.verify(t, 0)

for h.Len() > 0 {
i := h.Len() - 1
x := Remove[int](h, i)
if x != i {
t.Errorf("Remove(%d) got %d; want %d", i, x, i)
}
h.verify(t, 0)
}
}

func TestRemove1(t *testing.T) {
h := new(myHeap[int])
for i := 0; i < 10; i++ {
h.Push(i)
}
h.verify(t, 0)

for i := 0; h.Len() > 0; i++ {
x := Remove[int](h, 0)
if x != i {
t.Errorf("Remove(0) got %d; want %d", x, i)
}
h.verify(t, 0)
}
}

func TestRemove2(t *testing.T) {
N := 10

h := new(myHeap[int])
for i := 0; i < N; i++ {
h.Push(i)
}
h.verify(t, 0)

m := make(map[int]bool)
for h.Len() > 0 {
m[Remove[int](h, (h.Len()-1)/2)] = true
h.verify(t, 0)
}

if len(m) != N {
t.Errorf("len(m) = %d; want %d", len(m), N)
}
for i := 0; i < len(m); i++ {
if !m[i] {
t.Errorf("m[%d] doesn't exist", i)
}
}
}

func BenchmarkDup(b *testing.B) {
const n = 10000
h := make(myHeap[int], 0, n)
for i := 0; i < b.N; i++ {
for j := 0; j < n; j++ {
Push[int](&h, 0) // all elements are the same
}
for h.Len() > 0 {
Pop[int](&h)
}
}
}

func TestFix(t *testing.T) {
h := new(myHeap[int])
h.verify(t, 0)

for i := 200; i > 0; i -= 10 {
Push[int](h, i)
}
h.verify(t, 0)

if (*h)[0] != 10 {
t.Fatalf("Expected head to be 10, was %d", (*h)[0])
}
(*h)[0] = 210
Fix[int](h, 0)
h.verify(t, 0)

for i := 100; i > 0; i-- {
elem := rand.Intn(h.Len())
if i&1 == 0 {
(*h)[elem] *= 2
} else {
(*h)[elem] /= 2
}
Fix[int](h, elem)
h.verify(t, 0)
}
}

0 comments on commit cb53846

Please sign in to comment.