Code
heap.go — the skeleton
Create heap.go. The usual skeleton; the bodies are yours.
Worth noting:
Peekmust beO(1). No loop, no comparison —a[0]. If you are searching, re-read the heap property.Popmoves the last element of the array to the root, not a child. Step 2's gotcha.Pushcompares against the parent;Popagainst both children. SoPopdoes roughly twice the comparisons per level thatPushdoes.- Swaps are not comparisons.
c.Hit()fires only when two ratings are compared; swaps accumulate inSwaps. Step 6 rests on that distinction. NewMinHeaptakes a capacity hint and must use it (make([]Item, 0, capacity)). That is what makes the whole structure one allocation — the number step 6 sets against 20,020.
TopKHeap — where the thinking is
TopKHeap uses a min-heap of the k best items, and that is the whole trick.
The heap holds the k best seen so far. Its minimum is the weakest of those k. A new record only has to beat that one:
if the heap holds fewer than k: Push and continue
worst ← Peek()
c.Hit()
if the new one ≤ worst: skip // cannot displace the weakest of the k best
Pop(); Push(the new one)
For most records that is one comparison and done. Which is why the cost is
O(n log k) rather than O(n log n).
A min-heap for "the best" looks inverted, and it should: you keep the best and watch the worst of them, because that is the one that leaves first.
At the end the heap is drained and the result reversed — Pop gives the smallest
first, and you want the best first.