Skip to content

Commit e1f177e

Browse files
committed
Restructure of the Heap class
1 parent 42e2dec commit e1f177e

File tree

4 files changed

+85
-71
lines changed

4 files changed

+85
-71
lines changed

app/lib/search/heap.dart

Lines changed: 59 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,35 @@
88
/// than or equal to its children's values.
99
///
1010
/// The provided comparator decides which kind of heap is being built.
11-
class _Heap<T> {
11+
class Heap<T> {
1212
final Comparator<T> _compare;
1313
final _items = <T>[];
14+
bool _isValid = true;
1415

15-
_Heap(this._compare);
16+
Heap(this._compare);
1617

1718
int get length => _items.length;
1819

19-
void _pushDown(int index) {
20+
/// Collects [item] and adds it to the end of the internal list, marks the [Heap]
21+
/// as non-valid.
22+
///
23+
/// A separate operation may trigger the restoration of the heap proprerty.
24+
void collect(T item) {
25+
_items.add(item);
26+
_isValid = false;
27+
}
28+
29+
/// Collects [items] and adds them the end of the internal list, marks the [Heap]
30+
/// as non-valid.
31+
///
32+
/// A separate operation may trigger the restoration of the heap proprerty.
33+
void collectAll(Iterable<T> items) {
34+
_items.addAll(items);
35+
_isValid = false;
36+
}
37+
38+
/// Ensures that the tree structre below the [index] is a valid heap.
39+
void _heapify(int index) {
2040
final maxLength = _items.length;
2141
final item = _items[index];
2242
while (index < maxLength) {
@@ -39,60 +59,54 @@ class _Heap<T> {
3959
}
4060
}
4161

62+
/// (Re-)builds the heap property if needed.
63+
void _buildHeapIfNeeded() {
64+
if (_isValid) {
65+
assert(_isValidHeap());
66+
return;
67+
}
68+
69+
if (_items.isEmpty) {
70+
_isValid = true;
71+
return;
72+
}
73+
for (var i = (_items.length >> 1); i >= 0; i--) {
74+
_heapify(i);
75+
}
76+
77+
assert(_isValidHeap());
78+
_isValid = true;
79+
}
80+
81+
/// Verifies the heap property is true for all items.
4282
bool _isValidHeap() {
4383
for (var i = 1; i < _items.length; i++) {
4484
final parentIndex = (i - 1) >> 1;
4585
if (_compare(_items[parentIndex], _items[i]) > 0) {
46-
print(parentIndex);
47-
print(_items);
4886
return false;
4987
}
5088
}
5189
return true;
5290
}
53-
}
54-
55-
/// Builds a sorted list of the top-k items using the provided comparator.
56-
///
57-
/// The algorithm collects all items, builds a max-heap in O(N) steps, and
58-
/// then selects the top-k items by removing the largest item from the heap
59-
/// and restoring the heap property again in O(k * log(N)) steps.
60-
class TopKSortedListBuilder<T> {
61-
final int _k;
62-
final _Heap<T> _heap;
63-
64-
TopKSortedListBuilder(this._k, Comparator<T> compare)
65-
: _heap = _Heap<T>(compare);
66-
67-
void addAll(Iterable<T> items) {
68-
for (final item in items) {
69-
add(item);
70-
}
71-
}
72-
73-
void add(T item) {
74-
_heap._items.add(item);
75-
}
7691

77-
/// Gets and removes the top-k items from the current list.
78-
Iterable<T> getTopK() sync* {
79-
if (_heap._items.isEmpty) {
80-
return;
81-
}
82-
for (var i = (_heap._items.length >> 1); i >= 0; i--) {
83-
_heap._pushDown(i);
84-
}
85-
assert(_heap._isValidHeap());
86-
var count = _k;
87-
while (count > 0 && _heap._items.isNotEmpty) {
88-
yield _heap._items[0];
89-
count--;
90-
final last = _heap._items.removeLast();
91-
if (_heap._items.isEmpty) {
92+
/// Creates a sorted list of the top-k items and removes them from the [Heap].
93+
///
94+
/// The algorithm builds a max-heap in `O(N)` steps on the already collected items,
95+
/// and then selects the top-k items by removing the largest item from the [Heap]
96+
/// and restoring the heap property again in `O(k * log(N))` steps.
97+
Iterable<T> getAndRemoveTopK(int k) sync* {
98+
_buildHeapIfNeeded();
99+
var remaining = k;
100+
while (remaining > 0 && _items.isNotEmpty) {
101+
yield _items[0];
102+
remaining--;
103+
final last = _items.removeLast();
104+
if (_items.isEmpty) {
92105
break;
93106
}
94-
_heap._items[0] = last;
95-
_heap._pushDown(0);
107+
_items[0] = last;
108+
_heapify(0);
96109
}
110+
assert(_isValidHeap());
97111
}
98112
}

app/lib/search/mem_index.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -528,7 +528,7 @@ class InMemoryPackageIndex {
528528
/// Return (and sort) only the top-k results.
529529
required int topK,
530530
}) {
531-
final builder = TopKSortedListBuilder<int>(topK, (aIndex, bIndex) {
531+
final heap = Heap<int>((aIndex, bIndex) {
532532
if (aIndex == bestNameIndex) return -1;
533533
if (bIndex == bestNameIndex) return 1;
534534
final aScore = score.getValue(aIndex);
@@ -541,9 +541,9 @@ class InMemoryPackageIndex {
541541
for (var i = 0; i < score.length; i++) {
542542
final value = score.getValue(i);
543543
if (value <= 0.0 && i != bestNameIndex) continue;
544-
builder.add(i);
544+
heap.collect(i);
545545
}
546-
return builder.getTopK().map((i) => IndexedPackageHit(
546+
return heap.getAndRemoveTopK(topK).map((i) => IndexedPackageHit(
547547
i, PackageHit(package: score.keys[i], score: score.getValue(i))));
548548
}
549549

app/lib/search/token_index.dart

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -315,15 +315,15 @@ class IndexedScore<K> {
315315

316316
Map<K, double> top(int count, {double? minValue}) {
317317
minValue ??= 0.0;
318-
final builder = TopKSortedListBuilder<int>(
319-
count, (a, b) => -_values[a].compareTo(_values[b]));
318+
final heap = Heap<int>((a, b) => -_values[a].compareTo(_values[b]));
320319
for (var i = 0; i < length; i++) {
321320
final v = _values[i];
322321
if (v < minValue) continue;
323-
builder.add(i);
322+
heap.collect(i);
324323
}
325-
return Map.fromEntries(
326-
builder.getTopK().map((i) => MapEntry(_keys[i], _values[i])));
324+
return Map.fromEntries(heap
325+
.getAndRemoveTopK(count)
326+
.map((i) => MapEntry(_keys[i], _values[i])));
327327
}
328328

329329
Map<K, double> toMap() {

app/test/search/heap_test.dart

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,32 +12,32 @@ void main() {
1212
int compare(int a, int b) => -a.compareTo(b);
1313

1414
test('no items', () {
15-
final builder = TopKSortedListBuilder(5, compare);
16-
expect(builder.getTopK().toList(), []);
15+
final heap = Heap(compare);
16+
expect(heap.getAndRemoveTopK(5).toList(), []);
1717
});
1818

1919
test('single item', () {
20-
final builder = TopKSortedListBuilder(5, compare);
21-
builder.add(1);
22-
expect(builder.getTopK().toList(), [1]);
20+
final heap = Heap(compare);
21+
heap.collect(1);
22+
expect(heap.getAndRemoveTopK(5).toList(), [1]);
2323
});
2424

2525
test('three items ascending', () {
26-
final builder = TopKSortedListBuilder(5, compare);
27-
builder.addAll([1, 2, 3]);
28-
expect(builder.getTopK().toList(), [3, 2, 1]);
26+
final builder = Heap(compare);
27+
builder.collectAll([1, 2, 3]);
28+
expect(builder.getAndRemoveTopK(5).toList(), [3, 2, 1]);
2929
});
3030

3131
test('three items descending', () {
32-
final builder = TopKSortedListBuilder(5, compare);
33-
builder.addAll([3, 2, 1]);
34-
expect(builder.getTopK().toList(), [3, 2, 1]);
32+
final heap = Heap(compare);
33+
heap.collectAll([3, 2, 1]);
34+
expect(heap.getAndRemoveTopK(5).toList(), [3, 2, 1]);
3535
});
3636

3737
test('10 items + repeated', () {
38-
final builder = TopKSortedListBuilder(5, compare);
39-
builder.addAll([1, 10, 2, 9, 3, 8, 4, 7, 6, 5, 9]);
40-
expect(builder.getTopK().toList(), [10, 9, 9, 8, 7]);
38+
final heap = Heap(compare);
39+
heap.collectAll([1, 10, 2, 9, 3, 8, 4, 7, 6, 5, 9]);
40+
expect(heap.getAndRemoveTopK(5).toList(), [10, 9, 9, 8, 7]);
4141
});
4242

4343
test('randomized verification', () {
@@ -46,13 +46,13 @@ void main() {
4646
final length = 1000 + r.nextInt(1000);
4747
final k = 10 + r.nextInt(200);
4848
final items = List.generate(length, (i) => i);
49-
final b1 = TopKSortedListBuilder(k, compare)..addAll(items);
50-
final r1 = b1.getTopK().toList();
49+
final b1 = Heap(compare)..collectAll(items);
50+
final r1 = b1.getAndRemoveTopK(k).toList();
5151
expect(r1, List.generate(k, (i) => length - 1 - i));
5252

5353
items.shuffle(r);
54-
final b2 = TopKSortedListBuilder(k, compare)..addAll(items);
55-
final r2 = b2.getTopK().toList();
54+
final b2 = Heap(compare)..collectAll(items);
55+
final r2 = b2.getAndRemoveTopK(k).toList();
5656
expect(r2, r1);
5757
}
5858
});

0 commit comments

Comments
 (0)