Permalink
Browse files

Merge branch 'oplog-limits-buffered' into devel

  • Loading branch information...
2 parents 0791b31 + 1cb76e1 commit 53f2b73d6f05d024bc855f09f81370fdbe79b845 @Slava Slava committed Mar 4, 2014
@@ -0,0 +1 @@
+.build*
@@ -0,0 +1,138 @@
+Tinytest.add("binary-heap - simple max-heap tests", function (test) {
+ var h = new MaxHeap(function (a, b) { return a-b; });
+ h.set("a", 1);
+ h.set("b", 233);
+ h.set("c", -122);
+ h.set("d", 0);
+ h.set("e", 0);
+
+ test.equal(h.size(), 5);
+ test.equal(h.maxElementId(), "b");
+ test.equal(h.get("b"), 233);
+
+ h.remove("b");
+ test.equal(h.size(), 4);
+ test.equal(h.maxElementId(), "a");
+ h.set("e", 44);
+ test.equal(h.maxElementId(), "e");
+ test.equal(h.get("b"), null);
+ test.isTrue(h.has("a"));
+ test.isFalse(h.has("dd"));
+
+ h.clear();
+ test.isFalse(h.has("a"));
+ test.equal(h.size(), 0);
+ test.equal(h.setDefault("a", 12345), 12345);
+ test.equal(h.setDefault("a", 55555), 12345);
+ test.equal(h.size(), 1);
+ test.equal(h.maxElementId(), "a");
+});
+
+Tinytest.add("binary-heap - big test for max-heap", function (test) {
+ var positiveNumbers = _.shuffle(_.range(1, 41));
+ var negativeNumbers = _.shuffle(_.range(-1, -41, -1));
+ var allNumbers = negativeNumbers.concat(positiveNumbers);
+
+ var heap = new MaxHeap(function (a, b) { return a-b; });
+ var output = [];
+
+ _.each(allNumbers, function (n) { heap.set(n, n); });
+
+ _.times(positiveNumbers.length + negativeNumbers.length, function () {
+ var maxId = heap.maxElementId();
+ output.push(heap.get(maxId));
+ heap.remove(maxId);
+ });
+
+ allNumbers.sort(function (a, b) { return b-a; });
+
+ test.equal(output, allNumbers);
+});
+
+Tinytest.add("binary-heap - min-max heap tests", function (test) {
+ var h = new MinMaxHeap(function (a, b) { return a-b; });
+ h.set("a", 1);
+ h.set("b", 233);
+ h.set("c", -122);
+ h.set("d", 0);
+ h.set("e", 0);
+
+ test.equal(h.size(), 5);
+ test.equal(h.maxElementId(), "b");
+ test.equal(h.get("b"), 233);
+ test.equal(h.minElementId(), "c");
+
+ h.remove("b");
+ test.equal(h.size(), 4);
+ test.equal(h.minElementId(), "c");
+ h.set("e", -123);
+ test.equal(h.minElementId(), "e");
+ test.equal(h.get("b"), null);
+ test.isTrue(h.has("a"));
+ test.isFalse(h.has("dd"));
+
+ h.clear();
+ test.isFalse(h.has("a"));
+ test.equal(h.size(), 0);
+ test.equal(h.setDefault("a", 12345), 12345);
+ test.equal(h.setDefault("a", 55555), 12345);
+ test.equal(h.size(), 1);
+ test.equal(h.maxElementId(), "a");
+ test.equal(h.minElementId(), "a");
+});
+
+Tinytest.add("binary-heap - big test for min-max-heap", function (test) {
+ var N = 500;
+ var positiveNumbers = _.shuffle(_.range(1, N + 1));
+ var negativeNumbers = _.shuffle(_.range(-1, -N - 1, -1));
+ var allNumbers = positiveNumbers.concat(negativeNumbers);
+
+ var heap = new MinMaxHeap(function (a, b) { return a-b; });
+ var output = [];
+
+ var initialSets = _.clone(allNumbers);
+ _.each(allNumbers, function (n) {
+ heap.set(n, n);
+ heap._selfCheck();
+ heap._minHeap._selfCheck();
+ });
+
+ allNumbers = _.shuffle(allNumbers);
+ var secondarySets = _.clone(allNumbers);
+
+ _.each(allNumbers, function (n) {
+ heap.set(-n, n);
+ heap._selfCheck();
+ heap._minHeap._selfCheck();
+ });
+
+ _.times(positiveNumbers.length + negativeNumbers.length, function () {
+ var minId = heap.minElementId();
+ output.push(heap.get(minId));
+ heap.remove(minId);
+ heap._selfCheck(); heap._minHeap._selfCheck();
+ });
+
+ test.equal(heap.size(), 0);
+
+ allNumbers.sort(function (a, b) { return a-b; });
+
+ var initialTestText = "initial sets: " + initialSets.toString() +
+ "; secondary sets: " + secondarySets.toString();
+ test.equal(output, allNumbers, initialTestText);
+
+ _.each(initialSets, function (n) { heap.set(n, n); })
+ _.each(secondarySets, function (n) { heap.set(-n, n); });
+
+ allNumbers.sort(function (a, b) { return b-a; });
+ output = [];
+ _.times(positiveNumbers.length + negativeNumbers.length, function () {
+ var maxId = heap.maxElementId();
+ output.push(heap.get(maxId));
+ heap.remove(maxId);
+ heap._selfCheck(); heap._minHeap._selfCheck();
+ });
+
+ test.equal(output, allNumbers, initialTestText);
+});
+
@@ -0,0 +1,226 @@
+// Constructor of Heap
+// - comparator - Function - given two items returns a number
+// - options:
+// - initData - Array - Optional - the initial data in a format:
+// Object:
+// - id - String - unique id of the item
+// - value - Any - the data value
+// each value is retained
+// - IdMap - Constructor - Optional - custom IdMap class to store id->index
+// mappings internally. Standard IdMap is used by default.
+MaxHeap = function (comparator, options) {
+ if (! _.isFunction(comparator))
+ throw new Error('Passed comparator is invalid, should be a comparison function');
+ var self = this;
+
+ // a C-style comparator that is given two values and returns a number,
+ // negative if the first value is less than the second, positive if the second
+ // value is greater than the first and zero if they are equal.
+ self._comparator = comparator;
+
+ options = _.defaults(options || {}, { IdMap: IdMap });
+
+ // _heapIdx maps an id to an index in the Heap array the corresponding value
+ // is located on.
+ self._heapIdx = new options.IdMap;
+
+ // The Heap data-structure implemented as a 0-based contiguous array where
+ // every item on index idx is a node in a complete binary tree. Every node can
+ // have children on indexes idx*2+1 and idx*2+2, except for the leaves. Every
+ // node has a parent on index (idx-1)/2;
+ self._heap = [];
+
+ // If the initial array is passed, we can build the heap in linear time
+ // complexity (O(N)) compared to linearithmic time complexity (O(nlogn)) if
+ // we push elements one by one.
+ if (_.isArray(options.initData))
+ self._initFromData(options.initData);
+};
+
+_.extend(MaxHeap.prototype, {
+ // Builds a new heap in-place in linear time based on passed data
+ _initFromData: function (data) {
+ var self = this;
+
+ self._heap = _.map(data, function (o) {
+ return { id: o.id, value: o.value };
+ });
+
+ _.each(data, function (o, i) {
+ self._heapIdx.set(o.id, i);
+ });
+
+ if (! data.length)
+ return;
+
+ // start from the first non-leaf - the parent of the last leaf
+ for (var i = parentIdx(data.length - 1); i >= 0; i--)
+ self._downHeap(i);
+ },
+
+ _downHeap: function (idx) {
+ var self = this;
+
+ while (leftChildIdx(idx) < self.size()) {
+ var left = leftChildIdx(idx);
+ var right = rightChildIdx(idx);
+ var largest = idx;
+
+ if (left < self.size()) {
+ largest = self._maxIndex(largest, left);
+ }
+ if (right < self.size()) {
+ largest = self._maxIndex(largest, right);
+ }
+
+ if (largest === idx)
+ break;
+
+ self._swap(largest, idx);
+ idx = largest;
+ }
+ },
+
+ _upHeap: function (idx) {
+ var self = this;
+
+ while (idx > 0) {
+ var parent = parentIdx(idx);
+ if (self._maxIndex(parent, idx) === idx) {
+ self._swap(parent, idx)
+ idx = parent;
+ } else {
+ break;
+ }
+ }
+ },
+
+ _maxIndex: function (idxA, idxB) {
+ var self = this;
+ var valueA = self._get(idxA);
+ var valueB = self._get(idxB);
+ return self._comparator(valueA, valueB) >= 0 ? idxA : idxB;
+ },
+
+ // Internal: gets raw data object placed on idxth place in heap
+ _get: function (idx) {
+ var self = this;
+ return self._heap[idx].value;
+ },
+
+ _swap: function (idxA, idxB) {
+ var self = this;
+ var recA = self._heap[idxA];
+ var recB = self._heap[idxB];
+
+ self._heapIdx.set(recA.id, idxB);
+ self._heapIdx.set(recB.id, idxA);
+
+ self._heap[idxA] = recB;
+ self._heap[idxB] = recA;
+ },
+
+ get: function (id) {
+ var self = this;
+ if (! self.has(id))
+ return null;
+ return self._get(self._heapIdx.get(id));
+ },
+ set: function (id, value) {
+ var self = this;
+
+ if (self.has(id)) {
+ if (self.get(id) === value)
+ return;
+
+ var idx = self._heapIdx.get(id);
+ self._heap[idx].value = value;
+
+ // Fix the new value's position
+ // Either bubble new value up if it is greater than its parent
+ self._upHeap(idx);
+ // or bubble it down if it is smaller than one of its children
+ self._downHeap(idx);
+ } else {
+ self._heapIdx.set(id, self._heap.length);
+ self._heap.push({ id: id, value: value });
+ self._upHeap(self._heap.length - 1);
+ }
+ },
+ remove: function (id) {
+ var self = this;
+
+ if (self.has(id)) {
+ var last = self._heap.length - 1;
+ var idx = self._heapIdx.get(id);
+
+ if (idx !== last) {
+ self._swap(idx, last);
+ self._heap.pop();
+ self._heapIdx.remove(id);
+
+ // Fix the swapped value's position
+ self._upHeap(idx);
+ self._downHeap(idx);
+ } else {
+ self._heap.pop();
+ self._heapIdx.remove(id);
+ }
+ }
+ },
+ has: function (id) {
+ var self = this;
+ return self._heapIdx.has(id);
+ },
+ empty: function (id) {
+ var self = this;
+ return !self.size();
+ },
+ clear: function () {
+ var self = this;
+ self._heap = [];
+ self._heapIdx.clear();
+ },
+ // iterate over values in no particular order
+ forEach: function (iterator) {
+ var self = this;
+ _.each(self._heap, function (obj) {
+ return iterator(obj.value, obj.id);
+ });
+ },
+ size: function () {
+ var self = this;
+ return self._heap.length;
+ },
+ setDefault: function (id, def) {
+ var self = this;
+ if (self.has(id))
+ return self.get(id);
+ self.set(id, def);
+ return def;
+ },
+ clone: function () {
+ var self = this;
+ var clone = new MaxHeap(self._comparator, self._heap);
+ return clone;
+ },
+
+ maxElementId: function () {
+ var self = this;
+ return self.size() ? self._heap[0].id : null;
+ },
+
+ _selfCheck: function () {
+ var self = this;
+ for (var i = 1; i < self._heap.length; i++)
+ if (self._maxIndex(parentIdx(i), i) !== parentIdx(i))
+ throw new Error("An item with id " + self._heap[i].id +
+ " has a parent younger than it: " +
+ self._heap[parentIdx(i)].id);
+ }
+});
+
+function leftChildIdx (i) { return i * 2 + 1; }
+function rightChildIdx (i) { return i * 2 + 2; }
+function parentIdx (i) { return (i - 1) >> 1; }
+
Oops, something went wrong.

0 comments on commit 53f2b73

Please sign in to comment.