Skip to content

Commit 50c87f5

Browse files
committed
Add Heap container
1 parent 6762169 commit 50c87f5

File tree

4 files changed

+194
-0
lines changed

4 files changed

+194
-0
lines changed

cmp/cmp.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package cmp
2+
3+
// Comparable is an interface for types that can be compared.
4+
type Comparable[T any] interface {
5+
Compare(*T) int
6+
*T
7+
}

container/heap/heap.go

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
package heap
2+
3+
import (
4+
"container/heap"
5+
)
6+
7+
// This file is copied from https://go.dev/play/p/350YF5j23xY
8+
// related to issue https://github.com/golang/go/issues/47632
9+
// After the issue is fixed, this file can be removed.
10+
11+
// A Heap is a min-heap backed by a slice.
12+
type Heap[E any] struct {
13+
s sliceHeap[E]
14+
}
15+
16+
// New constructs a new Heap with a comparison function.
17+
func New[E any](cmp func(E, E) int) *Heap[E] {
18+
return &Heap[E]{sliceHeap[E]{cmp: cmp}}
19+
}
20+
21+
// Push pushes an element onto the heap. The complexity is O(log n)
22+
// where n = h.Len().
23+
func (h *Heap[E]) Push(elem E) {
24+
heap.Push(&h.s, elem)
25+
}
26+
27+
// Pop removes and returns the minimum element (according to the cmp function)
28+
// from the heap. Pop panics if the heap is empty.
29+
// The complexity is O(log n) where n = h.Len().
30+
func (h *Heap[E]) Pop() E {
31+
return heap.Pop(&h.s).(E)
32+
}
33+
34+
// Peek returns the minimum element (according to the cmp function) in the heap.
35+
// Peek panics if the heap is empty.
36+
// The complexity is O(1).
37+
func (h *Heap[E]) Peek() E {
38+
return h.s.s[0]
39+
}
40+
41+
// Len returns the number of elements in the heap.
42+
func (h *Heap[E]) Len() int {
43+
return len(h.s.s)
44+
}
45+
46+
// Empty returns true if the heap is empty.
47+
func (h *Heap[E]) Empty() bool {
48+
return len(h.s.s) == 0
49+
}
50+
51+
// Slice returns the underlying slice.
52+
// The slice is in heap order; the minimum value is at index 0.
53+
// The heap retains the returned slice, so altering the slice may break
54+
// the invariants and invalidate the heap.
55+
func (h *Heap[E]) Slice() []E {
56+
return h.s.s
57+
}
58+
59+
// SetIndex specifies an optional function to be called
60+
// when updating the position of any heap element within the slice,
61+
// including during the element's initial Push.
62+
//
63+
// SetIndex must be called at most once, before any calls to Push.
64+
//
65+
// When an element is removed from the heap by Pop or Remove,
66+
// the index function is called with the invalid index -1
67+
// to signify that the element is no longer within the slice.
68+
func (h *Heap[E]) SetIndex(f func(E, int)) {
69+
h.s.setIndex = f
70+
}
71+
72+
// Fix re-establishes the heap ordering
73+
// after the element at index i has changed its value.
74+
// Changing the value of the element at index i and then calling Fix
75+
// is equivalent to, but less expensive than,
76+
// calling h.Remove(i) followed by a Push of the new value.
77+
// The complexity is O(log n) where n = h.Len().
78+
// The index for use with Fix is recorded using the function passed to SetIndex.
79+
func (h *Heap[E]) Fix(i int) {
80+
heap.Fix(&h.s, i)
81+
}
82+
83+
// Remove removes and returns the element at index i from the heap.
84+
// The complexity is O(log n) where n = h.Len().
85+
// The index for use with Remove is recorded using the function passed to SetIndex.
86+
func (h *Heap[E]) Remove(i int) E {
87+
return heap.Remove(&h.s, i).(E)
88+
}
89+
90+
// sliceHeap just exists to use the existing heap.Interface as the
91+
// implementation of Heap.
92+
type sliceHeap[E any] struct {
93+
s []E
94+
cmp func(E, E) int
95+
setIndex func(E, int)
96+
}
97+
98+
func (s *sliceHeap[E]) Len() int { return len(s.s) }
99+
100+
func (s *sliceHeap[E]) Swap(i, j int) {
101+
s.s[i], s.s[j] = s.s[j], s.s[i]
102+
if s.setIndex != nil {
103+
s.setIndex(s.s[i], i)
104+
s.setIndex(s.s[j], j)
105+
}
106+
}
107+
108+
func (s *sliceHeap[E]) Less(i, j int) bool {
109+
return s.cmp(s.s[i], s.s[j]) < 0
110+
}
111+
112+
func (s *sliceHeap[E]) Push(x interface{}) {
113+
s.s = append(s.s, x.(E))
114+
if s.setIndex != nil {
115+
s.setIndex(s.s[len(s.s)-1], len(s.s)-1)
116+
}
117+
}
118+
119+
func (s *sliceHeap[E]) Pop() interface{} {
120+
e := s.s[len(s.s)-1]
121+
if s.setIndex != nil {
122+
s.setIndex(e, -1)
123+
}
124+
s.s = s.s[:len(s.s)-1]
125+
return e
126+
}

container/heap/heap_cmp.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package heap
2+
3+
import (
4+
"cmp"
5+
6+
xcmp "github.com/gravitton/x/cmp"
7+
)
8+
9+
// NewOrdered constructs a new Heap with a ordered elements
10+
func NewOrdered[E cmp.Ordered]() *Heap[E] {
11+
return &Heap[E]{sliceHeap[E]{cmp: cmp.Compare[E]}}
12+
}
13+
14+
// NewComparable constructs a new Heap with a comparable elements
15+
func NewComparable[T any, E xcmp.Comparable[T]]() *Heap[E] {
16+
return &Heap[E]{sliceHeap[E]{cmp: func(x E, y E) int {
17+
return x.Compare(y)
18+
}}}
19+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package heap
2+
3+
import (
4+
"cmp"
5+
"fmt"
6+
)
7+
8+
type priorityItem struct {
9+
value string
10+
priority int
11+
index int
12+
}
13+
14+
func (i *priorityItem) Compare(item *priorityItem) int {
15+
return cmp.Compare(i.priority, item.priority)
16+
}
17+
18+
func (i *priorityItem) setIndex(index int) {
19+
i.index = index
20+
}
21+
22+
func ExampleHeap_priorityQueue() {
23+
pq := NewComparable[priorityItem]()
24+
pq.SetIndex((*priorityItem).setIndex)
25+
26+
pq.Push(&priorityItem{value: "banana", priority: 3})
27+
pq.Push(&priorityItem{value: "apple", priority: 2})
28+
pq.Push(&priorityItem{value: "pear", priority: 4})
29+
30+
orange := &priorityItem{value: "orange", priority: 1}
31+
pq.Push(orange)
32+
orange.priority = 5
33+
pq.Fix(orange.index)
34+
35+
for pq.Len() > 0 {
36+
item := pq.Pop()
37+
fmt.Printf("%.2d:%s ", item.priority, item.value)
38+
}
39+
40+
// Output:
41+
// 02:apple 03:banana 04:pear 05:orange
42+
}

0 commit comments

Comments
 (0)