Add priority queue to package:collection.

R=ajohnsen@google.com, sgjesse@google.com

Review URL: https://codereview.chromium.org//110483006

git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/collection@31532 260f80e4-7a28-3924-810f-c04153c831b5
diff --git a/README.md b/README.md
index bb392fd..7ebd62a 100644
--- a/README.md
+++ b/README.md
@@ -8,14 +8,24 @@
 The `collection` package can be imported as separate libraries, or
 in totality:
 
-    import 'package:collection/equality.dart';
     import 'package:collection/algorithms.dart';
+    import 'package:collection/equality.dart';
+    import 'package:collection/iterable_zip.dart';
+    import 'package:collection/priority_queue.dart';
     import 'package:collection/wrappers.dart';
 
 or
 
     import 'package:collection/collection.dart';
 
+## Algorithms
+
+The algorithms library contains functions that operate on lists.
+
+It contains ways to shuffle a `List`, do binary search on a sorted `List`, and
+various sorting algorithms.
+
+
 ## Equality
 
 The equality library gives a way to specify equality of elements and
@@ -28,19 +38,23 @@
 case, for example, `const SetEquality(const IdentityEquality())` is an equality
 that considers two sets equal exactly if they contain identical elements.
 
-The library provides ways to define equalities on `Iterable`s, `List`s, `Set`s, and
-`Map`s, as well as combinations of these, such as:
+The library provides ways to define equalities on `Iterable`s, `List`s, `Set`s,
+and `Map`s, as well as combinations of these, such as:
 
     const MapEquality(const IdentityEquality(), const ListEquality());
 
-This equality considers maps equal if they have identical keys, and the corresponding values are lists with equal (`operator==`) values.
+This equality considers maps equal if they have identical keys, and the
+corresponding values are lists with equal (`operator==`) values.
 
-## Algorithms
 
-The algorithms library contains functions that operate on lists.
+## Iterable Zip
 
-It contains ways to shuffle a `List`, do binary search on a sorted `List`, and
-some different sorting algorithms.
+Utilities for "zipping" a list of iterables into an iterable of lists.
+
+
+## Priority Queue
+
+An interface and implemention of a priority queue.
 
 
 ## Wrappers
diff --git a/lib/collection.dart b/lib/collection.dart
index a6f192d..d640071 100644
--- a/lib/collection.dart
+++ b/lib/collection.dart
@@ -11,6 +11,7 @@
  *                      and various sorting algorithms).
  * - `equality.dart`: Different notions of equality of collections.
  * - `iterable_zip.dart`: Combining multiple iterables into one.
+ * - `priority_queue.dart`: Priority queue type and implementations.
  * - `wrappers.dart`: Wrapper classes that delegate to a collection object.
  *                    Includes unmodifiable views of collections.
  */
@@ -19,4 +20,5 @@
 export "algorithms.dart";
 export "equality.dart";
 export "iterable_zip.dart";
+export "priority_queue.dart";
 export "wrappers.dart";
diff --git a/lib/priority_queue.dart b/lib/priority_queue.dart
new file mode 100644
index 0000000..eb0cda3
--- /dev/null
+++ b/lib/priority_queue.dart
@@ -0,0 +1,383 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library dart.pkg.collection.priority_queue;
+
+import "dart:collection" show SplayTreeSet;
+
+/**
+ * A priority queue is a priority based work-list of elements.
+ *
+ * The queue allows adding elements, and removing them again in priority order.
+ */
+abstract class PriorityQueue<E> {
+  /**
+   * Number of elements in the queue.
+   */
+  int get length;
+
+  /**
+   * Whether the queue is empty.
+   */
+  bool get isEmpty;
+
+  /**
+   * Whether the queue has any elements.
+   */
+  bool get isNotEmpty;
+
+  /**
+   * Checks if [object] is in the queue.
+   *
+   * Returns true if the element is found.
+   */
+  bool contains(E object);
+
+  /**
+   * Returns the next element that will be returned by [removeFirst].
+   *
+   * The element is not removed from the queue.
+   *
+   * The queue must not be empty when this method is called.
+   */
+  E get first;
+
+  /**
+   * Removes and returns the element with the highest priority.
+   *
+   * Repeatedly calling this method, without adding element in between,
+   * is guaranteed to return elements in non-decreasing order as, specified by
+   * [comparison].
+   *
+   * The queue must not be empty when this method is called.
+   */
+  E removeFirst();
+
+  /**
+   * Removes an element that compares equal to [element] in the queue.
+   *
+   * Returns true if an element is found and removed,
+   * and false if not equal element is found.
+   */
+  bool remove(E element);
+
+  /**
+   * Removes all the elements from this queue and returns them.
+   *
+   * The returned iterable has no specified order.
+   */
+  Iterable<E> removeAll();
+
+  /**
+   * Removes all the elements from this queue.
+   */
+  void clear();
+
+  /**
+   * Returns a list of the elements of this queue in priority order.
+   *
+   * The queue is not modified.
+   *
+   * The order is the order that the elements would be in if they were
+   * removed from this queue using [removeFirst].
+   */
+  List<E> toList();
+
+  /**
+   * Return a comparator based set using the comparator of this queue.
+   *
+   * The queue is not modified.
+   *
+   * The returned [Set] is currently a [SplayTreeSet],
+   * but this may change as other ordered sets are implemented.
+   *
+   * The set contains all the elements of this queue.
+   * If an element occurs more than once in the queue,
+   * the set will contain it only once.
+   */
+  Set<E> toSet();
+}
+
+/**
+ * Heap based priority queue.
+ *
+ * The elements are kept in a heap structure,
+ * where the element with the highest priority is immediately accessible,
+ * and modifying a single element takes
+ * logarithmic time in the number of elements on average.
+ *
+ * * The [add] and [removeFirst] operations take amortized logarithmic time,
+ *   O(log(n)), but may occasionally take linear time when growing the capacity
+ *   of the heap.
+ * * The [addAll] operation works as doing repeated [add] operations.
+ * * The [first] getter takes constant time, O(1).
+ * * The [clear] and [removeAll] methods also take constant time, O(1).
+ * * The [contains] and [remove] operations may need to search the entire
+ *   queue for the elements, taking O(n) time.
+ * * The [toList] operation effectively sorts the elements, taking O(n*log(n))
+ *   time.
+ * * The [toSet] operation effectively adds each element to the new set, taking
+ *   an expected O(n*log(n)) time.
+ */
+class HeapPriorityQueue<E> implements PriorityQueue<E> {
+  /**
+   * Initial capacity of a queue when created, or when added to after a [clear].
+   *
+   * Number can be any positive value. Picking a size that gives a whole
+   * number of "tree levels" in the heap is only done for aesthetic reasons.
+   */
+  static const int _INITIAL_CAPACITY = 7;
+
+  /**
+   * The comparison being used to compare the priority of elements.
+   */
+  final Comparator comparison;
+
+  /**
+   * List implementation of a heap.
+   */
+  List<E> _queue = new List<E>(_INITIAL_CAPACITY);
+
+  /**
+   * Number of elements in queue.
+   *
+   * The heap is implemented in the first [_length] entries of [_queue].
+   */
+  int _length = 0;
+
+  /**
+   * Create a new priority queue.
+   *
+   * The [comparison] is a [Comparator] used to compare the priority of
+   * elements. An element that compares as less than another element has
+   * a higher priority.
+   *
+   * If [comparison] is omitted, it defaults to [Comparable.compare].
+   */
+  HeapPriorityQueue([int comparison(E e1, E e2)])
+      : comparison = (comparison != null) ? comparison : Comparable.compare;
+
+  void add(E element) {
+    _add(element);
+  }
+
+  void addAll(Iterable<E> elements) {
+    for (E element in elements) {
+      _add(element);
+    }
+  }
+
+  void clear() {
+    _queue = const [];
+    _length = 0;
+  }
+
+  bool contains(E object) {
+    return _locate(object) >= 0;
+  }
+
+  E get first {
+    if (_length == 0) throw new StateError("No such element");
+    return _queue[0];
+  }
+
+  bool get isEmpty => _length == 0;
+
+  bool get isNotEmpty => _length != 0;
+
+  int get length => _length;
+
+  bool remove(E element) {
+    int index = _locate(element);
+    if (index < 0) return false;
+    E last = _removeLast();
+    if (index < _length) {
+      int comp = comparison(last, element);
+      if (comp <= 0) {
+        _bubbleUp(last, index);
+      } else {
+        _bubbleDown(last, index);
+      }
+    }
+    return true;
+  }
+
+  Iterable<E> removeAll() {
+    List<E> result = _queue;
+    int length = _length;
+    _queue = const [];
+    _length = 0;
+    return result.take(length);
+  }
+
+  E removeFirst() {
+    if (_length == 0) throw new StateError("No such element");
+    E result = _queue[0];
+    E last = _removeLast();
+    if (_length > 0) {
+      _bubbleDown(last, 0);
+    }
+    return result;
+  }
+
+  List<E> toList() {
+    List<E> list = new List<E>()..length = _length;
+    list.setRange(0, _length, _queue);
+    list.sort(comparison);
+    return list;
+  }
+
+  Set<E> toSet() {
+    Set<E> set = new SplayTreeSet<E>(comparison);
+    for (int i = 0; i < _length; i++) {
+      set.add(_queue[i]);
+    }
+    return set;
+  }
+
+  /**
+   * Returns some representation of the queue.
+   *
+   * The format isn't significant, and may change in the future.
+   */
+  String toString() {
+    return _queue.take(_length).toString();
+  }
+
+  /**
+   * Add element to the queue.
+   *
+   * Grows the capacity if the backing list is full.
+   */
+  void _add(E element) {
+    if (_length == _queue.length) _grow();
+    _bubbleUp(element, _length++);
+  }
+
+  /**
+   * Find the index of an object in the heap.
+   *
+   * Returns -1 if the object is not found.
+   */
+  int _locate(E object) {
+    if (_length == 0) return -1;
+    // Count positions from one instad of zero. This gives the numbers
+    // some nice properties. For example, all right children are odd,
+    // their left sibling is even, and the parent is found by shifting
+    // right by one.
+    // Valid range for position is [1.._length], inclusive.
+    int position = 1;
+    // Pre-order depth first search, omit child nodes if the current
+    // node has lower priority than [object], because all nodes lower
+    // in the heap will also have lower priority.
+    do {
+      int index = position - 1;
+      E element = _queue[index];
+      int comp = comparison(element, object);
+      if (comp == 0) return index;
+      if (comp < 0) {
+        // Element may be in subtree.
+        // Continue with the left child, if it is there.
+        int leftChildPosition = position * 2;
+        if (leftChildPosition <= _length) {
+          position = leftChildPosition;
+          continue;
+        }
+      }
+      // Find the next right sibling or right ancestor sibling.
+      do {
+        while (position.isOdd) {
+          // While position is a right child, go to the parent.
+          position >>= 1;
+        }
+        // Then go to the right sibling of the left-child.
+        position += 1;
+      } while (position > _length);  // Happens if last element is a left child.
+    } while (position != 1);  // At root again. Happens for right-most element.
+    return -1;
+  }
+
+  E _removeLast() {
+    int newLength = _length - 1;
+    E last = _queue[newLength];
+    _queue[newLength] = null;
+    _length = newLength;
+    return last;
+  }
+
+  /**
+   * Place [element] in heap at [index] or above.
+   *
+   * Put element into the empty cell at `index`.
+   * While the `element` has higher priority than the
+   * parent, swap it with the parent.
+   */
+  void _bubbleUp(E element, int index) {
+    while (index > 0) {
+      int parentIndex = (index - 1) ~/ 2;
+      E parent = _queue[parentIndex];
+      if (comparison(element, parent) > 0) break;
+      _queue[index] = parent;
+      index = parentIndex;
+    }
+    _queue[index] = element;
+  }
+
+  /**
+   * Place [element] in heap at [index] or above.
+   *
+   * Put element into the empty cell at `index`.
+   * While the `element` has lower priority than either child,
+   * swap it with the highest priority child.
+   */
+  void _bubbleDown(E element, int index) {
+    int rightChildIndex = index * 2 + 2;
+    while (rightChildIndex < _length) {
+      int leftChildIndex = rightChildIndex - 1;
+      E leftChild = _queue[leftChildIndex];
+      E rightChild = _queue[rightChildIndex];
+      int comp = comparison(leftChild, rightChild);
+      int minChildIndex;
+      E minChild;
+      if (comp < 0) {
+        minChild = leftChild;
+        minChildIndex = leftChildIndex;
+      } else {
+        minChild = rightChild;
+        minChildIndex = rightChildIndex;
+      }
+      comp = comparison(element, minChild);
+      if (comp <= 0) {
+        _queue[index] = element;
+        return;
+      }
+      _queue[index] = minChild;
+      index = minChildIndex;
+      rightChildIndex = index * 2 + 2;
+    }
+    int leftChildIndex = rightChildIndex - 1;
+    if (leftChildIndex < _length) {
+      E child = _queue[leftChildIndex];
+      int comp = comparison(element, child);
+      if (comp > 0) {
+        _queue[index] = child;
+        index = leftChildIndex;
+      }
+    }
+    _queue[index] = element;
+  }
+
+  /**
+   * Grows the capacity of the list holding the heap.
+   *
+   * Called when the list is full.
+   */
+  void _grow() {
+    int newCapacity = _queue.length * 2 + 1;
+    if (newCapacity < _INITIAL_CAPACITY) newCapacity = _INITIAL_CAPACITY;
+    List<E> newQueue = new List<E>(newCapacity);
+    newQueue.setRange(0, _length, _queue);
+    _queue = newQueue;
+  }
+}
diff --git a/pubspec.yaml b/pubspec.yaml
index 0fec04b..09c07a7 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
 name: collection
-version: 0.9.0
+version: 0.9.1-dev
 author: Dart Team <misc@dartlang.org>
 description: Collections and utilities functions and classes related to collections.
 homepage: http://www.dartlang.org
diff --git a/test/priority_queue_test.dart b/test/priority_queue_test.dart
new file mode 100644
index 0000000..3d9c9fd
--- /dev/null
+++ b/test/priority_queue_test.dart
@@ -0,0 +1,167 @@
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// Tests priority queue implementations utilities.
+
+import "dart:collection";
+import "package:collection/priority_queue.dart";
+import "package:unittest/unittest.dart";
+
+void main() {
+  testInt(() => new HeapPriorityQueue<int>());
+  testCustom((comparator) => new HeapPriorityQueue<C>(comparator));
+}
+
+void testInt(PriorityQueue<int> create()) {
+  for (int count in [1, 5, 127, 128, 400]) {
+    testQueue("int:$count",
+              create,
+              new List<int>.generate(count, (x) => x),
+              count);
+  }
+}
+
+void testCustom(PriorityQueue<C> create(comparator)) {
+  for (int count in [1, 5, 127, 128, 400]) {
+    testQueue("Custom:$count/null",
+              () => create(null),
+              new List<C>.generate(count, (x) => new C(x)),
+              new C(count));
+    testQueue("Custom:$count/compare",
+              () => create(compare),
+              new List<C>.generate(count, (x) => new C(x)),
+              new C(count));
+    testQueue("Custom:$count/compareNeg",
+              () => create(compareNeg),
+              new List<C>.generate(count, (x) => new C(count - x)),
+              new C(0));
+  }
+}
+
+/**
+ * Test that a queue behaves correctly.
+ *
+ * The elements must be in priority order, from highest to lowest.
+ */
+void testQueue(String name, PriorityQueue create(), List elements, notElement) {
+  test(name, () => testQueueBody(create, elements, notElement));
+}
+
+void testQueueBody(PriorityQueue create(), List elements, notElement) {
+  PriorityQueue q = create();
+  expect(q.isEmpty, isTrue);
+  expect(q, hasLength(0));
+  expect(() { q.first; }, throwsStateError);
+  expect(() { q.removeFirst(); }, throwsStateError);
+
+  // Tests removeFirst, first, contains, toList and toSet.
+  void testElements() {
+    expect(q.isNotEmpty, isTrue);
+    expect(q, hasLength(elements.length));
+
+    expect(q.toList(), equals(elements));
+    expect(q.toSet().toList(), equals(elements));
+
+    for (int i = 0; i < elements.length; i++) {
+      expect(q.contains(elements[i]), isTrue);
+    }
+    expect(q.contains(notElement), isFalse);
+
+    List all = [];
+    while (!q.isEmpty) {
+      var expected = q.first;
+      var actual = q.removeFirst();
+      expect(actual, same(expected));
+      all.add(actual);
+    }
+
+    expect(all.length, elements.length);
+    for (int i = 0; i < all.length; i++) {
+      expect(all[i], same(elements[i]));
+    }
+
+    expect(q.isEmpty, isTrue);
+  }
+
+  q.addAll(elements);
+  testElements();
+
+  q.addAll(elements.reversed);
+  testElements();
+
+  // Add elements in a non-linear order (gray order).
+  for (int i = 0, j = 0; i < elements.length; i++) {
+    int gray;
+    do {
+      gray = j ^ (j >> 1);
+      j++;
+    } while (gray >= elements.length);
+    q.add(elements[gray]);
+  }
+  testElements();
+
+  // Add elements by picking the middle element first, and then recursing
+  // on each side.
+  void addRec(int min, int max) {
+    int mid = min + ((max - min) >> 1);
+    q.add(elements[mid]);
+    if (mid + 1 < max) addRec(mid + 1, max);
+    if (mid > min) addRec(min, mid);
+  }
+  addRec(0, elements.length);
+  testElements();
+
+  // Test removeAll.
+  q.addAll(elements);
+  expect(q, hasLength(elements.length));
+  Iterable all = q.removeAll();
+  expect(q.isEmpty, isTrue);
+  expect(all, hasLength(elements.length));
+  for (int i = 0; i < elements.length; i++) {
+    expect(all, contains(elements[i]));
+  }
+
+  // Test the same element more than once in queue.
+  q.addAll(elements);
+  q.addAll(elements.reversed);
+  expect(q, hasLength(elements.length * 2));
+  for (int i = 0; i < elements.length; i++) {
+    var element = elements[i];
+    expect(q.contains(element), isTrue);
+    expect(q.removeFirst(), element);
+    expect(q.removeFirst(), element);
+  }
+
+  // Test queue with all same element.
+  var a = elements[0];
+  for (int i = 0; i < elements.length; i++) {
+    q.add(a);
+  }
+  expect(q, hasLength(elements.length));
+  expect(q.contains(a), isTrue);
+  expect(q.contains(notElement), isFalse);
+  q.removeAll().forEach((x) => expect(x, same(a)));
+
+  // Test remove.
+  q.addAll(elements);
+  for (var element in elements.reversed) {
+    expect(q.remove(element), isTrue);
+  }
+  expect(q.isEmpty, isTrue);
+}
+
+
+// Custom class.
+// Class is comparable, comparators match normal and inverse order.
+int compare(C c1, C c2) => c1.value - c2.value;
+int compareNeg(C c1, C c2) => c2.value - c1.value;
+
+class C implements Comparable {
+  final int value;
+  const C(this.value);
+  int get hashCode => value;
+  bool operator==(Object other) => other is C && value == other.value;
+  int compareTo(C other) => value - other.value;
+  String toString() => "C($value)";
+}