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