Code

heap.go — the skeleton

Create heap.go. The usual skeleton; the bodies are yours.

Worth noting:

  • Peek must be O(1). No loop, no comparison — a[0]. If you are searching, re-read the heap property.
  • Pop moves the last element of the array to the root, not a child. Step 2's gotcha.
  • Push compares against the parent; Pop against both children. So Pop does roughly twice the comparisons per level that Push does.
  • Swaps are not comparisons. c.Hit() fires only when two ratings are compared; swaps accumulate in Swaps. Step 6 rests on that distinction.
  • NewMinHeap takes 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.