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.
		
		
		
		
		
			
		
			
				
					
					
						
							564 lines
						
					
					
						
							11 KiB
						
					
					
				
			
		
		
	
	
							564 lines
						
					
					
						
							11 KiB
						
					
					
				| /* eslint no-constant-condition: 0 */
 | |
| /**
 | |
|  * Mnemonist Merge Helpers
 | |
|  * ========================
 | |
|  *
 | |
|  * Various merge algorithms used to handle sorted lists. Note that the given
 | |
|  * functions are optimized and won't accept mixed arguments.
 | |
|  *
 | |
|  * Note: maybe this piece of code belong to sortilege, along with binary-search.
 | |
|  */
 | |
| var typed = require('./typed-arrays.js'),
 | |
|     isArrayLike = require('./iterables.js').isArrayLike,
 | |
|     binarySearch = require('./binary-search.js'),
 | |
|     FibonacciHeap = require('../fibonacci-heap.js');
 | |
| 
 | |
| // TODO: update to use exponential search
 | |
| // TODO: when not knowing final length => should use plain arrays rather than
 | |
| // same type as input
 | |
| 
 | |
| /**
 | |
|  * Merge two sorted array-like structures into one.
 | |
|  *
 | |
|  * @param  {array} a - First array.
 | |
|  * @param  {array} b - Second array.
 | |
|  * @return {array}
 | |
|  */
 | |
| function mergeArrays(a, b) {
 | |
| 
 | |
|   // One of the arrays is empty
 | |
|   if (a.length === 0)
 | |
|     return b.slice();
 | |
|   if (b.length === 0)
 | |
|     return a.slice();
 | |
| 
 | |
|   // Finding min array
 | |
|   var tmp;
 | |
| 
 | |
|   if (a[0] > b[0]) {
 | |
|     tmp = a;
 | |
|     a = b;
 | |
|     b = tmp;
 | |
|   }
 | |
| 
 | |
|   // If array have non overlapping ranges, we can just concatenate them
 | |
|   var aEnd = a[a.length - 1],
 | |
|       bStart = b[0];
 | |
| 
 | |
|   if (aEnd <= bStart) {
 | |
|     if (typed.isTypedArray(a))
 | |
|       return typed.concat(a, b);
 | |
|     return a.concat(b);
 | |
|   }
 | |
| 
 | |
|   // Initializing target
 | |
|   var array = new a.constructor(a.length + b.length);
 | |
| 
 | |
|   // Iterating until we overlap
 | |
|   var i, l, v;
 | |
| 
 | |
|   for (i = 0, l = a.length; i < l; i++) {
 | |
|     v = a[i];
 | |
| 
 | |
|     if (v <= bStart)
 | |
|       array[i] = v;
 | |
|     else
 | |
|       break;
 | |
|   }
 | |
| 
 | |
|   // Handling overlap
 | |
|   var aPointer = i,
 | |
|       aLength = a.length,
 | |
|       bPointer = 0,
 | |
|       bLength = b.length,
 | |
|       aHead,
 | |
|       bHead;
 | |
| 
 | |
|   while (aPointer < aLength && bPointer < bLength) {
 | |
|     aHead = a[aPointer];
 | |
|     bHead = b[bPointer];
 | |
| 
 | |
|     if (aHead <= bHead) {
 | |
|       array[i++] = aHead;
 | |
|       aPointer++;
 | |
|     }
 | |
|     else {
 | |
|       array[i++] = bHead;
 | |
|       bPointer++;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Filling
 | |
|   while (aPointer < aLength)
 | |
|     array[i++] = a[aPointer++];
 | |
|   while (bPointer < bLength)
 | |
|     array[i++] = b[bPointer++];
 | |
| 
 | |
|   return array;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Perform the union of two already unique sorted array-like structures into one.
 | |
|  *
 | |
|  * @param  {array} a - First array.
 | |
|  * @param  {array} b - Second array.
 | |
|  * @return {array}
 | |
|  */
 | |
| function unionUniqueArrays(a, b) {
 | |
| 
 | |
|   // One of the arrays is empty
 | |
|   if (a.length === 0)
 | |
|     return b.slice();
 | |
|   if (b.length === 0)
 | |
|     return a.slice();
 | |
| 
 | |
|   // Finding min array
 | |
|   var tmp;
 | |
| 
 | |
|   if (a[0] > b[0]) {
 | |
|     tmp = a;
 | |
|     a = b;
 | |
|     b = tmp;
 | |
|   }
 | |
| 
 | |
|   // If array have non overlapping ranges, we can just concatenate them
 | |
|   var aEnd = a[a.length - 1],
 | |
|       bStart = b[0];
 | |
| 
 | |
|   if (aEnd < bStart) {
 | |
|     if (typed.isTypedArray(a))
 | |
|       return typed.concat(a, b);
 | |
|     return a.concat(b);
 | |
|   }
 | |
| 
 | |
|   // Initializing target
 | |
|   var array = new a.constructor();
 | |
| 
 | |
|   // Iterating until we overlap
 | |
|   var i, l, v;
 | |
| 
 | |
|   for (i = 0, l = a.length; i < l; i++) {
 | |
|     v = a[i];
 | |
| 
 | |
|     if (v < bStart)
 | |
|       array.push(v);
 | |
|     else
 | |
|       break;
 | |
|   }
 | |
| 
 | |
|   // Handling overlap
 | |
|   var aPointer = i,
 | |
|       aLength = a.length,
 | |
|       bPointer = 0,
 | |
|       bLength = b.length,
 | |
|       aHead,
 | |
|       bHead;
 | |
| 
 | |
|   while (aPointer < aLength && bPointer < bLength) {
 | |
|     aHead = a[aPointer];
 | |
|     bHead = b[bPointer];
 | |
| 
 | |
|     if (aHead <= bHead) {
 | |
| 
 | |
|       if (array.length === 0 || array[array.length - 1] !== aHead)
 | |
|         array.push(aHead);
 | |
| 
 | |
|       aPointer++;
 | |
|     }
 | |
|     else {
 | |
|       if (array.length === 0 || array[array.length - 1] !== bHead)
 | |
|         array.push(bHead);
 | |
| 
 | |
|       bPointer++;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Filling
 | |
|   // TODO: it's possible to optimize a bit here, since the condition is only
 | |
|   // relevant the first time
 | |
|   while (aPointer < aLength) {
 | |
|     aHead = a[aPointer++];
 | |
| 
 | |
|     if (array.length === 0 || array[array.length - 1] !== aHead)
 | |
|       array.push(aHead);
 | |
|   }
 | |
|   while (bPointer < bLength) {
 | |
|     bHead = b[bPointer++];
 | |
| 
 | |
|     if (array.length === 0 || array[array.length - 1] !== bHead)
 | |
|       array.push(bHead);
 | |
|   }
 | |
| 
 | |
|   return array;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Perform the intersection of two already unique sorted array-like structures into one.
 | |
|  *
 | |
|  * @param  {array} a - First array.
 | |
|  * @param  {array} b - Second array.
 | |
|  * @return {array}
 | |
|  */
 | |
| exports.intersectionUniqueArrays = function(a, b) {
 | |
| 
 | |
|   // One of the arrays is empty
 | |
|   if (a.length === 0 || b.length === 0)
 | |
|     return new a.constructor(0);
 | |
| 
 | |
|   // Finding min array
 | |
|   var tmp;
 | |
| 
 | |
|   if (a[0] > b[0]) {
 | |
|     tmp = a;
 | |
|     a = b;
 | |
|     b = tmp;
 | |
|   }
 | |
| 
 | |
|   // If array have non overlapping ranges, there is no intersection
 | |
|   var aEnd = a[a.length - 1],
 | |
|       bStart = b[0];
 | |
| 
 | |
|   if (aEnd < bStart)
 | |
|     return new a.constructor(0);
 | |
| 
 | |
|   // Initializing target
 | |
|   var array = new a.constructor();
 | |
| 
 | |
|   // Handling overlap
 | |
|   var aPointer = binarySearch.lowerBound(a, bStart),
 | |
|       aLength = a.length,
 | |
|       bPointer = 0,
 | |
|       bLength = binarySearch.upperBound(b, aEnd),
 | |
|       aHead,
 | |
|       bHead;
 | |
| 
 | |
|   while (aPointer < aLength && bPointer < bLength) {
 | |
|     aHead = a[aPointer];
 | |
|     bHead = b[bPointer];
 | |
| 
 | |
|     if (aHead < bHead) {
 | |
|       aPointer = binarySearch.lowerBound(a, bHead, aPointer + 1);
 | |
|     }
 | |
|     else if (aHead > bHead) {
 | |
|       bPointer = binarySearch.lowerBound(b, aHead, bPointer + 1);
 | |
|     }
 | |
|     else {
 | |
|       array.push(aHead);
 | |
|       aPointer++;
 | |
|       bPointer++;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return array;
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Merge k sorted array-like structures into one.
 | |
|  *
 | |
|  * @param  {array<array>} arrays - Arrays to merge.
 | |
|  * @return {array}
 | |
|  */
 | |
| function kWayMergeArrays(arrays) {
 | |
|   var length = 0,
 | |
|       max = -Infinity,
 | |
|       al,
 | |
|       i,
 | |
|       l;
 | |
| 
 | |
|   var filtered = [];
 | |
| 
 | |
|   for (i = 0, l = arrays.length; i < l; i++) {
 | |
|     al = arrays[i].length;
 | |
| 
 | |
|     if (al === 0)
 | |
|       continue;
 | |
| 
 | |
|     filtered.push(arrays[i]);
 | |
| 
 | |
|     length += al;
 | |
| 
 | |
|     if (al > max)
 | |
|       max = al;
 | |
|   }
 | |
| 
 | |
|   if (filtered.length === 0)
 | |
|     return new arrays[0].constructor(0);
 | |
| 
 | |
|   if (filtered.length === 1)
 | |
|     return filtered[0].slice();
 | |
| 
 | |
|   if (filtered.length === 2)
 | |
|     return mergeArrays(filtered[0], filtered[1]);
 | |
| 
 | |
|   arrays = filtered;
 | |
| 
 | |
|   var array = new arrays[0].constructor(length);
 | |
| 
 | |
|   var PointerArray = typed.getPointerArray(max);
 | |
| 
 | |
|   var pointers = new PointerArray(arrays.length);
 | |
| 
 | |
|   // TODO: benchmark vs. a binomial heap
 | |
|   var heap = new FibonacciHeap(function(a, b) {
 | |
|     a = arrays[a][pointers[a]];
 | |
|     b = arrays[b][pointers[b]];
 | |
| 
 | |
|     if (a < b)
 | |
|       return -1;
 | |
| 
 | |
|     if (a > b)
 | |
|       return 1;
 | |
| 
 | |
|     return 0;
 | |
|   });
 | |
| 
 | |
|   for (i = 0; i < l; i++)
 | |
|     heap.push(i);
 | |
| 
 | |
|   i = 0;
 | |
| 
 | |
|   var p,
 | |
|       v;
 | |
| 
 | |
|   while (heap.size) {
 | |
|     p = heap.pop();
 | |
|     v = arrays[p][pointers[p]++];
 | |
|     array[i++] = v;
 | |
| 
 | |
|     if (pointers[p] < arrays[p].length)
 | |
|       heap.push(p);
 | |
|   }
 | |
| 
 | |
|   return array;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Perform the union of k sorted unique array-like structures into one.
 | |
|  *
 | |
|  * @param  {array<array>} arrays - Arrays to merge.
 | |
|  * @return {array}
 | |
|  */
 | |
| function kWayUnionUniqueArrays(arrays) {
 | |
|   var max = -Infinity,
 | |
|       al,
 | |
|       i,
 | |
|       l;
 | |
| 
 | |
|   var filtered = [];
 | |
| 
 | |
|   for (i = 0, l = arrays.length; i < l; i++) {
 | |
|     al = arrays[i].length;
 | |
| 
 | |
|     if (al === 0)
 | |
|       continue;
 | |
| 
 | |
|     filtered.push(arrays[i]);
 | |
| 
 | |
|     if (al > max)
 | |
|       max = al;
 | |
|   }
 | |
| 
 | |
|   if (filtered.length === 0)
 | |
|     return new arrays[0].constructor(0);
 | |
| 
 | |
|   if (filtered.length === 1)
 | |
|     return filtered[0].slice();
 | |
| 
 | |
|   if (filtered.length === 2)
 | |
|     return unionUniqueArrays(filtered[0], filtered[1]);
 | |
| 
 | |
|   arrays = filtered;
 | |
| 
 | |
|   var array = new arrays[0].constructor();
 | |
| 
 | |
|   var PointerArray = typed.getPointerArray(max);
 | |
| 
 | |
|   var pointers = new PointerArray(arrays.length);
 | |
| 
 | |
|   // TODO: benchmark vs. a binomial heap
 | |
|   var heap = new FibonacciHeap(function(a, b) {
 | |
|     a = arrays[a][pointers[a]];
 | |
|     b = arrays[b][pointers[b]];
 | |
| 
 | |
|     if (a < b)
 | |
|       return -1;
 | |
| 
 | |
|     if (a > b)
 | |
|       return 1;
 | |
| 
 | |
|     return 0;
 | |
|   });
 | |
| 
 | |
|   for (i = 0; i < l; i++)
 | |
|     heap.push(i);
 | |
| 
 | |
|   var p,
 | |
|       v;
 | |
| 
 | |
|   while (heap.size) {
 | |
|     p = heap.pop();
 | |
|     v = arrays[p][pointers[p]++];
 | |
| 
 | |
|     if (array.length === 0 || array[array.length - 1] !== v)
 | |
|       array.push(v);
 | |
| 
 | |
|     if (pointers[p] < arrays[p].length)
 | |
|       heap.push(p);
 | |
|   }
 | |
| 
 | |
|   return array;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Perform the intersection of k sorted array-like structures into one.
 | |
|  *
 | |
|  * @param  {array<array>} arrays - Arrays to merge.
 | |
|  * @return {array}
 | |
|  */
 | |
| exports.kWayIntersectionUniqueArrays = function(arrays) {
 | |
|   var max = -Infinity,
 | |
|       maxStart = -Infinity,
 | |
|       minEnd = Infinity,
 | |
|       first,
 | |
|       last,
 | |
|       al,
 | |
|       i,
 | |
|       l;
 | |
| 
 | |
|   for (i = 0, l = arrays.length; i < l; i++) {
 | |
|     al = arrays[i].length;
 | |
| 
 | |
|     // If one of the arrays is empty, so is the intersection
 | |
|     if (al === 0)
 | |
|       return [];
 | |
| 
 | |
|     if (al > max)
 | |
|       max = al;
 | |
| 
 | |
|     first = arrays[i][0];
 | |
|     last = arrays[i][al - 1];
 | |
| 
 | |
|     if (first > maxStart)
 | |
|       maxStart = first;
 | |
| 
 | |
|     if (last < minEnd)
 | |
|       minEnd = last;
 | |
|   }
 | |
| 
 | |
|   // Full overlap is impossible
 | |
|   if (maxStart > minEnd)
 | |
|     return [];
 | |
| 
 | |
|   // Only one value
 | |
|   if (maxStart === minEnd)
 | |
|     return [maxStart];
 | |
| 
 | |
|   // NOTE: trying to outsmart I(D,I(C,I(A,B))) is pointless unfortunately...
 | |
|   // NOTE: I tried to be very clever about bounds but it does not seem
 | |
|   // to improve the performance of the algorithm.
 | |
|   var a, b,
 | |
|       array = arrays[0],
 | |
|       aPointer,
 | |
|       bPointer,
 | |
|       aLimit,
 | |
|       bLimit,
 | |
|       aHead,
 | |
|       bHead,
 | |
|       start = maxStart;
 | |
| 
 | |
|   for (i = 1; i < l; i++) {
 | |
|     a = array;
 | |
|     b = arrays[i];
 | |
| 
 | |
|     // Change that to `[]` and observe some perf drops on V8...
 | |
|     array = new Array();
 | |
| 
 | |
|     aPointer = 0;
 | |
|     bPointer = binarySearch.lowerBound(b, start);
 | |
| 
 | |
|     aLimit = a.length;
 | |
|     bLimit = b.length;
 | |
| 
 | |
|     while (aPointer < aLimit && bPointer < bLimit) {
 | |
|       aHead = a[aPointer];
 | |
|       bHead = b[bPointer];
 | |
| 
 | |
|       if (aHead < bHead) {
 | |
|         aPointer = binarySearch.lowerBound(a, bHead, aPointer + 1);
 | |
|       }
 | |
|       else if (aHead > bHead) {
 | |
|         bPointer = binarySearch.lowerBound(b, aHead, bPointer + 1);
 | |
|       }
 | |
|       else {
 | |
|         array.push(aHead);
 | |
|         aPointer++;
 | |
|         bPointer++;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (array.length === 0)
 | |
|       return array;
 | |
| 
 | |
|     start = array[0];
 | |
|   }
 | |
| 
 | |
|   return array;
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Variadic merging all of the given arrays.
 | |
|  *
 | |
|  * @param  {...array}
 | |
|  * @return {array}
 | |
|  */
 | |
| exports.merge = function() {
 | |
|   if (arguments.length === 2) {
 | |
|     if (isArrayLike(arguments[0]))
 | |
|       return mergeArrays(arguments[0], arguments[1]);
 | |
|   }
 | |
|   else {
 | |
|     if (isArrayLike(arguments[0]))
 | |
|       return kWayMergeArrays(arguments);
 | |
|   }
 | |
| 
 | |
|   return null;
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Variadic function performing the union of all the given unique arrays.
 | |
|  *
 | |
|  * @param  {...array}
 | |
|  * @return {array}
 | |
|  */
 | |
| exports.unionUnique = function() {
 | |
|   if (arguments.length === 2) {
 | |
|     if (isArrayLike(arguments[0]))
 | |
|       return unionUniqueArrays(arguments[0], arguments[1]);
 | |
|   }
 | |
|   else {
 | |
|     if (isArrayLike(arguments[0]))
 | |
|       return kWayUnionUniqueArrays(arguments);
 | |
|   }
 | |
| 
 | |
|   return null;
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Variadic function performing the intersection of all the given unique arrays.
 | |
|  *
 | |
|  * @param  {...array}
 | |
|  * @return {array}
 | |
|  */
 | |
| exports.intersectionUnique = function() {
 | |
|   if (arguments.length === 2) {
 | |
|     if (isArrayLike(arguments[0]))
 | |
|       return exports.intersectionUniqueArrays(arguments[0], arguments[1]);
 | |
|   }
 | |
|   else {
 | |
|     if (isArrayLike(arguments[0]))
 | |
|       return exports.kWayIntersectionUniqueArrays(arguments);
 | |
|   }
 | |
| 
 | |
|   return null;
 | |
| };
 |