Skip to content

Commit

Permalink
SortedList and Heap now do implement the PriorityQueue interface.
Browse files Browse the repository at this point in the history
  • Loading branch information
renggli committed Dec 26, 2023
1 parent 56c7726 commit 18c98ac
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 37 deletions.
58 changes: 40 additions & 18 deletions lib/src/collection/heap.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import 'package:collection/collection.dart' show PriorityQueue;

import '../../comparator.dart';

/// A priority queue implemented using a binary heap.
class Heap<E> extends Iterable<E> {
class Heap<E> extends Iterable<E> implements PriorityQueue<E> {
/// Constructs an empty max-heap with an optional [comparator]. To create a
/// min-heap invert the comparator.
Heap({Comparator<E>? comparator})
Expand Down Expand Up @@ -38,27 +40,31 @@ class Heap<E> extends Iterable<E> {
@override
bool get isNotEmpty => _values.isNotEmpty;

/// Returns an [Iterator] over the underlying values.
@override
Iterator<E> get iterator => _values.iterator;

/// Returns the last/largest value from this heap.
E get peek {
@override
E get first {
_checkNotEmpty();
return _values[0];
}

/// Adds a new value onto this heap.
void push(E value) {
@override
void add(E value) {
_values.add(value);
_siftDown(0, _values.length - 1);
}

/// Adds multiple new values onto this heap.
void pushAll(Iterable<E> values) => values.forEach(push);
@override
void addAll(Iterable<E> values) => values.forEach(add);

/// Removes all objects from this heap.
@override
void clear() => _values.clear();

/// Removes and returns the last/largest value from this heap.
E pop() {
@override
E removeFirst() {
_checkNotEmpty();
final value = _values.removeLast();
if (_values.isNotEmpty) {
Expand All @@ -70,19 +76,31 @@ class Heap<E> extends Iterable<E> {
return value;
}

/// A pop immediately followed by a push. Contrary to [pushAndPop] this
/// Remove an element from the heap.
@override
bool remove(E value) => _values.remove(value);

/// Removes all objects from this heap.
@override
Iterable<E> removeAll() {
final result = _values.toList();
_values.clear();
return result;
}

/// A pop immediately followed by a push. Contrary to [addAndRemoveFirst] this
/// requires a non-empty heap and never returns `value`.
E popAndPush(E value) {
E removeFirstAndAdd(E value) {
_checkNotEmpty();
final result = _values[0];
_values[0] = value;
_siftUp(0);
return result;
}

/// A push immediately followed by a pop. Contrary to [popAndPush] this
/// A push immediately followed by a pop. Contrary to [removeFirstAndAdd] this
/// works on an empty heap and might directly return `value`.
E pushAndPop(E value) {
E addAndRemoveFirst(E value) {
if (_values.isEmpty || _comparator(_values[0], value) < 0) {
return value;
}
Expand All @@ -92,13 +110,17 @@ class Heap<E> extends Iterable<E> {
return result;
}

/// Removes all objects from this heap.
void clear() => _values.clear();
@override
Iterator<E> get iterator => _values.iterator;

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

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

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

void _siftDown(int start, int stop) {
Expand Down
29 changes: 28 additions & 1 deletion lib/src/collection/sortedlist.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import 'dart:collection' show ListBase;
import 'dart:math';

import 'package:collection/collection.dart' show PriorityQueue;

import '../../comparator.dart';

/// A sorted-list that remains sorted by a [Comparator] as elements get added.
class SortedList<E> extends ListBase<E> {
class SortedList<E> extends ListBase<E> implements PriorityQueue<E> {
/// Constructs an empty sorted-list with an optional `ordering`.
SortedList({Comparator<E>? comparator, bool growable = true})
: _values = List.empty(growable: growable),
Expand Down Expand Up @@ -64,12 +66,37 @@ class SortedList<E> extends ListBase<E> {
return true;
}

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

@override
E removeAt(int index) => _values.removeAt(index);

@override
E removeFirst() => _values.removeAt(0);

@override
E removeLast() => _values.removeLast();

@override
void clear() => _values.clear();

@override
void sort([int Function(E a, E b)? compare]) => _throw();

@override
void shuffle([Random? random]) => _throw();

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

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

static void _throw() =>
throw UnsupportedError('Cannot modify the order of a sorted list');
}
Expand Down
6 changes: 3 additions & 3 deletions lib/src/comparator/operations/smallest.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ extension SmallestComparator<T> on Comparator<T> {
List<T> smallest(Iterable<T> iterable, int k) {
final heap = Heap<T>(comparator: this);
for (final each in iterable) {
heap.push(each);
heap.add(each);
if (heap.length > k) {
heap.pop(); // drop the largest element
heap.removeFirst(); // drop the largest element
}
}
final result = List.generate(
math.min(k, heap.length), (index) => heap.pop(),
math.min(k, heap.length), (index) => heap.removeFirst(),
growable: false);
result.reverseRange(0, result.length);
return result;
Expand Down
63 changes: 48 additions & 15 deletions test/collection_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -665,7 +665,7 @@ void main() {
group(
'pushAll',
() => allHeapTests(<T>(List<T> list, {Comparator<T>? comparator}) =>
Heap<T>(comparator: comparator)..pushAll(list)));
Heap<T>(comparator: comparator)..addAll(list)));
});
group('iterable', () {
group('chunked', () {
Expand Down Expand Up @@ -4629,24 +4629,24 @@ void allHeapTests(
expect(heap.isEmpty, isTrue);
expect(heap.isNotEmpty, isFalse);
expect(heap.length, 0);
expect(() => heap.peek, throwsStateError);
expect(() => heap.pop(), throwsStateError);
expect(() => heap.popAndPush('Hello'), throwsStateError);
expect(heap.pushAndPop('World'), 'World');
expect(() => heap.first, throwsStateError);
expect(() => heap.removeFirst(), throwsStateError);
expect(() => heap.removeFirstAndAdd('Hello'), throwsStateError);
expect(heap.addAndRemoveFirst('World'), 'World');
expect(heap.length, 0);
});
test('popAndPush', () {
test('removeFirstAndAdd', () {
final heap = createHeap<String>(['Olivia', 'Emma', 'Sophia']);
expect(heap.popAndPush('Amelia'), 'Sophia');
expect(heap.popAndPush('Nora'), 'Olivia');
expect(heap.popAndPush('Violet'), 'Nora');
expect(heap.removeFirstAndAdd('Amelia'), 'Sophia');
expect(heap.removeFirstAndAdd('Nora'), 'Olivia');
expect(heap.removeFirstAndAdd('Violet'), 'Nora');
expect(heap.toList()..sort(), ['Amelia', 'Emma', 'Violet']);
});
test('pushAndPop', () {
test('addAndRemoveFirst', () {
final heap = createHeap<String>(['Olivia', 'Emma', 'Sophia']);
expect(heap.pushAndPop('Amelia'), 'Sophia');
expect(heap.pushAndPop('Nora'), 'Olivia');
expect(heap.pushAndPop('Violet'), 'Violet');
expect(heap.addAndRemoveFirst('Amelia'), 'Sophia');
expect(heap.addAndRemoveFirst('Nora'), 'Olivia');
expect(heap.addAndRemoveFirst('Violet'), 'Violet');
expect(heap.toList()..sort(), ['Amelia', 'Emma', 'Nora']);
});
test('clear', () {
Expand All @@ -4670,8 +4670,8 @@ void allHeapTests(
expect(heap.isNotEmpty, isTrue);
expect(heap.length, source.length);
final value = source.removeLast();
expect(heap.peek, value);
expect(heap.pop(), value);
expect(heap.first, value);
expect(heap.removeFirst(), value);
}
expect(heap.isEmpty, isTrue);
expect(heap.isNotEmpty, isFalse);
Expand Down Expand Up @@ -4741,6 +4741,11 @@ void allSortedListTests(
final list = createSortedList<int>([5, 1, 3], growable: false);
expect(() => list.addAll([2, 4]), throwsUnsupportedError);
});
test('clear', () {
final list = createSortedList<int>([5, 1, 3]);
list.clear();
expect(list, isEmpty);
});
test('remove', () {
final list = createSortedList<int>([5, 1, 3]);
expect(list, [1, 3, 5]);
Expand All @@ -4753,6 +4758,34 @@ void allSortedListTests(
final list = createSortedList<int>([5, 1, 3], growable: false);
expect(() => list.remove(3), throwsUnsupportedError);
});
test('removeAt', () {
final list = createSortedList<int>([5, 1, 3]);
expect(list.removeAt(1), 3);
expect(list, [1, 5]);
});
test('removeFirst', () {
final list = createSortedList<int>([5, 1, 3]);
expect(list.removeFirst(), 1);
expect(list, [3, 5]);
});
test('removeLast', () {
final list = createSortedList<int>([5, 1, 3]);
expect(list.removeLast(), 5);
expect(list, [1, 3]);
});
test('removeAll', () {
final list = createSortedList<int>([5, 1, 3]);
expect(list.removeAll(), [1, 3, 5]);
expect(list, isEmpty);
});
test('toUnorderedList', () {
final list = createSortedList<int>([5, 1, 3]);
expect(list.toUnorderedList(), [1, 3, 5]);
});
test('unorderedElements', () {
final list = createSortedList<int>([5, 1, 3]);
expect(list.unorderedElements, [1, 3, 5]);
});
test('stress', () {
final random = Random(6412);
final numbers = <int>{};
Expand Down

0 comments on commit 18c98ac

Please sign in to comment.