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;
 | 
						|
};
 |