Add `unorderedElements` getter to PriorityQueue. (#155)
* Add `unorderedElements` getter to PriorityQueue.
* Fix bug in queue_list which made tests fail.
diff --git a/lib/src/priority_queue.dart b/lib/src/priority_queue.dart
index 6abce79..6952a81 100644
--- a/lib/src/priority_queue.dart
+++ b/lib/src/priority_queue.dart
@@ -58,6 +58,16 @@
/// an object.
bool contains(E object);
+ /// Provides efficient access to all the elements curently in the queue.
+ ///
+ /// The operation should be performed without copying or moving
+ /// the elements, if at all possible.
+ ///
+ /// The elements are iterated in no particular order.
+ /// The order is stable as long as the queue is not modified.
+ /// The queue must not be modified during an iteration.
+ Iterable<E> get unorderedElements;
+
/// Adds element to the queue.
///
/// The element will become the next to be removed by [removeFirst]
@@ -177,6 +187,11 @@
/// The heap is implemented in the first [_length] entries of [_queue].
int _length = 0;
+ /// Modification count.
+ ///
+ /// Used to detect concurrent modifications during iteration.
+ int _modificationCount = 0;
+
/// Create a new priority queue.
///
/// The [comparison] is a [Comparator] used to compare the priority of
@@ -193,18 +208,23 @@
@override
void add(E element) {
+ _modificationCount++;
_add(element);
}
@override
void addAll(Iterable<E> elements) {
+ var modified = 0;
for (var element in elements) {
+ modified = 1;
_add(element);
}
+ _modificationCount += modified;
}
@override
void clear() {
+ _modificationCount++;
_queue = const [];
_length = 0;
}
@@ -212,6 +232,16 @@
@override
bool contains(E object) => _locate(object) >= 0;
+ /// Provides efficient access to all the elements curently in the queue.
+ ///
+ /// The operation is performed in the order they occur
+ /// in the underlying heap structure.
+ ///
+ /// The order is stable as long as the queue is not modified.
+ /// The queue must not be modified during an iteration.
+ @override
+ Iterable<E> get unorderedElements => _UnorderedElementsIterable<E>(this);
+
@override
E get first {
if (_length == 0) throw StateError('No element');
@@ -231,6 +261,7 @@
bool remove(E element) {
var index = _locate(element);
if (index < 0) return false;
+ _modificationCount++;
var last = _removeLast();
if (index < _length) {
var comp = comparison(last, element);
@@ -243,8 +274,15 @@
return true;
}
+ /// Removes all the elements from this queue and returns them.
+ ///
+ /// The returned iterable has no specified order.
+ /// The operation does not copy the elements,
+ /// but instead keeps them in the existing heap structure,
+ /// and iterates over that directly.
@override
Iterable<E> removeAll() {
+ _modificationCount++;
var result = _queue;
var length = _length;
_queue = const [];
@@ -255,6 +293,7 @@
@override
E removeFirst() {
if (_length == 0) throw StateError('No element');
+ _modificationCount++;
var result = _elementAt(0);
var last = _removeLast();
if (_length > 0) {
@@ -418,3 +457,41 @@
_queue = newQueue;
}
}
+
+/// Implementation of [HeapPriorityQueue.unorderedElements].
+class _UnorderedElementsIterable<E> extends Iterable<E> {
+ final HeapPriorityQueue<E> _queue;
+ _UnorderedElementsIterable(this._queue);
+ @override
+ Iterator<E> get iterator => _UnorderedElementsIterator<E>(_queue);
+}
+
+class _UnorderedElementsIterator<E> implements Iterator<E> {
+ final HeapPriorityQueue<E> _queue;
+ final int _initialModificationCount;
+ E? _current;
+ int _index = -1;
+
+ _UnorderedElementsIterator(this._queue)
+ : _initialModificationCount = _queue._modificationCount;
+
+ @override
+ bool moveNext() {
+ if (_initialModificationCount != _queue._modificationCount) {
+ throw ConcurrentModificationError(_queue);
+ }
+ var nextIndex = _index + 1;
+ if (0 <= nextIndex && nextIndex < _queue.length) {
+ _current = _queue._queue[nextIndex];
+ _index = nextIndex;
+ return true;
+ }
+ _current = null;
+ _index = -2;
+ return false;
+ }
+
+ @override
+ E get current =>
+ _index < 0 ? throw StateError('No element') : (_current ?? null as E);
+}
diff --git a/lib/src/queue_list.dart b/lib/src/queue_list.dart
index 2bda02b..14b696e 100644
--- a/lib/src/queue_list.dart
+++ b/lib/src/queue_list.dart
@@ -150,14 +150,10 @@
@override
set length(int value) {
if (value < 0) throw RangeError('Length $value may not be negative.');
- if (value > length) {
- try {
- null as E;
- } on TypeError {
- throw UnsupportedError(
- 'The length can only be increased when the element type is '
- 'nullable, but the current element type is `$E`.');
- }
+ if (value > length && null is! E) {
+ throw UnsupportedError(
+ 'The length can only be increased when the element type is '
+ 'nullable, but the current element type is `$E`.');
}
var delta = value - length;
diff --git a/test/priority_queue_test.dart b/test/priority_queue_test.dart
index 191857d..ae27f98 100644
--- a/test/priority_queue_test.dart
+++ b/test/priority_queue_test.dart
@@ -14,6 +14,7 @@
testCustom((comparator) => HeapPriorityQueue<C>(comparator));
testDuplicates();
testNullable();
+ testConcurrentModification();
}
void testDefault() {
@@ -69,6 +70,11 @@
expect(q.toList(), equals(elements));
expect(q.toSet().toList(), equals(elements));
+ expect(q.toUnorderedList(), unorderedEquals(elements));
+ expect(q.unorderedElements, unorderedEquals(elements));
+
+ var allElements = q.removeAll();
+ q.addAll(allElements);
for (var i = 0; i < elements.length; i++) {
expect(q.contains(elements[i]), isTrue);
@@ -261,6 +267,98 @@
});
}
+void testConcurrentModification() {
+ group('concurrent modification for', () {
+ test('add', () {
+ var q = HeapPriorityQueue<int>((a, b) => a - b)
+ ..addAll([6, 4, 2, 3, 5, 8]);
+ var e = q.unorderedElements;
+ q.add(12); // Modifiation before creating iterator is not a problem.
+ var it = e.iterator;
+ q.add(7); // Modification after creatig iterator is a problem.
+ expect(it.moveNext, throwsConcurrentModificationError);
+
+ it = e.iterator; // New iterator is not affected.
+ expect(it.moveNext(), true);
+ expect(it.moveNext(), true);
+ q.add(9); // Modification during iteration is a problem.
+ expect(it.moveNext, throwsConcurrentModificationError);
+ });
+
+ test('addAll', () {
+ var q = HeapPriorityQueue<int>((a, b) => a - b)
+ ..addAll([6, 4, 2, 3, 5, 8]);
+ var e = q.unorderedElements;
+ q.addAll([12]); // Modifiation before creating iterator is not a problem.
+ var it = e.iterator;
+ q.addAll([7]); // Modification after creatig iterator is a problem.
+ expect(it.moveNext, throwsConcurrentModificationError);
+ it = e.iterator; // New iterator is not affected.
+ expect(it.moveNext(), true);
+ q.addAll([]); // Adding nothing is not a modification.
+ expect(it.moveNext(), true);
+ q.addAll([9]); // Modification during iteration is a problem.
+ expect(it.moveNext, throwsConcurrentModificationError);
+ });
+
+ test('removeFirst', () {
+ var q = HeapPriorityQueue<int>((a, b) => a - b)
+ ..addAll([6, 4, 2, 3, 5, 8]);
+ var e = q.unorderedElements;
+ expect(q.removeFirst(),
+ 2); // Modifiation before creating iterator is not a problem.
+ var it = e.iterator;
+ expect(q.removeFirst(),
+ 3); // Modification after creatig iterator is a problem.
+ expect(it.moveNext, throwsConcurrentModificationError);
+
+ it = e.iterator; // New iterator is not affected.
+ expect(it.moveNext(), true);
+ expect(it.moveNext(), true);
+ expect(q.removeFirst(), 4); // Modification during iteration is a problem.
+ expect(it.moveNext, throwsConcurrentModificationError);
+ });
+
+ test('remove', () {
+ var q = HeapPriorityQueue<int>((a, b) => a - b)
+ ..addAll([6, 4, 2, 3, 5, 8]);
+ var e = q.unorderedElements;
+ expect(q.remove(3), true);
+ var it = e.iterator;
+ expect(q.remove(2), true);
+ expect(it.moveNext, throwsConcurrentModificationError);
+ it = e.iterator;
+ expect(q.remove(99), false);
+ expect(it.moveNext(), true);
+ expect(it.moveNext(), true);
+ expect(q.remove(5), true);
+ expect(it.moveNext, throwsConcurrentModificationError);
+ });
+
+ test('removeAll', () {
+ var q = HeapPriorityQueue<int>((a, b) => a - b)
+ ..addAll([6, 4, 2, 3, 5, 8]);
+ var e = q.unorderedElements;
+ var it = e.iterator;
+ expect(it.moveNext(), true);
+ expect(it.moveNext(), true);
+ expect(q.removeAll(), hasLength(6));
+ expect(it.moveNext, throwsConcurrentModificationError);
+ });
+
+ test('clear', () {
+ var q = HeapPriorityQueue<int>((a, b) => a - b)
+ ..addAll([6, 4, 2, 3, 5, 8]);
+ var e = q.unorderedElements;
+ var it = e.iterator;
+ expect(it.moveNext(), true);
+ expect(it.moveNext(), true);
+ q.clear();
+ expect(it.moveNext, throwsConcurrentModificationError);
+ });
+ });
+}
+
// Custom class.
// Class is comparable, comparators match normal and inverse order.
int compare(C c1, C c2) => c1.value - c2.value;