Go

How do you implement a priority queue with container/heap? Why must Push and Pop use pointer receivers, and what does heap.Fix do?

Question 532MediumGo 1.22 to 1.25

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

All 16 More Standard Library Essentials questions