span-skip-list
Version:
A data structure for calculating running totals in multiple dimensions in O(ln(n)).
299 lines (272 loc) • 10.5 kB
JavaScript
(function() {
var SpanSkipList,
__slice = [].slice;
module.exports = SpanSkipList = (function() {
SpanSkipList.prototype.maxHeight = 8;
SpanSkipList.prototype.probability = .25;
function SpanSkipList() {
var dimensions, index;
dimensions = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
this.dimensions = dimensions;
this.head = this.createNode(this.maxHeight, this.buildZeroDistance());
this.tail = this.createNode(this.maxHeight, this.buildZeroDistance());
index = 0;
while (index < this.maxHeight) {
this.head.next[index] = this.tail;
this.head.distance[index] = this.buildZeroDistance();
index++;
}
}
SpanSkipList.prototype.totalTo = function(target, dimension) {
var index, nextDistanceInTargetDimension, node, totalDistance, _ref;
totalDistance = this.buildZeroDistance();
node = this.head;
index = this.maxHeight - 1;
while (index >= 0) {
while (true) {
if (node.next[index] === this.tail) {
break;
}
nextDistanceInTargetDimension = totalDistance[dimension] + node.distance[index][dimension] + ((_ref = node.next[index].element[dimension]) != null ? _ref : 1);
if (nextDistanceInTargetDimension > target) {
break;
}
this.incrementDistance(totalDistance, node.distance[index]);
this.incrementDistance(totalDistance, node.next[index].element);
node = node.next[index];
}
index--;
}
return totalDistance;
};
SpanSkipList.prototype.splice = function() {
var count, dimension, elements, index;
dimension = arguments[0], index = arguments[1], count = arguments[2], elements = 4 <= arguments.length ? __slice.call(arguments, 3) : [];
return this.spliceArray(dimension, index, count, elements);
};
SpanSkipList.prototype.spliceArray = function(dimension, index, count, elements) {
var i, newNode, nextNode, previous, previousDistances, removedElements;
previous = this.buildPreviousArray();
previousDistances = this.buildPreviousDistancesArray();
nextNode = this.findClosestNode(dimension, index, previous, previousDistances);
removedElements = [];
while (count > 0 && nextNode !== this.tail) {
removedElements.push(nextNode.element);
nextNode = this.removeNode(nextNode, previous, previousDistances);
count--;
}
i = elements.length - 1;
while (i >= 0) {
newNode = this.createNode(this.getRandomNodeHeight(), elements[i]);
this.insertNode(newNode, previous, previousDistances);
i--;
}
return removedElements;
};
SpanSkipList.prototype.getLength = function() {
return this.getElements().length;
};
SpanSkipList.prototype.getElements = function() {
var elements, node;
elements = [];
node = this.head;
while (node.next[0] !== this.tail) {
elements.push(node.next[0].element);
node = node.next[0];
}
return elements;
};
SpanSkipList.prototype.findClosestNode = function(dimension, index, previous, previousDistances) {
var i, nextHopDistance, node, totalDistance, _i, _ref, _ref1;
totalDistance = this.buildZeroDistance();
node = this.head;
for (i = _i = _ref = this.maxHeight - 1; _ref <= 0 ? _i <= 0 : _i >= 0; i = _ref <= 0 ? ++_i : --_i) {
while (true) {
if (node.next[i] === this.tail) {
break;
}
nextHopDistance = ((_ref1 = node.next[i].element[dimension]) != null ? _ref1 : 1) + node.distance[i][dimension];
if (totalDistance[dimension] + nextHopDistance > index) {
break;
}
this.incrementDistance(totalDistance, node.distance[i]);
this.incrementDistance(totalDistance, node.next[i].element);
this.incrementDistance(previousDistances[i], node.distance[i]);
this.incrementDistance(previousDistances[i], node.next[i].element);
node = node.next[i];
}
previous[i] = node;
}
return node.next[0];
};
SpanSkipList.prototype.insertNode = function(node, previous, previousDistances) {
var coveredDistance, level;
coveredDistance = this.buildZeroDistance();
level = 0;
while (level < node.height) {
node.next[level] = previous[level].next[level];
previous[level].next[level] = node;
node.distance[level] = this.subtractDistances(previous[level].distance[level], coveredDistance);
previous[level].distance[level] = this.cloneObject(coveredDistance);
this.incrementDistance(coveredDistance, previousDistances[level]);
level++;
}
level = node.height;
while (level < this.maxHeight) {
this.incrementDistance(previous[level].distance[level], node.element);
level++;
}
};
SpanSkipList.prototype.removeNode = function(node, previous) {
var level;
level = 0;
while (level < node.height) {
previous[level].next[level] = node.next[level];
this.incrementDistance(previous[level].distance[level], node.distance[level]);
level++;
}
level = node.height;
while (level < this.maxHeight) {
this.decrementDistance(previous[level].distance[level], node.element);
level++;
}
return node.next[0];
};
SpanSkipList.prototype.buildPreviousArray = function() {
var index, previous;
previous = new Array(this.maxHeight);
index = 0;
while (index < this.maxHeight) {
previous[index] = this.head;
index++;
}
return previous;
};
SpanSkipList.prototype.buildPreviousDistancesArray = function() {
var distances, index;
distances = new Array(this.maxHeight);
index = 0;
while (index < this.maxHeight) {
distances[index] = this.buildZeroDistance();
index++;
}
return distances;
};
SpanSkipList.prototype.getRandomNodeHeight = function() {
var height;
height = 1;
while (height < this.maxHeight && Math.random() < this.probability) {
height++;
}
return height;
};
SpanSkipList.prototype.buildZeroDistance = function() {
var dimension, _i, _len, _ref;
if (this.zeroDistance == null) {
this.zeroDistance = {
elements: 0
};
_ref = this.dimensions;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
dimension = _ref[_i];
this.zeroDistance[dimension] = 0;
}
}
return this.cloneObject(this.zeroDistance);
};
SpanSkipList.prototype.incrementDistance = function(distance, delta) {
var dimension, _i, _len, _ref, _ref1;
distance.elements += (_ref = delta.elements) != null ? _ref : 1;
_ref1 = this.dimensions;
for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
dimension = _ref1[_i];
distance[dimension] += delta[dimension];
}
};
SpanSkipList.prototype.decrementDistance = function(distance, delta) {
var dimension, _i, _len, _ref, _ref1;
distance.elements -= (_ref = delta.elements) != null ? _ref : 1;
_ref1 = this.dimensions;
for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
dimension = _ref1[_i];
distance[dimension] -= delta[dimension];
}
};
SpanSkipList.prototype.addDistances = function(a, b) {
var dimension, distance, _i, _len, _ref, _ref1, _ref2;
distance = {
elements: ((_ref = a.elements) != null ? _ref : 1) + ((_ref1 = b.elements) != null ? _ref1 : 1)
};
_ref2 = this.dimensions;
for (_i = 0, _len = _ref2.length; _i < _len; _i++) {
dimension = _ref2[_i];
distance[dimension] = a[dimension] + b[dimension];
}
return distance;
};
SpanSkipList.prototype.subtractDistances = function(a, b) {
var dimension, distance, _i, _len, _ref, _ref1, _ref2;
distance = {
elements: ((_ref = a.elements) != null ? _ref : 1) - ((_ref1 = b.elements) != null ? _ref1 : 1)
};
_ref2 = this.dimensions;
for (_i = 0, _len = _ref2.length; _i < _len; _i++) {
dimension = _ref2[_i];
distance[dimension] = a[dimension] - b[dimension];
}
return distance;
};
SpanSkipList.prototype.verifyDistanceInvariant = function() {
var distanceOnPreviousLevel, distanceOnThisLevel, isEqual, level, node, _i, _ref, _results;
isEqual = require('underscore').isEqual;
_results = [];
for (level = _i = _ref = this.maxHeight - 1; _ref <= 1 ? _i <= 1 : _i >= 1; level = _ref <= 1 ? ++_i : --_i) {
node = this.head;
_results.push((function() {
var _results1;
_results1 = [];
while (node !== this.tail) {
distanceOnThisLevel = this.addDistances(node.element, node.distance[level]);
distanceOnPreviousLevel = this.distanceBetweenNodesAtLevel(node, node.next[level], level - 1);
if (!isEqual(distanceOnThisLevel, distanceOnPreviousLevel)) {
console.log(this.inspect());
throw new Error("On level " + level + ": Distance " + (JSON.stringify(distanceOnThisLevel)) + " does not match " + (JSON.stringify(distanceOnPreviousLevel)));
}
_results1.push(node = node.next[level]);
}
return _results1;
}).call(this));
}
return _results;
};
SpanSkipList.prototype.distanceBetweenNodesAtLevel = function(startNode, endNode, level) {
var distance, node;
distance = this.buildZeroDistance();
node = startNode;
while (node !== endNode) {
this.incrementDistance(distance, node.element);
this.incrementDistance(distance, node.distance[level]);
node = node.next[level];
}
return distance;
};
SpanSkipList.prototype.createNode = function(height, element) {
return {
height: height,
element: element,
next: new Array(height),
distance: new Array(height)
};
};
SpanSkipList.prototype.cloneObject = function(object) {
var cloned, key, value;
cloned = {};
for (key in object) {
value = object[key];
cloned[key] = value;
}
return cloned;
};
return SpanSkipList;
})();
}).call(this);