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
			| 
											3 years ago
										 | /* 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; | ||
|  | }; |