heap.gno
3.83 Kb · 144 lines
1// Package heap is a binary min-heap / priority queue over string items with
2// integer priorities, as a pure, reusable package.
3//
4// Go's container/heap makes the caller implement five methods and hands back an
5// interface; that indirection buys generality this chain does not need and
6// costs gas it does. This is the concrete data structure instead: an implicit
7// binary heap in a slice, Push/Pop in O(log n), Peek in O(1).
8//
9// Ordering is TOTAL and deterministic. Equal priorities are broken by insertion
10// sequence, so two heaps fed the same items in the same order always pop the
11// same sequence — a heap that reordered ties by allocation address would make a
12// Render vary between nodes, which is a consensus bug rather than a cosmetic
13// one.
14//
15// A live demo of this package is at
16// [r/moul/x/daily/heapdemo](/r/moul/x/daily/heapdemo/v0).
17package heap
18
19// MaxItems bounds the heap so gas stays predictable.
20const MaxItems = 4096
21
22type item struct {
23 value string
24 priority int
25 seq int // insertion sequence, breaks priority ties
26}
27
28// Heap is a binary min-heap: the lowest priority pops first.
29type Heap struct {
30 items []item
31 next int // monotonic insertion counter
32 max bool
33}
34
35// New returns an empty min-heap (lowest priority pops first).
36func New() *Heap { return &Heap{} }
37
38// NewMax returns an empty max-heap (highest priority pops first). Ties are
39// still broken by insertion order, oldest first.
40func NewMax() *Heap { return &Heap{max: true} }
41
42// Len returns the number of items.
43func (h *Heap) Len() int { return len(h.items) }
44
45// IsEmpty reports whether the heap holds nothing.
46func (h *Heap) IsEmpty() bool { return len(h.items) == 0 }
47
48// IsMax reports whether this is a max-heap.
49func (h *Heap) IsMax() bool { return h.max }
50
51// less reports whether a should pop before b.
52func (h *Heap) less(a, b item) bool {
53 if a.priority != b.priority {
54 if h.max {
55 return a.priority > b.priority
56 }
57 return a.priority < b.priority
58 }
59 // Total order: equal priorities pop oldest-first, in BOTH heap kinds.
60 return a.seq < b.seq
61}
62
63// Push adds value with the given priority. Returns false when the heap is full.
64func (h *Heap) Push(value string, priority int) bool {
65 if len(h.items) >= MaxItems {
66 return false
67 }
68 h.items = append(h.items, item{value: value, priority: priority, seq: h.next})
69 h.next++
70 h.up(len(h.items) - 1)
71 return true
72}
73
74// Peek returns the item that would pop next, without removing it.
75func (h *Heap) Peek() (value string, priority int, ok bool) {
76 if len(h.items) == 0 {
77 return "", 0, false
78 }
79 return h.items[0].value, h.items[0].priority, true
80}
81
82// Pop removes and returns the next item.
83func (h *Heap) Pop() (value string, priority int, ok bool) {
84 if len(h.items) == 0 {
85 return "", 0, false
86 }
87 top := h.items[0]
88 last := len(h.items) - 1
89 h.items[0] = h.items[last]
90 h.items = h.items[:last]
91 if len(h.items) > 0 {
92 h.down(0)
93 }
94 return top.value, top.priority, true
95}
96
97// Drain pops everything, returning values in pop order. The heap ends empty.
98func (h *Heap) Drain() []string {
99 out := make([]string, 0, len(h.items))
100 for {
101 v, _, ok := h.Pop()
102 if !ok {
103 return out
104 }
105 out = append(out, v)
106 }
107}
108
109// Clone returns an independent copy.
110func (h *Heap) Clone() *Heap {
111 cp := &Heap{items: make([]item, len(h.items)), next: h.next, max: h.max}
112 copy(cp.items, h.items)
113 return cp
114}
115
116func (h *Heap) up(i int) {
117 for i > 0 {
118 parent := (i - 1) / 2
119 if !h.less(h.items[i], h.items[parent]) {
120 return
121 }
122 h.items[i], h.items[parent] = h.items[parent], h.items[i]
123 i = parent
124 }
125}
126
127func (h *Heap) down(i int) {
128 n := len(h.items)
129 for {
130 left := 2*i + 1
131 if left >= n {
132 return
133 }
134 best := left
135 if right := left + 1; right < n && h.less(h.items[right], h.items[left]) {
136 best = right
137 }
138 if !h.less(h.items[best], h.items[i]) {
139 return
140 }
141 h.items[i], h.items[best] = h.items[best], h.items[i]
142 i = best
143 }
144}