You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							388 lines
						
					
					
						
							8.9 KiB
						
					
					
				
			
		
		
	
	
							388 lines
						
					
					
						
							8.9 KiB
						
					
					
				| /*
 | |
|  * Mnemonist StaticIntervalTree
 | |
|  * =============================
 | |
|  *
 | |
|  * JavaScript implementation of a static interval tree. This tree is static in
 | |
|  * that you are required to know all its items beforehand and to built it
 | |
|  * from an iterable.
 | |
|  *
 | |
|  * This implementation represents the interval tree as an augmented balanced
 | |
|  * binary search tree. It works by sorting the intervals by startpoint first
 | |
|  * then proceeds building the augmented balanced BST bottom-up from the
 | |
|  * sorted list.
 | |
|  *
 | |
|  * Note that this implementation considers every given intervals as closed for
 | |
|  * simplicity's sake.
 | |
|  *
 | |
|  * For more information: https://en.wikipedia.org/wiki/Interval_tree
 | |
|  */
 | |
| var iterables = require('./utils/iterables.js'),
 | |
|     typed = require('./utils/typed-arrays.js');
 | |
| 
 | |
| var FixedStack = require('./fixed-stack.js');
 | |
| 
 | |
| 
 | |
| // TODO: pass index to getters
 | |
| // TODO: custom comparison
 | |
| // TODO: possibility to pass offset buffer
 | |
| 
 | |
| // TODO: intervals() => Symbol.iterator
 | |
| // TODO: dfs()
 | |
| 
 | |
| /**
 | |
|  * Helpers.
 | |
|  */
 | |
| 
 | |
| /**
 | |
|  * Recursive function building the BST from the sorted list of interval
 | |
|  * indices.
 | |
|  *
 | |
|  * @param  {array}    intervals     - Array of intervals to index.
 | |
|  * @param  {function} endGetter     - Getter function for end of intervals.
 | |
|  * @param  {array}    sortedIndices - Sorted indices of the intervals.
 | |
|  * @param  {array}    tree          - BST memory.
 | |
|  * @param  {array}    augmentations - Array of node augmentations.
 | |
|  * @param  {number}   i             - BST index of current node.
 | |
|  * @param  {number}   low           - Dichotomy low index.
 | |
|  * @param  {number}   high          - Dichotomy high index.
 | |
|  * @return {number}                 - Created node augmentation value.
 | |
|  */
 | |
| function buildBST(
 | |
|   intervals,
 | |
|   endGetter,
 | |
|   sortedIndices,
 | |
|   tree,
 | |
|   augmentations,
 | |
|   i,
 | |
|   low,
 | |
|   high
 | |
| ) {
 | |
|   var mid = (low + (high - low) / 2) | 0,
 | |
|       midMinusOne = ~-mid,
 | |
|       midPlusOne = -~mid;
 | |
| 
 | |
|   var current = sortedIndices[mid];
 | |
|   tree[i] = current + 1;
 | |
| 
 | |
|   var end = endGetter ? endGetter(intervals[current]) : intervals[current][1];
 | |
| 
 | |
|   var left = i * 2 + 1,
 | |
|       right = i * 2 + 2;
 | |
| 
 | |
|   var leftEnd = -Infinity,
 | |
|       rightEnd = -Infinity;
 | |
| 
 | |
|   if (low <= midMinusOne) {
 | |
|     leftEnd = buildBST(
 | |
|       intervals,
 | |
|       endGetter,
 | |
|       sortedIndices,
 | |
|       tree,
 | |
|       augmentations,
 | |
|       left,
 | |
|       low,
 | |
|       midMinusOne
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   if (midPlusOne <= high) {
 | |
|     rightEnd = buildBST(
 | |
|       intervals,
 | |
|       endGetter,
 | |
|       sortedIndices,
 | |
|       tree,
 | |
|       augmentations,
 | |
|       right,
 | |
|       midPlusOne,
 | |
|       high
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   var augmentation = Math.max(end, leftEnd, rightEnd);
 | |
| 
 | |
|   var augmentationPointer = current;
 | |
| 
 | |
|   if (augmentation === leftEnd)
 | |
|     augmentationPointer = augmentations[tree[left] - 1];
 | |
|   else if (augmentation === rightEnd)
 | |
|     augmentationPointer = augmentations[tree[right] - 1];
 | |
| 
 | |
|   augmentations[current] = augmentationPointer;
 | |
| 
 | |
|   return augmentation;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * StaticIntervalTree.
 | |
|  *
 | |
|  * @constructor
 | |
|  * @param {array}           intervals - Array of intervals to index.
 | |
|  * @param {array<function>} getters   - Optional getters.
 | |
|  */
 | |
| function StaticIntervalTree(intervals, getters) {
 | |
| 
 | |
|   // Properties
 | |
|   this.size = intervals.length;
 | |
|   this.intervals = intervals;
 | |
| 
 | |
|   var startGetter = null,
 | |
|       endGetter = null;
 | |
| 
 | |
|   if (Array.isArray(getters)) {
 | |
|     startGetter = getters[0];
 | |
|     endGetter = getters[1];
 | |
|   }
 | |
| 
 | |
|   // Building the indices array
 | |
|   var length = intervals.length;
 | |
| 
 | |
|   var IndicesArray = typed.getPointerArray(length + 1);
 | |
| 
 | |
|   var indices = new IndicesArray(length);
 | |
| 
 | |
|   var i;
 | |
| 
 | |
|   for (i = 1; i < length; i++)
 | |
|     indices[i] = i;
 | |
| 
 | |
|   // Sorting indices array
 | |
|   // TODO: check if some version of radix sort can outperform this part
 | |
|   indices.sort(function(a, b) {
 | |
|     a = intervals[a];
 | |
|     b = intervals[b];
 | |
| 
 | |
|     if (startGetter) {
 | |
|       a = startGetter(a);
 | |
|       b = startGetter(b);
 | |
|     }
 | |
|     else {
 | |
|       a = a[0];
 | |
|       b = b[0];
 | |
|     }
 | |
| 
 | |
|     if (a < b)
 | |
|       return -1;
 | |
| 
 | |
|     if (a > b)
 | |
|       return 1;
 | |
| 
 | |
|     // TODO: use getters
 | |
|     // TODO: this ordering has the following invariant: if query interval
 | |
|     // contains [nodeStart, max], then whole right subtree can be collected
 | |
|     // a = a[1];
 | |
|     // b = b[1];
 | |
| 
 | |
|     // if (a < b)
 | |
|     //   return 1;
 | |
| 
 | |
|     // if (a > b)
 | |
|     //   return -1;
 | |
| 
 | |
|     return 0;
 | |
|   });
 | |
| 
 | |
|   // Building the binary tree
 | |
|   var height = Math.ceil(Math.log2(length + 1)),
 | |
|       treeSize = Math.pow(2, height) - 1;
 | |
| 
 | |
|   var tree = new IndicesArray(treeSize);
 | |
| 
 | |
|   var augmentations = new IndicesArray(length);
 | |
| 
 | |
|   buildBST(
 | |
|     intervals,
 | |
|     endGetter,
 | |
|     indices,
 | |
|     tree,
 | |
|     augmentations,
 | |
|     0,
 | |
|     0,
 | |
|     length - 1
 | |
|   );
 | |
| 
 | |
|   // Dropping indices
 | |
|   indices = null;
 | |
| 
 | |
|   // Storing necessary information
 | |
|   this.height = height;
 | |
|   this.tree = tree;
 | |
|   this.augmentations = augmentations;
 | |
|   this.startGetter = startGetter;
 | |
|   this.endGetter = endGetter;
 | |
| 
 | |
|   // Initializing DFS stack
 | |
|   this.stack = new FixedStack(IndicesArray, this.height);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Method returning a list of intervals containing the given point.
 | |
|  *
 | |
|  * @param  {any}   point - Target point.
 | |
|  * @return {array}
 | |
|  */
 | |
| StaticIntervalTree.prototype.intervalsContainingPoint = function(point) {
 | |
|   var matches = [];
 | |
| 
 | |
|   var stack = this.stack;
 | |
| 
 | |
|   stack.clear();
 | |
|   stack.push(0);
 | |
| 
 | |
|   var l = this.tree.length;
 | |
| 
 | |
|   var bstIndex,
 | |
|       intervalIndex,
 | |
|       interval,
 | |
|       maxInterval,
 | |
|       start,
 | |
|       end,
 | |
|       max,
 | |
|       left,
 | |
|       right;
 | |
| 
 | |
|   while (stack.size) {
 | |
|     bstIndex = stack.pop();
 | |
|     intervalIndex = this.tree[bstIndex] - 1;
 | |
|     interval = this.intervals[intervalIndex];
 | |
|     maxInterval = this.intervals[this.augmentations[intervalIndex]];
 | |
| 
 | |
|     max = this.endGetter ? this.endGetter(maxInterval) : maxInterval[1];
 | |
| 
 | |
|     // No possible match, point is farther right than the max end value
 | |
|     if (point > max)
 | |
|       continue;
 | |
| 
 | |
|     // Searching left
 | |
|     left = bstIndex * 2 + 1;
 | |
| 
 | |
|     if (left < l && this.tree[left] !== 0)
 | |
|       stack.push(left);
 | |
| 
 | |
|     start = this.startGetter ? this.startGetter(interval) : interval[0];
 | |
|     end = this.endGetter ? this.endGetter(interval) : interval[1];
 | |
| 
 | |
|     // Checking current node
 | |
|     if (point >= start && point <= end)
 | |
|       matches.push(interval);
 | |
| 
 | |
|     // If the point is to the left of the start of the current interval,
 | |
|     // then it cannot be in the right child
 | |
|     if (point < start)
 | |
|       continue;
 | |
| 
 | |
|     // Searching right
 | |
|     right = bstIndex * 2 + 2;
 | |
| 
 | |
|     if (right < l && this.tree[right] !== 0)
 | |
|       stack.push(right);
 | |
|   }
 | |
| 
 | |
|   return matches;
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Method returning a list of intervals overlapping the given interval.
 | |
|  *
 | |
|  * @param  {any}   interval - Target interval.
 | |
|  * @return {array}
 | |
|  */
 | |
| StaticIntervalTree.prototype.intervalsOverlappingInterval = function(interval) {
 | |
|   var intervalStart = this.startGetter ? this.startGetter(interval) : interval[0],
 | |
|       intervalEnd = this.endGetter ? this.endGetter(interval) : interval[1];
 | |
| 
 | |
|   var matches = [];
 | |
| 
 | |
|   var stack = this.stack;
 | |
| 
 | |
|   stack.clear();
 | |
|   stack.push(0);
 | |
| 
 | |
|   var l = this.tree.length;
 | |
| 
 | |
|   var bstIndex,
 | |
|       intervalIndex,
 | |
|       currentInterval,
 | |
|       maxInterval,
 | |
|       start,
 | |
|       end,
 | |
|       max,
 | |
|       left,
 | |
|       right;
 | |
| 
 | |
|   while (stack.size) {
 | |
|     bstIndex = stack.pop();
 | |
|     intervalIndex = this.tree[bstIndex] - 1;
 | |
|     currentInterval = this.intervals[intervalIndex];
 | |
|     maxInterval = this.intervals[this.augmentations[intervalIndex]];
 | |
| 
 | |
|     max = this.endGetter ? this.endGetter(maxInterval) : maxInterval[1];
 | |
| 
 | |
|     // No possible match, start is farther right than the max end value
 | |
|     if (intervalStart > max)
 | |
|       continue;
 | |
| 
 | |
|     // Searching left
 | |
|     left = bstIndex * 2 + 1;
 | |
| 
 | |
|     if (left < l && this.tree[left] !== 0)
 | |
|       stack.push(left);
 | |
| 
 | |
|     start = this.startGetter ? this.startGetter(currentInterval) : currentInterval[0];
 | |
|     end = this.endGetter ? this.endGetter(currentInterval) : currentInterval[1];
 | |
| 
 | |
|     // Checking current node
 | |
|     if (intervalEnd >= start && intervalStart <= end)
 | |
|       matches.push(currentInterval);
 | |
| 
 | |
|     // If the end is to the left of the start of the current interval,
 | |
|     // then it cannot be in the right child
 | |
|     if (intervalEnd < start)
 | |
|       continue;
 | |
| 
 | |
|     // Searching right
 | |
|     right = bstIndex * 2 + 2;
 | |
| 
 | |
|     if (right < l && this.tree[right] !== 0)
 | |
|       stack.push(right);
 | |
|   }
 | |
| 
 | |
|   return matches;
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Convenience known methods.
 | |
|  */
 | |
| StaticIntervalTree.prototype.inspect = function() {
 | |
|   var proxy = this.intervals.slice();
 | |
| 
 | |
|   // Trick so that node displays the name of the constructor
 | |
|   Object.defineProperty(proxy, 'constructor', {
 | |
|     value: StaticIntervalTree,
 | |
|     enumerable: false
 | |
|   });
 | |
| 
 | |
|   return proxy;
 | |
| };
 | |
| 
 | |
| if (typeof Symbol !== 'undefined')
 | |
|   StaticIntervalTree.prototype[Symbol.for('nodejs.util.inspect.custom')] = StaticIntervalTree.prototype.inspect;
 | |
| 
 | |
| /**
 | |
|  * Static @.from function taking an arbitrary iterable & converting it into
 | |
|  * a structure.
 | |
|  *
 | |
|  * @param  {Iterable} iterable - Target iterable.
 | |
|  * @return {StaticIntervalTree}
 | |
|  */
 | |
| StaticIntervalTree.from = function(iterable, getters) {
 | |
|   if (iterables.isArrayLike(iterable))
 | |
|     return new StaticIntervalTree(iterable, getters);
 | |
| 
 | |
|   return new StaticIntervalTree(Array.from(iterable), getters);
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Exporting.
 | |
|  */
 | |
| module.exports = StaticIntervalTree;
 |