UNPKG

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
(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);