Factor out core elements of ol.TileQueue into ol.structs.PriorityQueue
This commit is contained in:
288
src/ol/structs/priorityqueue.js
Normal file
288
src/ol/structs/priorityqueue.js
Normal file
@@ -0,0 +1,288 @@
|
|||||||
|
goog.provide('ol.structs.PriorityQueue');
|
||||||
|
|
||||||
|
goog.require('goog.asserts');
|
||||||
|
goog.require('goog.object');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Priority queue.
|
||||||
|
*
|
||||||
|
* The implementation is inspired from the Closure Library's Heap class and
|
||||||
|
* Python's heapq module.
|
||||||
|
*
|
||||||
|
* @see http://closure-library.googlecode.com/svn/docs/closure_goog_structs_heap.js.source.html
|
||||||
|
* @see http://hg.python.org/cpython/file/2.7/Lib/heapq.py
|
||||||
|
*
|
||||||
|
* @constructor
|
||||||
|
* @param {function(?): number} priorityFunction Priority function.
|
||||||
|
* @param {function(?): string} keyFunction Key function.
|
||||||
|
*/
|
||||||
|
ol.structs.PriorityQueue = function(priorityFunction, keyFunction) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {function(?): number}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.priorityFunction_ = priorityFunction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {function(?): string}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.keyFunction_ = keyFunction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {Array}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.elements_ = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {Array.<number>}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.priorities_ = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {Object.<string, boolean>}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.queuedElements_ = {};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @const {number}
|
||||||
|
*/
|
||||||
|
ol.structs.PriorityQueue.DROP = Infinity;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FIXME empty desciption for jsdoc
|
||||||
|
*/
|
||||||
|
ol.structs.PriorityQueue.prototype.assertValid = function() {
|
||||||
|
var elements = this.elements_;
|
||||||
|
var priorities = this.priorities_;
|
||||||
|
var n = elements.length;
|
||||||
|
goog.asserts.assert(priorities.length == n);
|
||||||
|
var i, priority;
|
||||||
|
for (i = 0; i < (n >> 1) - 1; ++i) {
|
||||||
|
priority = priorities[i];
|
||||||
|
goog.asserts.assert(priority <= priorities[this.getLeftChildIndex_(i)]);
|
||||||
|
goog.asserts.assert(priority <= priorities[this.getRightChildIndex_(i)]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FIXME empty description for jsdoc
|
||||||
|
*/
|
||||||
|
ol.structs.PriorityQueue.prototype.clear = function() {
|
||||||
|
this.elements_.length = 0;
|
||||||
|
this.priorities_.length = 0;
|
||||||
|
goog.object.clear(this.queuedElements_);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove and return the highest-priority element. O(log N).
|
||||||
|
* @return {*} Element.
|
||||||
|
*/
|
||||||
|
ol.structs.PriorityQueue.prototype.dequeue = function() {
|
||||||
|
var elements = this.elements_;
|
||||||
|
goog.asserts.assert(elements.length > 0);
|
||||||
|
var priorities = this.priorities_;
|
||||||
|
var element = elements[0];
|
||||||
|
if (elements.length == 1) {
|
||||||
|
elements.length = 0;
|
||||||
|
priorities.length = 0;
|
||||||
|
} else {
|
||||||
|
elements[0] = elements.pop();
|
||||||
|
priorities[0] = priorities.pop();
|
||||||
|
this.siftUp_(0);
|
||||||
|
}
|
||||||
|
var elementKey = this.keyFunction_(element);
|
||||||
|
goog.asserts.assert(elementKey in this.queuedElements_);
|
||||||
|
delete this.queuedElements_[elementKey];
|
||||||
|
return element;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enqueue an element. O(log N).
|
||||||
|
* @param {*} element Element.
|
||||||
|
*/
|
||||||
|
ol.structs.PriorityQueue.prototype.enqueue = function(element) {
|
||||||
|
goog.asserts.assert(!(this.keyFunction_(element) in this.queuedElements_));
|
||||||
|
var priority = this.priorityFunction_(element);
|
||||||
|
if (priority != ol.structs.PriorityQueue.DROP) {
|
||||||
|
this.elements_.push(element);
|
||||||
|
this.priorities_.push(priority);
|
||||||
|
this.queuedElements_[this.keyFunction_(element)] = true;
|
||||||
|
this.siftDown_(0, this.elements_.length - 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {number} Count.
|
||||||
|
*/
|
||||||
|
ol.structs.PriorityQueue.prototype.getCount = function() {
|
||||||
|
return this.elements_.length;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the index of the left child of the node at the given index.
|
||||||
|
* @param {number} index The index of the node to get the left child for.
|
||||||
|
* @return {number} The index of the left child.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
ol.structs.PriorityQueue.prototype.getLeftChildIndex_ = function(index) {
|
||||||
|
return index * 2 + 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the index of the right child of the node at the given index.
|
||||||
|
* @param {number} index The index of the node to get the right child for.
|
||||||
|
* @return {number} The index of the right child.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
ol.structs.PriorityQueue.prototype.getRightChildIndex_ = function(index) {
|
||||||
|
return index * 2 + 2;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the index of the parent of the node at the given index.
|
||||||
|
* @param {number} index The index of the node to get the parent for.
|
||||||
|
* @return {number} The index of the parent.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
ol.structs.PriorityQueue.prototype.getParentIndex_ = function(index) {
|
||||||
|
return (index - 1) >> 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make this a heap. O(N).
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
ol.structs.PriorityQueue.prototype.heapify_ = function() {
|
||||||
|
var i;
|
||||||
|
for (i = (this.elements_.length >> 1) - 1; i >= 0; i--) {
|
||||||
|
this.siftUp_(i);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {boolean} Is empty.
|
||||||
|
*/
|
||||||
|
ol.structs.PriorityQueue.prototype.isEmpty = function() {
|
||||||
|
return this.elements_.length === 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} key Key.
|
||||||
|
* @return {boolean} Is key queued.
|
||||||
|
*/
|
||||||
|
ol.structs.PriorityQueue.prototype.isKeyQueued = function(key) {
|
||||||
|
return key in this.queuedElements_;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {*} element Element.
|
||||||
|
* @return {boolean} Is queued.
|
||||||
|
*/
|
||||||
|
ol.structs.PriorityQueue.prototype.isQueued = function(element) {
|
||||||
|
return this.isKeyQueued(this.keyFunction_(element));
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} index The index of the node to move down.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
ol.structs.PriorityQueue.prototype.siftUp_ = function(index) {
|
||||||
|
var elements = this.elements_;
|
||||||
|
var priorities = this.priorities_;
|
||||||
|
var count = elements.length;
|
||||||
|
var element = elements[index];
|
||||||
|
var priority = priorities[index];
|
||||||
|
var startIndex = index;
|
||||||
|
|
||||||
|
while (index < (count >> 1)) {
|
||||||
|
var lIndex = this.getLeftChildIndex_(index);
|
||||||
|
var rIndex = this.getRightChildIndex_(index);
|
||||||
|
|
||||||
|
var smallerChildIndex = rIndex < count &&
|
||||||
|
priorities[rIndex] < priorities[lIndex] ?
|
||||||
|
rIndex : lIndex;
|
||||||
|
|
||||||
|
elements[index] = elements[smallerChildIndex];
|
||||||
|
priorities[index] = priorities[smallerChildIndex];
|
||||||
|
index = smallerChildIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
elements[index] = element;
|
||||||
|
priorities[index] = priority;
|
||||||
|
this.siftDown_(startIndex, index);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} startIndex The index of the root.
|
||||||
|
* @param {number} index The index of the node to move up.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
ol.structs.PriorityQueue.prototype.siftDown_ = function(startIndex, index) {
|
||||||
|
var elements = this.elements_;
|
||||||
|
var priorities = this.priorities_;
|
||||||
|
var element = elements[index];
|
||||||
|
var priority = priorities[index];
|
||||||
|
|
||||||
|
while (index > startIndex) {
|
||||||
|
var parentIndex = this.getParentIndex_(index);
|
||||||
|
if (priorities[parentIndex] > priority) {
|
||||||
|
elements[index] = elements[parentIndex];
|
||||||
|
priorities[index] = priorities[parentIndex];
|
||||||
|
index = parentIndex;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
elements[index] = element;
|
||||||
|
priorities[index] = priority;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FIXME empty description for jsdoc
|
||||||
|
*/
|
||||||
|
ol.structs.PriorityQueue.prototype.reprioritize = function() {
|
||||||
|
var priorityFunction = this.priorityFunction_;
|
||||||
|
var elements = this.elements_;
|
||||||
|
var priorities = this.priorities_;
|
||||||
|
var index = 0;
|
||||||
|
var n = elements.length;
|
||||||
|
var element, i, priority;
|
||||||
|
for (i = 0; i < n; ++i) {
|
||||||
|
element = elements[i];
|
||||||
|
priority = priorityFunction(element);
|
||||||
|
if (priority == ol.structs.PriorityQueue.DROP) {
|
||||||
|
delete this.queuedElements_[this.keyFunction_(element)];
|
||||||
|
} else {
|
||||||
|
priorities[index] = priority;
|
||||||
|
elements[index++] = element;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
elements.length = index;
|
||||||
|
priorities.length = index;
|
||||||
|
this.heapify_();
|
||||||
|
};
|
||||||
186
test/spec/ol/structs/priorityqueue.test.js
Normal file
186
test/spec/ol/structs/priorityqueue.test.js
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
goog.provide('ol.test.structs.PriorityQueue');
|
||||||
|
|
||||||
|
|
||||||
|
describe('ol.structs.PriorityQueue', function() {
|
||||||
|
|
||||||
|
describe('when empty', function() {
|
||||||
|
|
||||||
|
var pq;
|
||||||
|
beforeEach(function() {
|
||||||
|
pq = new ol.structs.PriorityQueue(
|
||||||
|
goog.identityFunction, goog.identityFunction);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('is valid', function() {
|
||||||
|
expect(function() {
|
||||||
|
pq.assertValid();
|
||||||
|
}).not.to.throwException();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('is empty', function() {
|
||||||
|
expect(pq.isEmpty()).to.be(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('dequeue raises an exception', function() {
|
||||||
|
expect(function() {
|
||||||
|
pq.dequeue();
|
||||||
|
}).to.throwException();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('enqueue adds an element', function() {
|
||||||
|
pq.enqueue(0);
|
||||||
|
expect(function() {
|
||||||
|
pq.assertValid();
|
||||||
|
}).not.to.throwException();
|
||||||
|
expect(pq.elements_).to.equalArray([0]);
|
||||||
|
expect(pq.priorities_).to.equalArray([0]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('maintains the pq property while elements are enqueued', function() {
|
||||||
|
var i;
|
||||||
|
for (i = 0; i < 32; ++i) {
|
||||||
|
pq.enqueue(Math.random());
|
||||||
|
expect(function() {
|
||||||
|
pq.assertValid();
|
||||||
|
}).not.to.throwException();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when populated', function() {
|
||||||
|
|
||||||
|
var elements, pq;
|
||||||
|
beforeEach(function() {
|
||||||
|
elements = [];
|
||||||
|
pq = new ol.structs.PriorityQueue(
|
||||||
|
goog.identityFunction, goog.identityFunction);
|
||||||
|
var element, i;
|
||||||
|
for (i = 0; i < 32; ++i) {
|
||||||
|
element = Math.random();
|
||||||
|
pq.enqueue(element);
|
||||||
|
elements.push(element);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('dequeues elements in the correct order', function() {
|
||||||
|
elements.sort();
|
||||||
|
var i;
|
||||||
|
for (i = 0; i < elements.length; ++i) {
|
||||||
|
expect(pq.dequeue()).to.be(elements[i]);
|
||||||
|
}
|
||||||
|
expect(pq.isEmpty()).to.be(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('with an impure priority function', function() {
|
||||||
|
|
||||||
|
var pq, target;
|
||||||
|
beforeEach(function() {
|
||||||
|
target = 0.5;
|
||||||
|
pq = new ol.structs.PriorityQueue(function(element) {
|
||||||
|
return Math.abs(element - target);
|
||||||
|
}, goog.identityFunction);
|
||||||
|
var element, i;
|
||||||
|
for (i = 0; i < 32; ++i) {
|
||||||
|
pq.enqueue(Math.random());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('dequeue elements in the correct order', function() {
|
||||||
|
var lastDelta = 0;
|
||||||
|
var delta;
|
||||||
|
while (!pq.isEmpty()) {
|
||||||
|
delta = Math.abs(pq.dequeue() - target);
|
||||||
|
expect(lastDelta <= delta).to.be(true);
|
||||||
|
lastDelta = delta;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allows reprioritization', function() {
|
||||||
|
var target = 0.5;
|
||||||
|
pq.reprioritize();
|
||||||
|
var lastDelta = 0;
|
||||||
|
var delta;
|
||||||
|
while (!pq.isEmpty()) {
|
||||||
|
delta = Math.abs(pq.dequeue() - target);
|
||||||
|
expect(lastDelta <= delta).to.be(true);
|
||||||
|
lastDelta = delta;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allows dropping during reprioritization', function() {
|
||||||
|
var target = 0.5;
|
||||||
|
var i = 0;
|
||||||
|
pq.priorityFunction_ = function(element) {
|
||||||
|
if (i++ % 2 === 0) {
|
||||||
|
return Math.abs(element - target);
|
||||||
|
} else {
|
||||||
|
return ol.structs.PriorityQueue.DROP;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
pq.reprioritize();
|
||||||
|
expect(pq.getCount()).to.be(16);
|
||||||
|
var lastDelta = 0;
|
||||||
|
var delta;
|
||||||
|
while (!pq.isEmpty()) {
|
||||||
|
delta = Math.abs(pq.dequeue() - target);
|
||||||
|
expect(lastDelta <= delta).to.be(true);
|
||||||
|
lastDelta = delta;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('tracks elements in the queue', function() {
|
||||||
|
|
||||||
|
var pq;
|
||||||
|
beforeEach(function() {
|
||||||
|
pq = new ol.structs.PriorityQueue(
|
||||||
|
goog.identityFunction, goog.identityFunction);
|
||||||
|
pq.enqueue('a');
|
||||||
|
pq.enqueue('b');
|
||||||
|
pq.enqueue('c');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('tracks which elements have been queued', function() {
|
||||||
|
expect(pq.isQueued('a')).to.be(true);
|
||||||
|
expect(pq.isQueued('b')).to.be(true);
|
||||||
|
expect(pq.isQueued('c')).to.be(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('tracks which elements have not been queued', function() {
|
||||||
|
expect(pq.isQueued('d')).to.be(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('raises an error when an queued element is re-queued', function() {
|
||||||
|
expect(function() {
|
||||||
|
pq.enqueue('a');
|
||||||
|
}).to.throwException();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('tracks which elements have be dequeued', function() {
|
||||||
|
expect(pq.isQueued('a')).to.be(true);
|
||||||
|
expect(pq.isQueued('b')).to.be(true);
|
||||||
|
expect(pq.isQueued('c')).to.be(true);
|
||||||
|
expect(pq.dequeue()).to.be('a');
|
||||||
|
expect(pq.isQueued('a')).to.be(false);
|
||||||
|
expect(pq.isQueued('b')).to.be(true);
|
||||||
|
expect(pq.isQueued('c')).to.be(true);
|
||||||
|
expect(pq.dequeue()).to.be('b');
|
||||||
|
expect(pq.isQueued('a')).to.be(false);
|
||||||
|
expect(pq.isQueued('b')).to.be(false);
|
||||||
|
expect(pq.isQueued('c')).to.be(true);
|
||||||
|
expect(pq.dequeue()).to.be('c');
|
||||||
|
expect(pq.isQueued('a')).to.be(false);
|
||||||
|
expect(pq.isQueued('b')).to.be(false);
|
||||||
|
expect(pq.isQueued('c')).to.be(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
goog.require('ol.structs.PriorityQueue');
|
||||||
Reference in New Issue
Block a user