|
import { MinHeap } from './min-heap'; |
|
|
|
/** |
|
* Maintains the top K largest items. |
|
*/ |
|
export class TopK<T> { |
|
private capacity: number; |
|
private heap: MinHeap<T>; |
|
|
|
/** |
|
* @param k The maximum number of items to store |
|
* @param comparator a function comparator(a, b) that |
|
* returns < 0 if a < b, |
|
* = 0 if a == b, |
|
* > 0 if a > b. |
|
* |
|
* By default, we want a min-heap, so if a < b => comparator(a, b) < 0 |
|
* which means 'a' should be on top in a min-heap scenario. |
|
* But for top-K largest items, we store them in a min-heap, |
|
* so the smallest is always at the root => easy to pop out the smallest if needed. |
|
*/ |
|
constructor(k: number, comparator: (a: T, b: T) => number) { |
|
this.capacity = k; |
|
// MinHeap that orders items by 'comparator' |
|
this.heap = new MinHeap(comparator); |
|
} |
|
|
|
/** |
|
* Potentially add a new item to the top-K structure. |
|
*/ |
|
public add(item: T): void { |
|
// If we still have room, just push |
|
if (this.heap.size() < this.capacity) { |
|
this.heap.push(item); |
|
return; |
|
} |
|
|
|
// If the new item is bigger than the min of the heap => replace |
|
const min = this.heap.peek(); |
|
if (min !== undefined && this.heapComparator(item, min) > 0) { |
|
// pop the smallest |
|
this.heap.pop(); |
|
this.heap.push(item); |
|
} |
|
} |
|
|
|
/** |
|
* Return the current count of items in the top-K. |
|
*/ |
|
public size(): number { |
|
return this.heap.size(); |
|
} |
|
|
|
/** |
|
* Returns a sorted array of the top-K items, largest to smallest. |
|
* |
|
* NOTE: This is an O(K log K) operation, because we need to pop everything |
|
* from the heap if we want them strictly sorted. |
|
* Alternatively, we could just return the internal data unsorted |
|
* if an approximate top-K is enough for your usage. |
|
*/ |
|
public getTopK(): T[] { |
|
// Copy the heap data, then pop to retrieve items in ascending order |
|
// (since it's a min-heap). |
|
const items: T[] = []; |
|
const temp: T[] = []; |
|
|
|
// We'll clone the heap data (shallow) so we don't destroy the original |
|
const cloneData = [...(this as any).heap.data]; |
|
const clone = new MinHeap<T>((a, b) => this.heapComparator(a, b)); |
|
cloneData.forEach((el) => clone.push(el)); |
|
|
|
// Now pop everything out in ascending order, |
|
// and build 'items' array from largest to smallest |
|
while (clone.size() > 0) { |
|
temp.push(clone.pop() as T); |
|
} |
|
// 'temp' is sorted ascending, so we reverse for largest -> smallest |
|
temp.reverse(); |
|
items.push(...temp); |
|
return items; |
|
} |
|
|
|
/** |
|
* A private utility to re-use the comparator in a simpler way: |
|
* returns negative if a < b, zero if equal, positive if a > b. |
|
*/ |
|
private heapComparator(a: T, b: T) { |
|
return this.heap['comparator'](a, b); |
|
} |
|
} |