serverless-spy
Version:
CDK-based library for writing elegant integration tests on AWS serverless architecture and an additional web console to monitor events in real time.
564 lines (444 loc) • 11.2 kB
JavaScript
/* 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;
};