How do you implement a priority queue with container/heap? Why must Push and Pop use pointer receivers, and what does heap.Fix do?
You implement heap.Interface (which embeds sort.Interface, so Len, Less and Swap, plus Push(any) and Pop() any). Then you call the package functions heap.Init, heap.Push, heap.Pop and heap.Fix, never your own methods directly. Push and Pop change the slice length, so they need pointer receivers. With a value receiver, append or reslicing would only change a copy of the slice header. heap.Pop first swaps the root to the end and sifts down, then calls your Pop, which only removes the last element.
type Item struct {
value string
priority int
index int // kept up to date by Swap; heap.Fix needs it
}
type PQ []*Item
func (pq PQ) Len() int { return len(pq) }
func (pq PQ) Less(i, j int) bool { return pq[i].priority > pq[j].priority } // max-heap
func (pq PQ) Swap(i, j int) {
pq[i], pq[j] = pq[j], pq[i]
pq[i].index = i
pq[j].index = j
}
func (pq *PQ) Push(x any) {
it := x.(*Item)
it.index = len(*pq)
*pq = append(*pq, it)
}
func (pq *PQ) Pop() any {
old := *pq
n := len(old)
it := old[n-1]
old[n-1] = nil // let the GC reclaim it
it.index = -1
*pq = old[:n-1]
return it
}
func (pq *PQ) update(it *Item, p int) {
it.priority = p
heap.Fix(pq, it.index) // O(log n) re-sift instead of Remove+Push
}
func main() {
pq := &PQ{}
a := &Item{value: "a", priority: 1}
heap.Push(pq, a)
heap.Push(pq, &Item{value: "b", priority: 5})
pq.update(a, 10)
for pq.Len() > 0 {
it := heap.Pop(pq).(*Item)
fmt.Println(it.value, it.priority) // a 10, then b 5
}
}
heap.Fix(h, i) restores the heap order after the element at index i changed its priority. It sifts up or down in O(log n). Gotchas: calling pq.Push instead of heap.Push skips the sift and silently breaks the ordering. Also, the heap is not safe for concurrent use.
More on More Standard Library Essentials
- Q533Compare sort.Slice, sort.SliceStable, sort.Sort (sort.Interface) and slices.SortFunc. Which are stable, and what are the performance differences?
- Q534How does Go's time layout work (the reference time Mon Jan 2 15:04:05 MST 2006)? What bugs come from using "2006-01-02" vs "YYYY-MM-DD", time zones and time.Parse vs time.ParseInLocation?
- Q535Compare math/rand, math/rand/v2 and crypto/rand. What changed about seeding in Go 1.20 and 1.22, and when must you use crypto/rand?
- Q536text/template vs html/template: how does contextual auto-escaping prevent XSS, and what does template.HTML bypass?
- Q537How do you prevent SQL injection with database/sql? Why can't placeholders be used for table names or ORDER BY columns?
- Q538Why should you compare secrets with crypto/subtle.ConstantTimeCompare instead of ==? How do you hash passwords in Go?