Skip to content

Commit

Permalink
Rename BinaryHeapPriorityQueue back to Heap and implement efficie…
Browse files Browse the repository at this point in the history
…nt `remove` and `contains`.
  • Loading branch information
renggli committed Dec 28, 2023
1 parent f55fcfc commit 85a4544
Show file tree
Hide file tree
Showing 8 changed files with 380 additions and 286 deletions.
3 changes: 2 additions & 1 deletion lib/collection.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ library collection;
export 'src/collection/bimap.dart'
show BiMap, BiMapOnIterableExtension, BiMapOnMapExtension;
export 'src/collection/bitlist.dart' show BitList, BitListExtension;
export 'src/collection/heap.dart'
show Heap, HeapEntry, KeyValueHeapEntry, ValueHeapEntry;
export 'src/collection/iterable/chunked.dart' show ChunkedIterableExtension;
export 'src/collection/iterable/combinations.dart'
show CombinationsIterableExtension;
Expand Down Expand Up @@ -48,7 +50,6 @@ export 'src/collection/multimap/list.dart'
export 'src/collection/multimap/set.dart'
show SetMultimap, SetMultimapOnIterableExtension, SetMultimapOnMapExtension;
export 'src/collection/multiset.dart' show Multiset, MultisetExtension;
export 'src/collection/queue/binary_heap.dart' show BinaryHeapPriorityQueue;
export 'src/collection/range.dart' show Range, RangeIterator;
export 'src/collection/range/bigint.dart'
show BigIntRange, BigIntRangeExtension;
Expand Down
209 changes: 209 additions & 0 deletions lib/src/collection/heap.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
import 'package:collection/collection.dart' show PriorityQueue;

/// A priority queue implemented using a binary min-heap.
///
/// Contrary to the [PriorityQueue] in the Dart standard library, this
/// implementation requires elements to be subclasses of [HeapEntry]. For
/// convenience two standard implementation as provided: [ValueHeapEntry] and
/// [KeyValueHeapEntry].
///
/// This implementation does not only provide amortized logarithmic time for
/// [add] and [removeFirst], but also for [remove]. Furthermore it can not only
/// answer [first], but also [contains] in constant time. It does this by
/// keeping track of the index in the internal element list of all entries.
class Heap<E extends HeapEntry<E>> implements PriorityQueue<E> {
/// Constructs an empty binary min-heap.
Heap() : _elements = <E>[];

/// Constructs a binary min-heap from an [iterable] of elements.
Heap.of(Iterable<E> iterable) : _elements = List<E>.of(iterable) {
for (var i = 0; i < _elements.length; i++) {
final element = _elements[i];
_checkNoIndex(element);
element._index = i;
}
if (_elements.length > 1) {
for (var i = _elements.length ~/ 2; i >= 0; i--) {
_siftUp(i);
}
}
}

final List<E> _elements;

@override
int get length => _elements.length;

@override
bool get isEmpty => _elements.isEmpty;

@override
bool get isNotEmpty => _elements.isNotEmpty;

@override
bool contains(E element) =>
element._index != HeapEntry._invalidIndex &&
_elements[element._index] == element;

@override
Iterable<E> get unorderedElements => _elements;

@override
void add(E element) {
_checkNoIndex(element);
element._index = _elements.length;
_elements.add(element);
_siftDown(0, _elements.length - 1);
}

@override
void addAll(Iterable<E> elements) => elements.forEach(add);

@override
E get first {
_checkNotEmpty();
return _elements[0];
}

@override
E removeFirst() {
_checkNotEmpty();
final element = _elements.removeLast();
if (_elements.isNotEmpty) {
final result = _elements[0];
_elements[0] = element;
element._index = 0;
_siftUp(0);
result._index = HeapEntry._invalidIndex;
return result;
}
element._index = HeapEntry._invalidIndex;
return element;
}

@override
bool remove(E element) {
if (element._index != HeapEntry._invalidIndex &&
_elements[element._index] == element) {
_elements.removeAt(element._index);
for (var i = element._index; i < _elements.length; i++) {
_elements[i]._index = i;
}
element._index = HeapEntry._invalidIndex;
return true;
}
return false;
}

@override
Iterable<E> removeAll() {
final result = _elements.toList();
clear();
return result;
}

@override
void clear() {
for (final element in _elements) {
element._index = HeapEntry._invalidIndex;
}
_elements.clear();
}

@override
List<E> toList() => _elements.toList()..sort();

@override
List<E> toUnorderedList() => _elements.toList();

@override
Set<E> toSet() => _elements.toSet();

void _checkNotEmpty() {
if (_elements.isEmpty) {
throw StateError('No element');
}
}

void _checkNoIndex(E element) {
if (element._index != HeapEntry._invalidIndex) {
throw StateError('$element is already part of a heap');
}
}

void _siftDown(int start, int stop) {
final element = _elements[stop];
while (start < stop) {
final parentIndex = (stop - 1) >> 1;
final parent = _elements[parentIndex];
if (0 < element.compareTo(parent)) {
break;
}
_elements[stop] = parent;
parent._index = stop;
stop = parentIndex;
}
_elements[stop] = element;
element._index = stop;
}

void _siftUp(int pos) {
final end = _elements.length;
final start = pos;
final element = _elements[pos];
var childLeft = 2 * pos + 1;
while (childLeft < end) {
final childRight = childLeft + 1;
if (childRight < end &&
0 < _elements[childLeft].compareTo(_elements[childRight])) {
childLeft = childRight;
}
_elements[pos] = _elements[childLeft];
_elements[childLeft]._index = pos;
pos = childLeft;
childLeft = 2 * pos + 1;
}
_elements[pos] = element;
element._index = pos;
_siftDown(start, pos);
}
}

/// The abstract superclass of all elements in a [Heap].
abstract mixin class HeapEntry<T> implements Comparable<T> {
/// Index marker of an entry that is not currently in a heap.
static const _invalidIndex = -1;

/// Internal index of the entry in the heap.
int _index = _invalidIndex;
}

/// Convenience class providing a [Heap] entry with a single comparable value.
class ValueHeapEntry<V extends Comparable<V>>
extends HeapEntry<ValueHeapEntry<V>> {
ValueHeapEntry(this.value);

final V value;

@override
int compareTo(ValueHeapEntry<V> other) => value.compareTo(other.value);

@override
String toString() => '$value';
}

/// Convenience class providing a [Heap] entry with a key as its priority and
/// an associated value.
class KeyValueHeapEntry<K extends Comparable<K>, V>
extends HeapEntry<KeyValueHeapEntry<K, V>> {
KeyValueHeapEntry(this.key, this.value);

final K key;
final V value;

@override
int compareTo(KeyValueHeapEntry<K, V> other) => key.compareTo(other.key);

@override
String toString() => '$key: $value';
}
136 changes: 0 additions & 136 deletions lib/src/collection/queue/binary_heap.dart

This file was deleted.

4 changes: 1 addition & 3 deletions lib/src/comparator/operations/largest.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,11 @@ import 'dart:math' as math;

import 'package:collection/collection.dart';

import '../../collection/queue/binary_heap.dart';

extension LargestComparator<T> on Comparator<T> {
/// Returns a list of the [k] largest elements of the given iterable
/// according to this ordering, in order from largest to smallest.
List<T> largest(Iterable<T> iterable, int k) {
final heap = BinaryHeapPriorityQueue<T>(this);
final heap = PriorityQueue<T>(this);
for (final each in iterable) {
heap.add(each);
if (heap.length > k) {
Expand Down
Loading

0 comments on commit 85a4544

Please sign in to comment.