diff --git a/package-lock.json b/package-lock.json index 709bccd75..1779557fb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "extra-array", - "version": "4.0.2", + "version": "4.0.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "extra-array", - "version": "4.0.2", + "version": "4.0.3", "license": "MIT", "devDependencies": { "@rollup/plugin-commonjs": "^24.1.0", diff --git a/package.json b/package.json index 0260fc9dc..51019955e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "extra-array", - "version": "4.0.2", + "version": "4.0.3", "description": "An array is a collection of values, stored contiguously.", "main": "index.js", "module": "index.mjs", diff --git a/src/index.ts b/src/index.ts index 9b79fb9ca..beaf49409 100644 --- a/src/index.ts +++ b/src/index.ts @@ -130,28 +130,6 @@ function toSet(x: T[], fm: MapFunction | null=null): Set { // #region GENERATE // ---------------- -/** - * Convert an iterable to array. - * @param x an iterable - * @returns x as array - */ -export function fromIterable(x: Iterable): T[] { - return [...x]; -} -export {fromIterable as from}; - - -/** - * Convert an iterable to array! - * @param x an iterable (updatable if array!) - * @returns x as array - */ -export function fromIterable$(x: Iterable): T[] { - return Array.isArray(x)? x : [...x]; -} -export {fromIterable$ as from$}; - - /** * Generate array from given number range. * @param v start number @@ -197,6 +175,28 @@ export function fromApplication(fm: MapFunction, v: T, n: number): T[] return a; } export {fromApplication as fromApply}; + + +/** + * Convert an iterable to array. + * @param x an iterable + * @returns x as array + */ +export function fromIterable(x: Iterable): T[] { + return [...x]; +} +export {fromIterable as from}; + + +/** + * Convert an iterable to array! + * @param x an iterable (updatable if array!) + * @returns x as array + */ +export function fromIterable$(x: Iterable): T[] { + return Array.isArray(x)? x : [...x]; +} +export {fromIterable$ as from$}; // #endregion @@ -242,18 +242,6 @@ export function is(v: any): v is any[] { } -/** - * Examine if array is sorted. - * @param x an array - * @param fc compare function (a, b) - * @param fm map function (v, i, x) - * @returns x is sorted? - */ -export function isSorted(x: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): boolean { - return searchUnsortedValue(x, fc, fm) === -1; -} - - /** * Obtain all indices. * @param x an array @@ -317,8 +305,8 @@ export function ientries(x: T[]): IEntries { -// #region LENGTH -// -------------- +// #region INDEX +// ------------- /** * Get zero-based index for an element in array. @@ -345,6 +333,22 @@ export function indexRange(x: T[], i: number=0, I: number=x.length): [number, var I = I>=0? Math.min(I, X) : Math.max(X+I, 0); return [i, Math.max(i, I)]; } +// #endregion + + + + +// #region LENGTH +// -------------- + +/** + * Check if an array is empty. + * @param x an array + * @returns |x| = 0? + */ +export function isEmpty(x: T[]): boolean { + return x.length===0; +} /** @@ -361,16 +365,6 @@ export function length(x: T[], i: number=0, I: number=x.length): number { export {length as size}; -/** - * Check if an array is empty. - * @param x an array - * @returns |x| = 0? - */ -export function isEmpty(x: T[]): boolean { - return x.length===0; -} - - /** * Resize an array to given length! * @param x an array @@ -621,2859 +615,2877 @@ export function removePath$(x: any[], p: number[]): any[] { -// #region PROPERTY -// ---------------- - -/** - * Count values which satisfy a test. - * @param x an array - * @param ft test function (v, i, x) - * @returns Σtᵢ | tᵢ = 1 if ft(vᵢ) else 0; vᵢ ∈ x - */ -export function count(x: T[], ft: TestFunction): number { - var i = -1, a = 0; - for (var v of x) - if (ft(v, ++i, x)) ++a; - return a; -} - +// #region SORT +// ------------ /** - * Count occurrences of each distinct value. + * Examine if array is sorted. * @param x an array + * @param fc compare function (a, b) * @param fm map function (v, i, x) - * @returns Map \{value ⇒ count\} + * @returns x is sorted? */ -export function countEach(x: T[], fm: MapFunction | null=null): Map { - var fm = fm || IDENTITY; - var i = -1, a = new Map(); - for (var v of x) { - var w = fm(v, ++i, x); - a.set(w, (a.get(w) || 0) + 1); - } - return a; +export function isSorted(x: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): boolean { + return searchUnsortedValue(x, fc, fm) === -1; } -export {countEach as countAs}; // DEPRECATED /** - * Find first smallest value. + * Examine if array has an unsorted value. * @param x an array * @param fc compare function (a, b) * @param fm map function (v, i, x) - * @returns v | v ≤ vᵢ; vᵢ ∈ x + * @returns x is not sorted? */ -export function minimum(x: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): T { - var i = searchMinimumValue(x, fc, fm); - return x[i]; +export function hasUnsortedValue(x: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): boolean { + return searchUnsortedValue(x, fc, fm) >= 0; } -export {minimum as min}; /** - * Find first smallest entry. + * Find first index of an unsorted value. * @param x an array * @param fc compare function (a, b) * @param fm map function (v, i, x) - * @returns [min_index, min_value] + * @returns index of first unsorted value, -1 if sorted */ -export function minimumEntry(x: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): [number, T] { - var i = searchMinimumValue(x, fc, fm); - return [i, x[i]]; +export function searchUnsortedValue(x: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): number { + var fc = fc || COMPARE; + var fm = fm || IDENTITY; + var X = x.length; + if (X<=1) return -1; + var w0 = fm(x[0], 0, x); + for (var i=1; i0) return i; + w0 = w; + } + return -1; } -export {minimumEntry as minEntry}; /** - * Find first largest value. + * Arrange values in order. * @param x an array * @param fc compare function (a, b) * @param fm map function (v, i, x) - * @returns v | v ≥ vᵢ; vᵢ ∈ x + * @param fs swap function (x, i, j) + * @returns x' | x' = x; x'[i] ≤ x'[j] ∀ i ≤ j */ -export function maximum(x: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): T { - var i = searchMaximumValue(x, fc, fm); - return x[i]; +export function sort(x: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null, fs: SwapFunction | null=null): T[] { + return sort$(x.slice(), fc, fm, fs); } -export {maximum as max}; +export {sort as toSorted}; /** - * Find first largest entry. - * @param x an array + * Arrange values in order! + * @param x an array (updated!) * @param fc compare function (a, b) * @param fm map function (v, i, x) - * @returns [max_index, max_value] + * @param fs swap function (x, i, j) + * @returns x | x[i] ≤ x[j] ∀ i ≤ j */ -export function maximumEntry(x: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): [number, T] { - var i = searchMaximumValue(x, fc, fm); - return [i, x[i]]; +export function sort$(x: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null, fs: SwapFunction | null=null): T[] { + var fc = fc || COMPARE; + if (!fm) return x.sort(fc); + var X = x.length; + var fm = fm || IDENTITY; + var fs = fs || swapRaw$; + return partialIntroSort$(x, 0, X, X, fc, fm, fs); } -export {maximumEntry as maxEntry}; /** - * Find smallest and largest values. + * Partially arrange values in order. * @param x an array + * @param i start index + * @param I end index (exclusive) + * @param n minimum number of values to sort * @param fc compare function (a, b) * @param fm map function (v, i, x) - * @returns [min_value, max_value] + * @param fs swap function (x, i, j) + * @returns x' | x' = x; x'[i] ≤ x'[j] ∀ i ≤ j */ -export function range(x: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): [T, T] { - var [a, b] = rangeEntries(x, fc, fm); - return [a[1], b[1]]; +export function partialSort(x: T[], i: number, I: number, n: number, fc: CompareFunction | null=null, fm: MapFunction | null=null, fs: SwapFunction | null=null): T[] { + return partialSort$(x.slice(), i, I, n, fc, fm, fs); } /** - * Find smallest and largest entries. - * @param x an array + * Partially arrange values in order! + * @param x an array (updated!) + * @param i start index + * @param I end index (exclusive) + * @param n minimum number of values to sort * @param fc compare function (a, b) * @param fm map function (v, i, x) - * @returns [min_entry, max_entry] + * @param fs swap function (x, i, j) + * @returns x | x[i] ≤ x[j] ∀ i ≤ j */ -export function rangeEntries(x: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): [[number, T], [number, T]] { +export function partialSort$(x: T[], i: number, I: number, n: number, fc: CompareFunction | null=null, fm: MapFunction | null=null, fs: SwapFunction | null=null): T[] { var fc = fc || COMPARE; var fm = fm || IDENTITY; - var X = x.length; - if (X===0) return [[-1, undefined], [-1, undefined]]; - var v = x[0], w = fm(v, 0, x); - var mi = 0, mv = v, mw = w; - var ni = 0, nv = v, nw = w; - for (var i=1; i0) { ni = i; nv = v; nw = w; } - } - return [[mi, mv], [ni, nv]]; + var fs = fs || swapRaw$; + // TODO: Check various sort functions. + return partialIntroSort$(x, i, I, n, fc, fm, fs); } /** - * Find smallest values. - * @param x an array - * @param n number of values + * Partially arrange values in order! + * @param x an array (updated!) + * @param i start index + * @param I end index (exclusive) + * @param n minimum number of values to sort * @param fc compare function (a, b) * @param fm map function (v, i, x) - * @returns n smallest values in ascending order + * @param fs swap function (x, i, j) + * @returns x | x[i] ≤ x[j] ∀ i ≤ j */ -export function minimums(x: T[], n: number, fc: CompareFunction | null=null, fm: MapFunction | null=null): T[] { - var is = searchMinimumValues(x, n, fc, fm); - return is.map(i => x[i]); +function partialIntroSort$(x: T[], i: number, I: number, n: number, fc: CompareFunction, fm: MapFunction, fs: SwapFunction): T[] { + var d = Math.floor(Math.log2(I-i)*2); // Maximum depth of recursion + var s = 16; // When to switch to insertion sort + return partialIntroSortHelper$(x, i, I, d, s, n, fc, fm, fs); +} + + +// Partially arrange values in order with hybrid quick sort, heap sort, and insertion sort. +function partialIntroSortHelper$(x: T[], i: number, I: number, d: number, s: number, n: number, fc: CompareFunction, fm: MapFunction, fs: SwapFunction): T[] { + if (n<=0 || I-i<=1) return x; // Nothing to sort + if (I-i<=s) return partialInsertionSort$(x, i, I, n, fc, fm, fs); // Insertion sort + if (d<=0) return partialHeapSort$(x, i, I, n, fc, fm, fs); // Heap sort + var p = i + Math.floor((I-i)*Math.random()); // Choose pivot + var p = quickSortPartition$(x, i, I, p, fc, fm, fs); // Partition array + partialIntroSortHelper$(x, i, p, d, s, Math.min(p-i, n), fc, fm, fs); // Sort left part + partialIntroSortHelper$(x, p+1, I, d, s, Math.min(I-p-1, n), fc, fm, fs); // Sort right part + return x; } /** - * Find smallest entries. - * @param x an array - * @param n number of values - * @param fc compare function (a, b) - * @param fm map function (v, i, x) - * @returns n smallest entries in ascending order - */ -export function minimumEntries(x: T[], n: number, fc: CompareFunction | null=null, fm: MapFunction | null=null): [number, T][] { - var is = searchMinimumValues(x, n, fc, fm); - return is.map(i => [i, x[i]]); -} - - -/** - * Find largest values. - * @param x an array - * @param n number of values + * Partially arrange values in order! + * @param x an array (updated!) + * @param i start index + * @param I end index (exclusive) + * @param n minimum number of values to sort * @param fc compare function (a, b) * @param fm map function (v, i, x) - * @returns n largest values in descending order + * @param fs swap function (x, i, j) + * @returns x | x[i] ≤ x[j] ∀ i ≤ j */ -export function maximums(x: T[], n: number, fc: CompareFunction | null=null, fm: MapFunction | null=null): T[] { - var is = searchMaximumValues(x, n, fc, fm); - return is.map(i => x[i]); +function partialQuickSort$(x: T[], i: number, I: number, n: number, fc: CompareFunction, fm: MapFunction, fs: SwapFunction): T[] { + if (n<=0 || I-i<=1) return x; // Nothing to sort + var p = i + Math.floor((I-i)*Math.random()); // Choose pivot + var p = quickSortPartition$(x, i, I, p, fc, fm, fs); // Partition array + partialQuickSort$(x, i, p, Math.min(p-i, n), fc, fm, fs); // Sort left part + partialQuickSort$(x, p+1, I, Math.min(I-p-1, n), fc, fm, fs); // Sort right part + return x; +} + + +// TODO: Make this a generic function. +// Partition the array into two parts, such that values in the first part are less than values in the second part. +function quickSortPartition$(x: T[], i: number, I: number, p: number, fc: CompareFunction, fm: MapFunction, fs: SwapFunction): number { + var wp = fm(x[p], p, x); // Pivot value + var j = i-1; // Last index of values ≤ pivot + fs(x, p, I-1); // Move pivot to end + for (var k=i; k 0) continue; + fs(x, ++j, k); // Move value ≤ pivot to left + } + fs(x, ++j, I-1); // Move pivot to middle + return j; // Return pivot index } /** - * Find largest entries. - * @param x an array - * @param n number of values + * Partially arrange values in order! + * @param x an array (updated!) + * @param i start index + * @param I end index (exclusive) + * @param n minimum number of values to sort * @param fc compare function (a, b) * @param fm map function (v, i, x) - * @returns n largest entries in descending order + * @param fs swap function (x, i, j) + * @returns x | x[i] ≤ x[j] ∀ i ≤ j */ -export function maximumEntries(x: T[], n: number, fc: CompareFunction | null=null, fm: MapFunction | null=null): [number, T][] { - var is = searchMaximumValues(x, n, fc, fm); - return is.map(i => [i, x[i]]); +function partialHeapSort$(x: T[], i: number, I: number, n: number, fc: CompareFunction, fm: MapFunction, fs: SwapFunction): T[] { + buildReverseMinHeap$(x, i, I, fc, fm, fs); + for (var r=I-1; n>0 && i(x: T[], i: number, I: number, fc: CompareFunction, fm: MapFunction, fs: SwapFunction): void { + for (var r=I-Math.floor((I-i)/2); r(x: T[], vd?: T): T { - return x.length>0? x[0] : vd; +function reverseMinHeapify$(x: T[], i: number, I: number, r: number, fc: CompareFunction, fm: MapFunction, fs: SwapFunction): void { + var s = r; // Index of smallest value + var lt = 2*r - I; // Left child, reverse of lt = 2*r+1 + var rt = lt - 1; // Right child, reverse of rt = 2*r+2 + if (lt>=i && fc(fm(x[lt], lt, x), fm(x[s], s, x)) < 0) s = lt; // Left child is smaller? + if (rt>=i && fc(fm(x[rt], rt, x), fm(x[s], s, x)) < 0) s = rt; // Right child is smaller? + if (s !== r) { // Smallest is not root? + fs(x, s, r); // Swap root with smallest + reverseMinHeapify$(x, i, I, s, fc, fm, fs); // Rebuild heap + } } -export {head as front}; -export {head as first}; -/** - * Get last value. - * @param x an array - * @param vd default value - * @returns x[|x|-1] || vd - */ -export function last(x: T[], vd?: T): T { - return x.length>0? x[x.length-1] : vd; +// Build a max-heap, where root node is the smallest and placed at the beginning. +function buildMaxHeap$(x: T[], i: number, I: number, fc: CompareFunction, fm: MapFunction, fs: SwapFunction): void { + for (var r=i+Math.floor((I-i)/2)-1; r>=i; --r) + maxHeapify$(x, i, I, r, fc, fm, fs); } -export {last as back}; /** - * Get values from middle. - * @param x an array + * Max-heapify values, such that root node is the largest and placed at the beginning. + * @param x an array (updated!) * @param i start index - * @param n number of values [1] - * @returns x[i..i+n] + * @param I end index (exclusive) + * @param r root index + * @param fc compare function (a, b) + * @param fm map function (v, i, x) + * @param fs swap function (x, i, j) */ -export function middle(x: T[], i: number, n: number=1): T[] { - return x.slice(i, i+n); +function maxHeapify$(x: T[], i: number, I: number, r: number, fc: CompareFunction, fm: MapFunction, fs: SwapFunction): void { + var s = r; // Index of largest value + var lt = 2*r - i + 1; // Left child, like lt = 2*r+1 + var rt = lt + 1; // Right child, like rt = 2*r+2 + if (lt 0) s = lt; // Left child is larger? + if (rt 0) s = rt; // Right child is larger? + if (s !== r) { // Largest is not root? + fs(x, s, r); // Swap root with largest + maxHeapify$(x, i, I, s, fc, fm, fs); // Rebuild heap + } } /** - * Get values except first. - * @param x an array - * @returns x[1..|x|] + * Partially arrange values in order! + * @param x an array (updated!) + * @param i start index + * @param I end index (exclusive) + * @param n minimum number of values to sort (ignored) + * @param fc compare function (a, b) + * @param fm map function (v, i, x) + * @param fs swap function (x, i, j) + * @returns x | x[i] ≤ x[j] ∀ i ≤ j */ -export function tail(x: T[]): T[] { - return x.slice(1); +function partialInsertionSort$(x: T[], i: number, I: number, n: number, fc: CompareFunction, fm: MapFunction, fs: SwapFunction): T[] { + // NOTE: Insertion sort does not support partial sorting, so we ignore n. + if (fs===swapRaw$) return partialInsertionSortSwapless$(x, i, I, n, fc, fm, fs); + else return partialInsertionSortSwap$ (x, i, I, n, fc, fm, fs); } -/** - * Get values except last. - * @param x an array - * @returns x[0..|x|-1] - */ -export function init(x: T[]): T[] { - return x.slice(0, -1); +// Sort values in order with swap-enabled version of insertion sort. +function partialInsertionSortSwap$(x: T[], i: number, I: number, n: number, fc: CompareFunction, fm: MapFunction, fs: SwapFunction): T[] { + for (var j=i+1; j=i && fc(fm(x[k], k, x), wkey)>0; --k) + fs(x, k, k+1); + } + return x; } -/** - * Get part of an array. - * @param x an array - * @param i start index [0] - * @param I end index [|x|] - * @returns x[i..I] - */ -export function slice(x: T[], i: number=0, I: number=x.length): T[] { - return x.slice(i, I); +// Sort values in order with swapless version of insertion sort. +function partialInsertionSortSwapless$(x: T[], i: number, I: number, n: number, fc: CompareFunction, fm: MapFunction, fs: SwapFunction): T[] { + for (var j=i+1; j=i && fc(fm(x[k], k, x), wkey)>0; --k) + x[k+1] = x[k]; + x[k+1] = key; + } + return x; } /** - * Get part of an array! + * Partially arrange values in order! * @param x an array (updated!) - * @param i start index [0] - * @param I end index [|x|] - * @returns x = x[i..I] + * @param i start index + * @param I end index (exclusive) + * @param n minimum number of values to sort + * @param fc compare function (a, b) + * @param fm map function (v, i, x) + * @param fs swap function (x, i, j) + * @returns x | x[i] ≤ x[j] ∀ i ≤ j */ -export function slice$(x: T[], i: number=0, I: number=x.length): T[] { - x.copyWithin(0, i, I); - x.length = length(x, i, I); +function partialSelectionSort$(x: T[], i: number, I: number, n: number, fc: CompareFunction, fm: MapFunction, fs: SwapFunction): T[] { + for (var j=i; n>0 && j 0) { l = k; wl = wk; } + } + fs(x, j, l); + } return x; } +// #endregion + -/** - * Keep first n values only. - * @param x an array - * @param n number of values [1] - * @returns x[0..n] - */ -export function take(x: T[], n: number=1): T[] { - return x.slice(0, n); -} -export {take as left}; +// #region MINIMUM/MAXIMUM +// ----------------------- /** - * Keep last n values only. + * Find first smallest value. * @param x an array - * @param n number of values [1] - * @returns x[0..n] + * @param fc compare function (a, b) + * @param fm map function (v, i, x) + * @returns v | v ≤ vᵢ; vᵢ ∈ x */ -export function takeRight(x: T[], n: number=1): T[] { - return x.slice(x.length-n); +export function minimum(x: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): T { + var i = searchMinimumValue(x, fc, fm); + return x[i]; } -export {takeRight as right}; +export {minimum as min}; /** - * Keep values from left, while a test passes. + * Find first smallest entry. * @param x an array - * @param ft test function (v, i, x) - * @returns x[0..T-1] | ft(x[i]) = true ∀ i ∈ [0, T-1] & ft(x[T]) = false + * @param fc compare function (a, b) + * @param fm map function (v, i, x) + * @returns [min_index, min_value] */ -export function takeWhile(x: T[], ft: TestFunction): T[] { - return x.slice(0, scanWhile(x, ft)); +export function minimumEntry(x: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): [number, T] { + var i = searchMinimumValue(x, fc, fm); + return [i, x[i]]; } +export {minimumEntry as minEntry}; /** - * Keep values from right, while a test passes. + * Find first largest value. * @param x an array - * @param ft test function (v, i, x) - * @returns x[T..] | ft(x[i]) = true ∀ i ∈ [T, |x|-1] & ft(x[T-1]) = false + * @param fc compare function (a, b) + * @param fm map function (v, i, x) + * @returns v | v ≥ vᵢ; vᵢ ∈ x */ -export function takeWhileRight(x: T[], ft: TestFunction): T[] { - return x.slice(scanWhileRight(x, ft)); +export function maximum(x: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): T { + var i = searchMaximumValue(x, fc, fm); + return x[i]; } +export {maximum as max}; /** - * Discard first n values only. + * Find first largest entry. * @param x an array - * @param n number of values [1] - * @returns x[n..] + * @param fc compare function (a, b) + * @param fm map function (v, i, x) + * @returns [max_index, max_value] */ -export function drop(x: T[], n: number=1): T[] { - return x.slice(n); +export function maximumEntry(x: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): [number, T] { + var i = searchMaximumValue(x, fc, fm); + return [i, x[i]]; } +export {maximumEntry as maxEntry}; /** - * Discard last n values only. + * Find smallest and largest values. * @param x an array - * @param n number of values [1] - * @returns x[0..-n] + * @param fc compare function (a, b) + * @param fm map function (v, i, x) + * @returns [min_value, max_value] */ -export function dropRight(x: T[], n: number=1): T[] { - return x.slice(0, x.length-n); +export function range(x: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): [T, T] { + var [a, b] = rangeEntries(x, fc, fm); + return [a[1], b[1]]; } /** - * Discard values from left, while a test passes. + * Find smallest and largest entries. * @param x an array - * @param ft test function (v, i, x) - * @returns x[T..] | ft(x[i]) = true ∀ i ∈ [0, T-1] & ft(x[T]) = false + * @param fc compare function (a, b) + * @param fm map function (v, i, x) + * @returns [min_entry, max_entry] */ -export function dropWhile(x: T[], ft: TestFunction): T[] { - return x.slice(scanWhile(x, ft)); +export function rangeEntries(x: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): [[number, T], [number, T]] { + var fc = fc || COMPARE; + var fm = fm || IDENTITY; + var X = x.length; + if (X===0) return [[-1, undefined], [-1, undefined]]; + var v = x[0], w = fm(v, 0, x); + var mi = 0, mv = v, mw = w; + var ni = 0, nv = v, nw = w; + for (var i=1; i0) { ni = i; nv = v; nw = w; } + } + return [[mi, mv], [ni, nv]]; } /** - * Discard values from right, while a test passes. + * Find smallest values. * @param x an array - * @param ft test function (v, i, x) - * @returns x[0..T-1] | ft(x[i]) = true ∀ i ∈ [T, |x|-1] & ft(x[T-1]) = false + * @param n number of values + * @param fc compare function (a, b) + * @param fm map function (v, i, x) + * @returns n smallest values in ascending order */ -export function dropWhileRight(x: T[], ft: TestFunction): T[] { - return x.slice(0, scanWhileRight(x, ft)); +export function minimums(x: T[], n: number, fc: CompareFunction | null=null, fm: MapFunction | null=null): T[] { + var is = searchMinimumValues(x, n, fc, fm); + return is.map(i => x[i]); } -// #endregion - - -// #region ARRANGEMENTS -// -------------------- - /** - * Obtain all possible prefixes. + * Find smallest entries. * @param x an array - * @param n number of values [-1 ⇒ any] - * @returns [[], x[..1], x[..2], ...] if n<0; [x[..n]] otherwise + * @param n number of values + * @param fc compare function (a, b) + * @param fm map function (v, i, x) + * @returns n smallest entries in ascending order */ -export function prefixes(x: T[], n: number=-1): T[][] { - return [...iprefixes(x, n)]; +export function minimumEntries(x: T[], n: number, fc: CompareFunction | null=null, fm: MapFunction | null=null): [number, T][] { + var is = searchMinimumValues(x, n, fc, fm); + return is.map(i => [i, x[i]]); } /** - * List all possible prefixes. + * Find largest values. * @param x an array - * @param n number of values [-1 ⇒ any] - * @returns [], x[..1], x[..2], ... if n<0; x[..n] otherwise + * @param n number of values + * @param fc compare function (a, b) + * @param fm map function (v, i, x) + * @returns n largest values in descending order */ -export function* iprefixes(x: T[], n: number=-1): IterableIterator { - var X = x.length; - if (n>X) return; - if (n>=0) { yield x.slice(0, n); return; } - for (var i=0; i<=X; ++i) - yield x.slice(0, i); +export function maximums(x: T[], n: number, fc: CompareFunction | null=null, fm: MapFunction | null=null): T[] { + var is = searchMaximumValues(x, n, fc, fm); + return is.map(i => x[i]); } /** - * Obtain all possible suffixes. + * Find largest entries. * @param x an array - * @param n number of values [-1 ⇒ any] - * @returns [x[0..], x[1..], x[2..], ...] if n<0; [x[-n..]] otherwise + * @param n number of values + * @param fc compare function (a, b) + * @param fm map function (v, i, x) + * @returns n largest entries in descending order */ -export function suffixes(x: T[], n: number=-1): T[][] { - return [...isuffixes(x, n)]; +export function maximumEntries(x: T[], n: number, fc: CompareFunction | null=null, fm: MapFunction | null=null): [number, T][] { + var is = searchMaximumValues(x, n, fc, fm); + return is.map(i => [i, x[i]]); } /** - * List all possible suffixes. + * Find first index of minimum value. * @param x an array - * @param n number of values [-1 ⇒ any] - * @returns x[0..], x[1..], x[2..], ... if n<0; x[-n..] otherwise + * @param fc compare function (a, b) + * @param fm map function (v, i, x) + * @returns first index of minimum value, -1 if empty */ -export function* isuffixes(x: T[], n: number=-1): IterableIterator { - var X = x.length; - if (n>X) return; - if (n>=0) { yield x.slice(x.length - n); return; } - for (var i=0; i<=X; ++i) - yield x.slice(i); +export function searchMinimumValue(x: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): number { + var fc = fc || COMPARE; + var fm = fm || IDENTITY; + var X = x.length; + if (X===0) return -1; + var mi = 0, mw = fm(x[0], 0, x); + for (var i=1; i(x: T[], n: number=-1): T[][] { - return [...iinfixes(x, n)]; +export function searchMaximumValue(x: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): number { + var fc = fc || COMPARE; + var fm = fm || IDENTITY; + var X = x.length; + if (X===0) return -1; + var ni = 0, nw = fm(x[0], 0, x); + for (var i=1; i0) { ni = i; nw = w; } + } + return ni; } /** - * List all possible infixes. + * Find indices of minimum values. * @param x an array - * @param n number of values [-1 ⇒ any] - * @returns [], x[0..1], x[0..2], ..., x[1..2], ... if n<0; only of length n otherwise + * @param n number of values + * @param fc compare function (a, b) + * @param fm map function (v, i, x) + * @returns indices of minimum values in ascending order */ -export function iinfixes(x: T[], n: number=-1): IterableIterator { - if (n>=0) return infixesFixed(x, n); - else return infixesAll(x); -} - -function* infixesFixed(x: T[], n: number): IterableIterator { - var X = x.length; - if (n>X) return; - if (n===0) { yield []; return; } - for (var i=0, I=X-n+1; i(x: T[]): IterableIterator { - var X = x.length; yield []; - for (var i=0; i(x: T[], n: number, fc: CompareFunction | null=null, fm: MapFunction | null=null): number[] { + var fc = fc || COMPARE; + var fm = fm || IDENTITY; + var X = x.length; + // Create a max heap of indices. + var IH = Math.min(n, X); + var ih = fromRange(0, IH); + buildMaxHeap$(ih, 0, IH, fc, i => fm(x[i], i, x), swapRaw$); + var wr = fm(x[ih[0]], ih[0], x); + // Search for minimum values, and update heap. + for (var i=n; i= 0) continue; + ih[0] = i; + maxHeapify$(ih, 0, IH, 0, fc, i => fm(x[i], i, x), swapRaw$); + var wr = fm(x[ih[0]], ih[0], x); } + // Sort max heap in ascending order. + ih.sort((i, j) => fc(fm(x[i], i, x), fm(x[j], j, x))); + return ih; } /** - * Obtain all possible subsequences. + * Find indices of maximum values. * @param x an array - * @param n number of values [-1 ⇒ any] - * @returns [elements selected by bit from 0..2^|x|] if n<0; [only of length n] otherwise + * @param n number of values + * @param fc compare function (a, b) + * @param fm map function (v, i, x) + * @returns indices of maximum values in descending order */ -export function subsequences(x: T[], n: number=-1): T[][] { - return [...isubsequences(x, n)]; +export function searchMaximumValues(x: T[], n: number, fc: CompareFunction | null=null, fm: MapFunction | null=null): number[] { + var fc = fc || COMPARE; + var fd = (a: T|U, b: T|U) => -fc(a, b); + return searchMinimumValues(x, n, fd, fm); } +// #endregion + + +// #region COMPARE +// --------------- + /** - * List all possible subsequences. + * Examine if two arrays are equal. * @param x an array - * @param n number of values [-1 ⇒ any] - * @returns elements selected by bit from 0..2^|x| if n<0; only of length n otherwise + * @param y another array + * @param fc compare function (a, b) + * @param fm map function (v, i, x) + * @returns x = y? */ -export function* isubsequences(x: T[], n: number=-1): IterableIterator { - var X = x.length; - if (n>X) return; - if (n===X) { yield x; return; } - if (n===0 || X===0) { yield []; return; } - var y = x.slice(0, -1); - yield* isubsequences(y, n); - for (var s of isubsequences(y, n-1)) { - s.push(x[X-1]); - yield s; - } +export function isEqual(x: T[], y: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): boolean { + var X = x.length, Y = y.length; + return X===Y && compare(x, y, fc, fm)===0; } /** - * Obtain all possible permutations. + * Compare two arrays (lexicographically). * @param x an array - * @param n number of values [-1 ⇒ any] - * @returns [[], arrangements of length 1, of length 2, ...] if n<0; [only of length n] otherwise - */ -export function permutations(x: T[], n: number=-1): T[][] { - return [...ipermutations(x, n)]; + * @param y another array + * @param fc compare function (a, b) + * @param fm map function (v, i, x) + * @returns xy: +ve + */ +export function compare(x: T[], y: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): number { + var fc = fc || COMPARE; + var fm = fm || IDENTITY; + var X = x.length; + var Y = y.length; + for (var i=0, I=Math.min(X, Y); i(x: T[], n: number=-1): IterableIterator { - var X = x.length; - if (n>X) return; - var i = n<0? 0 : n; - var I = n<0? X : n - for (; i<=I; ++i) - yield* ipermutationsFixed(x, i); -} -function* ipermutationsFixed(x: T[], n: number): IterableIterator { - var X = x.length; - if (X===0 || n===0) { yield []; return; } - for (var i=0; i(x: T[], fr: ReadFunction | null=Math.random): T { - var i = Math.floor(fr() * x.length); - return x[i]; +export function head(x: T[], vd?: T): T { + return x.length>0? x[0] : vd; } -export {randomValue as value}; // DEPRECATED +export {head as front}; +export {head as first}; /** - * Pick an arbitrary prefix. + * Get last value. * @param x an array - * @param n number of values [-1 ⇒ any] - * @param fr random number generator ([0, 1)) - * @returns x[..i] if n<0; x[..n] otherwise | i ∈ 0..|x| + * @param vd default value + * @returns x[|x|-1] || vd */ -export function randomPrefix(x: T[], n: number=-1, fr: ReadFunction | null=Math.random): T[] { - var X = x.length; - if (n>X) return null; - var n = n>=0? n : Math.floor((X+1)*fr()); - return x.slice(0, n); +export function last(x: T[], vd?: T): T { + return x.length>0? x[x.length-1] : vd; } -export {randomPrefix as prefix}; // DEPRECATED +export {last as back}; /** - * Pick an arbitrary suffix. + * Get values from middle. * @param x an array - * @param n number of values [-1 ⇒ any] - * @param fr random number generator ([0, 1)) - * @returns x[|x|-i..] if n<0; x[|x|-n..] otherwise | i ∈ 0..|x| + * @param i start index + * @param n number of values [1] + * @returns x[i..i+n] */ -export function randomSuffix(x: T[], n: number=-1, fr: ReadFunction | null=Math.random): T[] { - var X = x.length; - if (n>X) return null; - var n = n>=0? n : Math.floor((X+1)*fr()); - return x.slice(X-n); +export function middle(x: T[], i: number, n: number=1): T[] { + return x.slice(i, i+n); } -export {randomSuffix as suffix}; // DEPRECATED /** - * Pick an arbitrary infix. + * Get values except first. * @param x an array - * @param n number of values [-1 ⇒ any] - * @param fr random number generator ([0, 1)) - * @returns x[i..j] if n<0; x[i..i+n] otherwise | i, j ∈ 0..|x| + * @returns x[1..|x|] */ -export function randomInfix(x: T[], n: number=-1, fr: ReadFunction | null=Math.random): T[] { - var X = x.length; - if (n>X) return null; - var n = n>=0? n : randomInfixLength(X, fr()); - var i = Math.floor((X+1-n)*fr()); - return x.slice(i, i+n); -} -export {randomInfix as infix}; // DEPRECATED - -// Not all infix lengths are equally proable. -function randomInfixLength(X: number, r: number): number { - var C = 0.5 * X*(X+1) + 1; - var n = 0.5 * Math.sqrt(1 + 8*r*C) - 0.5; - return X+1 - Math.floor(n+1); +export function tail(x: T[]): T[] { + return x.slice(1); } /** - * Pick an arbitrary subsequence. + * Get values except last. * @param x an array - * @param n number of values [-1 ⇒ any] - * @param fr random number generator ([0, 1)) - * @returns x[i, j, ...] | [i, j, ...] = is; |is| = |x| if n<0 else n + * @returns x[0..|x|-1] */ -export function randomSubsequence(x: T[], n: number=-1, fr: ReadFunction | null=Math.random): T[] { - var X = x.length; - if (n>X) return null; - if (n>=0) return randomSubsequenceFixed(x, n, fr); - else return randomSubsequenceAll(x, fr); -} -export {randomSubsequence as subsequence}; // DEPRECATED - -function randomSubsequenceFixed(x: T[], n: number, fr: ReadFunction): T[] { - var is = fromRange(0, x.length); - randomPermutation$(is, n, fr).sort(); - return getAll(x, is); -} - -function randomSubsequenceAll(x: T[], fr: ReadFunction): T[] { - var a = []; - for (var v of x) - if (fr()<0.5) a.push(v); - return a; +export function init(x: T[]): T[] { + return x.slice(0, -1); } /** - * Pick an arbitrary permutation. + * Get part of an array. * @param x an array - * @param n number of values [-1 ⇒ any] - * @param fr random number generator ([0, 1)) - * @returns x' | x' = x; values are randomly shuffled + * @param i start index [0] + * @param I end index [|x|] + * @returns x[i..I] */ -export function randomPermutation(x: T[], n: number=-1, fr: ReadFunction | null=Math.random): T[] { - var X = x.length; - if (n>X) return null; - return randomPermutation$(x.slice(), n, fr); +export function slice(x: T[], i: number=0, I: number=x.length): T[] { + return x.slice(i, I); } -export {randomPermutation as permutation}; // DEPRECATED /** - * Pick an arbitrary permutation! + * Get part of an array! * @param x an array (updated!) - * @param n number of values [-1 ⇒ any] - * @param fr random number generator ([0, 1)) - * @returns x | values are randomly shuffled + * @param i start index [0] + * @param I end index [|x|] + * @returns x = x[i..I] */ -export function randomPermutation$(x: T[], n: number=-1, fr: ReadFunction | null=Math.random): T[] { - var X = x.length; - if (n>X) return x; - var n = n>=0? n : Math.floor((X+1)*fr()); - for (var i=0; i(x: T[], i: number=0, I: number=x.length): T[] { + x.copyWithin(0, i, I); + x.length = length(x, i, I); return x; } -export {randomPermutation$ as permutation$}; // DEPRECATED -export {randomPermutation$ as permute$}; -export {randomPermutation$ as shuffle$}; -// - https://en.wikipedia.org/wiki/Fisher–Yates_shuffle -// #endregion - - -// #region FIND -// ------------ - /** - * Check if array has a value. + * Keep first n values only. * @param x an array - * @param v search value - * @param i start index [0] - * @returns v ∈ x[i..]? + * @param n number of values [1] + * @returns x[0..n] */ -export function includes(x: T[], v: T, i: number=0): boolean { - return x.includes(v, i); +export function take(x: T[], n: number=1): T[] { + return x.slice(0, n); } +export {take as left}; /** - * Find first index of a value. + * Keep last n values only. * @param x an array - * @param v search value - * @param i start index [0] - * @returns index of v in x[i..] if found else -1 + * @param n number of values [1] + * @returns x[0..n] */ -export function indexOf(x: T[], v: T, i: number=0): number { - return x.indexOf(v, i); +export function takeRight(x: T[], n: number=1): T[] { + return x.slice(x.length-n); } +export {takeRight as right}; /** - * Find last index of a value. + * Keep values from left, while a test passes. * @param x an array - * @param v search value - * @param i start index [|x|-1] - * @returns last index of v in x[0..i] if found else -1 + * @param ft test function (v, i, x) + * @returns x[0..T-1] | ft(x[i]) = true ∀ i ∈ [0, T-1] & ft(x[T]) = false */ -export function lastIndexOf(x: T[], v: T, i: number=x.length-1) { - return x.lastIndexOf(v, i); +export function takeWhile(x: T[], ft: TestFunction): T[] { + return x.slice(0, scanWhile(x, ft)); } /** - * Find first value passing a test. + * Keep values from right, while a test passes. * @param x an array * @param ft test function (v, i, x) - * @returns first v | ft(v) = true; v ∈ x + * @returns x[T..] | ft(x[i]) = true ∀ i ∈ [T, |x|-1] & ft(x[T-1]) = false */ -export function find(x: T[], ft: TestFunction): T { - return x.find(ft); +export function takeWhileRight(x: T[], ft: TestFunction): T[] { + return x.slice(scanWhileRight(x, ft)); } /** - * Find last value passing a test. + * Discard first n values only. * @param x an array - * @param ft test function (v, i, x) - * @returns last v | ft(v) = true; v ∈ x + * @param n number of values [1] + * @returns x[n..] */ -export function findRight(x: T[], ft: TestFunction): T { - for (var i=x.length-1; i>=0; --i) - if (ft(x[i], i, x)) return x[i]; +export function drop(x: T[], n: number=1): T[] { + return x.slice(n); } /** - * Scan from left, while a test passes. + * Discard last n values only. * @param x an array - * @param ft test function (v, i, x) - * @returns first index where test fails + * @param n number of values [1] + * @returns x[0..-n] */ -export function scanWhile(x: T[], ft: TestFunction): number { - var i = -1; - for (var v of x) - if (!ft(v, ++i, x)) return i; - return ++i; +export function dropRight(x: T[], n: number=1): T[] { + return x.slice(0, x.length-n); } /** - * Scan from right, while a test passes. + * Discard values from left, while a test passes. * @param x an array * @param ft test function (v, i, x) - * @returns first index where test passes till end + * @returns x[T..] | ft(x[i]) = true ∀ i ∈ [0, T-1] & ft(x[T]) = false */ -export function scanWhileRight(x: T[], ft: TestFunction): number { - for (var i=x.length-1; i>=0; --i) - if (!ft(x[i], i, x)) break; - return ++i; +export function dropWhile(x: T[], ft: TestFunction): T[] { + return x.slice(scanWhile(x, ft)); } /** - * Scan from left, until a test passes. + * Discard values from right, while a test passes. * @param x an array * @param ft test function (v, i, x) - * @returns first index where test passes + * @returns x[0..T-1] | ft(x[i]) = true ∀ i ∈ [T, |x|-1] & ft(x[T-1]) = false */ -export function scanUntil(x: T[], ft: TestFunction): number { - var i = -1; - for (var v of x) - if (ft(v, ++i, x)) return i; - return ++i; +export function dropWhileRight(x: T[], ft: TestFunction): T[] { + return x.slice(0, scanWhileRight(x, ft)); } +// #endregion + + +// #region PROPERTY +// ---------------- + /** - * Scan from right, until a test passes. + * Count values which satisfy a test. * @param x an array * @param ft test function (v, i, x) - * @returns first index where test fails till end + * @returns Σtᵢ | tᵢ = 1 if ft(vᵢ) else 0; vᵢ ∈ x */ -export function scanUntilRight(x: T[], ft: TestFunction): number { - for (var i=x.length-1; i>=0; --i) - if (ft(x[i], i, x)) break; - return ++i; +export function count(x: T[], ft: TestFunction): number { + var i = -1, a = 0; + for (var v of x) + if (ft(v, ++i, x)) ++a; + return a; } /** - * Find index of first value passing a test. + * Count occurrences of each distinct value. * @param x an array - * @param ft test function (v, i, x) - * @returns first index of value, -1 if not found + * @param fm map function (v, i, x) + * @returns Map \{value ⇒ count\} */ -export function search(x: T[], ft: TestFunction): number { - return x.findIndex(ft); +export function countEach(x: T[], fm: MapFunction | null=null): Map { + var fm = fm || IDENTITY; + var i = -1, a = new Map(); + for (var v of x) { + var w = fm(v, ++i, x); + a.set(w, (a.get(w) || 0) + 1); + } + return a; } -export {search as findIndex}; +export {countEach as countAs}; // DEPRECATED +// #endregion + + +// #region ARRANGEMENTS +// -------------------- + /** - * Find index of last value passing a test. + * Obtain all possible prefixes. * @param x an array - * @param ft test function (v, i, x) - * @returns last index of value, -1 if not found + * @param n number of values [-1 ⇒ any] + * @returns [[], x[..1], x[..2], ...] if n<0; [x[..n]] otherwise */ -export function searchRight(x: T[], ft: TestFunction): number { - for (var i=x.length-1; i>=0; --i) - if (ft(x[i], i, x)) return i; - return -1; +export function prefixes(x: T[], n: number=-1): T[][] { + return [...iprefixes(x, n)]; } -export {searchRight as findLastIndex}; /** - * Find indices of values passing a test. + * List all possible prefixes. * @param x an array - * @param ft test function (v, i, x) - * @returns indices of value + * @param n number of values [-1 ⇒ any] + * @returns [], x[..1], x[..2], ... if n<0; x[..n] otherwise */ -export function searchAll(x: T[], ft: TestFunction): number[] { - var i = -1, a = []; - for (var v of x) - if (ft(v, ++i, x)) a.push(i); - return a; +export function* iprefixes(x: T[], n: number=-1): IterableIterator { + var X = x.length; + if (n>X) return; + if (n>=0) { yield x.slice(0, n); return; } + for (var i=0; i<=X; ++i) + yield x.slice(0, i); } /** - * Find first index of a value. + * Obtain all possible suffixes. * @param x an array - * @param v search value - * @param fc compare function (a, b) - * @param fm map function (v, i, x) - * @returns first index of value, -1 if not found + * @param n number of values [-1 ⇒ any] + * @returns [x[0..], x[1..], x[2..], ...] if n<0; [x[-n..]] otherwise */ -export function searchValue(x: T[], v: T, fc: CompareFunction | null=null, fm: MapFunction | null=null): number { - var fc = fc || COMPARE; - var fm = fm || IDENTITY; - var w = fm(v, 0, null), i = -1; - for (var vx of x) { - var wx = fm(vx, ++i, x); - if (fc(wx, w)===0) return i; - } - return -1; +export function suffixes(x: T[], n: number=-1): T[][] { + return [...isuffixes(x, n)]; } /** - * Find last index of a value. + * List all possible suffixes. * @param x an array - * @param v search value - * @param fc compare function (a, b) - * @param fm map function (v, i, x) - * @returns last index of value, -1 if not found + * @param n number of values [-1 ⇒ any] + * @returns x[0..], x[1..], x[2..], ... if n<0; x[-n..] otherwise */ -export function searchValueRight(x: T[], v: T, fc: CompareFunction | null=null, fm: MapFunction | null=null): number { - var fc = fc || COMPARE; - var fm = fm || IDENTITY; - var w = fm(v, 0, null); - for (var i=x.length-1; i>=0; --i) { - var wx = fm(x[i], i, x); - if (fc(wx, w)===0) return i; - } - return -1; +export function* isuffixes(x: T[], n: number=-1): IterableIterator { + var X = x.length; + if (n>X) return; + if (n>=0) { yield x.slice(x.length - n); return; } + for (var i=0; i<=X; ++i) + yield x.slice(i); } /** - * Find indices of value. + * Obtain all possible infixes. * @param x an array - * @param v search value - * @param fc compare function (a, b) - * @param fm map function (v, i, x) - * @returns indices of value + * @param n number of values [-1 ⇒ any] + * @returns [[], x[0..1], x[0..2], ..., x[1..2], ...] if n<0; [only of length n] otherwise */ -export function searchValueAll(x: T[], v: T, fc: CompareFunction | null=null, fm: MapFunction | null=null): number[] { - var fc = fc || COMPARE; - var fm = fm || IDENTITY; - var w = fm(v, 0, null); - var i = -1, a = []; - for (var vx of x) { - var wx = fm(vx, ++i, x); - if (fc(wx, w)===0) a.push(i); - } - return a; +export function infixes(x: T[], n: number=-1): T[][] { + return [...iinfixes(x, n)]; } /** - * Find first index of minimum value. + * List all possible infixes. * @param x an array - * @param fc compare function (a, b) - * @param fm map function (v, i, x) - * @returns first index of minimum value, -1 if empty + * @param n number of values [-1 ⇒ any] + * @returns [], x[0..1], x[0..2], ..., x[1..2], ... if n<0; only of length n otherwise */ -export function searchMinimumValue(x: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): number { - var fc = fc || COMPARE; - var fm = fm || IDENTITY; - var X = x.length; - if (X===0) return -1; - var mi = 0, mw = fm(x[0], 0, x); - for (var i=1; i(x: T[], n: number=-1): IterableIterator { + if (n>=0) return infixesFixed(x, n); + else return infixesAll(x); } +function* infixesFixed(x: T[], n: number): IterableIterator { + var X = x.length; + if (n>X) return; + if (n===0) { yield []; return; } + for (var i=0, I=X-n+1; i(x: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): number { - var fc = fc || COMPARE; - var fm = fm || IDENTITY; - var X = x.length; - if (X===0) return -1; - var ni = 0, nw = fm(x[0], 0, x); - for (var i=1; i0) { ni = i; nw = w; } +function* infixesAll(x: T[]): IterableIterator { + var X = x.length; yield []; + for (var i=0; i(x: T[], n: number, fc: CompareFunction | null=null, fm: MapFunction | null=null): number[] { - var fc = fc || COMPARE; - var fm = fm || IDENTITY; - var X = x.length; - // Create a max heap of indices. - var IH = Math.min(n, X); - var ih = fromRange(0, IH); - buildMaxHeap$(ih, 0, IH, fc, i => fm(x[i], i, x), swapRaw$); - var wr = fm(x[ih[0]], ih[0], x); - // Search for minimum values, and update heap. - for (var i=n; i= 0) continue; - ih[0] = i; - maxHeapify$(ih, 0, IH, 0, fc, i => fm(x[i], i, x), swapRaw$); - var wr = fm(x[ih[0]], ih[0], x); - } - // Sort max heap in ascending order. - ih.sort((i, j) => fc(fm(x[i], i, x), fm(x[j], j, x))); - return ih; +export function subsequences(x: T[], n: number=-1): T[][] { + return [...isubsequences(x, n)]; } /** - * Find indices of maximum values. + * List all possible subsequences. * @param x an array - * @param n number of values - * @param fc compare function (a, b) - * @param fm map function (v, i, x) - * @returns indices of maximum values in descending order + * @param n number of values [-1 ⇒ any] + * @returns elements selected by bit from 0..2^|x| if n<0; only of length n otherwise */ -export function searchMaximumValues(x: T[], n: number, fc: CompareFunction | null=null, fm: MapFunction | null=null): number[] { - var fc = fc || COMPARE; - var fd = (a: T|U, b: T|U) => -fc(a, b); - return searchMinimumValues(x, n, fd, fm); +export function* isubsequences(x: T[], n: number=-1): IterableIterator { + var X = x.length; + if (n>X) return; + if (n===X) { yield x; return; } + if (n===0 || X===0) { yield []; return; } + var y = x.slice(0, -1); + yield* isubsequences(y, n); + for (var s of isubsequences(y, n-1)) { + s.push(x[X-1]); + yield s; + } } /** - * Find first index of an unsorted value. + * Obtain all possible permutations. * @param x an array - * @param fc compare function (a, b) - * @param fm map function (v, i, x) - * @returns index of first unsorted value, -1 if sorted + * @param n number of values [-1 ⇒ any] + * @returns [[], arrangements of length 1, of length 2, ...] if n<0; [only of length n] otherwise */ -export function searchUnsortedValue(x: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): number { - var fc = fc || COMPARE; - var fm = fm || IDENTITY; - var X = x.length; - if (X<=1) return -1; - var w0 = fm(x[0], 0, x); - for (var i=1; i0) return i; - w0 = w; - } - return -1; +export function permutations(x: T[], n: number=-1): T[][] { + return [...ipermutations(x, n)]; } /** - * Find first index of an adjacent duplicate value. + * List all possible permutations. * @param x an array - * @param fc compare function (a, b) - * @param fm map function (v, i, x) - * @returns index of first adjacent duplicate value, -1 if none + * @param n number of values [-1 ⇒ any] + * @returns [], arrangements of length 1, of length 2, ... if n<0; only of length n otherwise */ -export function searchAdjacentDuplicateValue(x: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): number { - var fc = fc || COMPARE; - var fm = fm || IDENTITY; - var X = x.length; - if (X<=1) return -1; - var w0 = fm(x[0], 0, x); - for (var i=1; i(x: T[], n: number=-1): IterableIterator { + var X = x.length; + if (n>X) return; + var i = n<0? 0 : n; + var I = n<0? X : n + for (; i<=I; ++i) + yield* ipermutationsFixed(x, i); +} + +function* ipermutationsFixed(x: T[], n: number): IterableIterator { + var X = x.length; + if (X===0 || n===0) { yield []; return; } + for (var i=0; i(x: T[], y: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): number { - var fc = fc || COMPARE; - var fm = fm || IDENTITY; - var X = x.length; - var Y = y.length; - for (var i=0, I=Math.min(X, Y); i(x: T[], fr: ReadFunction | null=Math.random): T { + var i = Math.floor(fr() * x.length); + return x[i]; } -export {searchMismatchedValue as searchMismatch}; +export {randomValue as value}; // DEPRECATED /** - * Find first index of an infix. + * Pick an arbitrary prefix. * @param x an array - * @param y search infix - * @param fc compare function (a, b) - * @param fm map function (v, i, x) - * @returns first i | x[i..i+|y|] = y else -1 + * @param n number of values [-1 ⇒ any] + * @param fr random number generator ([0, 1)) + * @returns x[..i] if n<0; x[..n] otherwise | i ∈ 0..|x| */ -export function searchInfix(x: T[], y: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): number { - var fc = fc || COMPARE; - var fm = fm || IDENTITY; - var X = x.length, Y = y.length; - for (var i=0; i<=X-Y; ++i) - if (isInfixAt(x, y, i, fc, fm)) return i; - return -1; +export function randomPrefix(x: T[], n: number=-1, fr: ReadFunction | null=Math.random): T[] { + var X = x.length; + if (n>X) return null; + var n = n>=0? n : Math.floor((X+1)*fr()); + return x.slice(0, n); } +export {randomPrefix as prefix}; // DEPRECATED /** - * Find last index of an infix. + * Pick an arbitrary suffix. * @param x an array - * @param y search infix - * @param fc compare function (a, b) - * @param fm map function (v, i, x) - * @returns first i | x[i..i+|y|] = y else -1 + * @param n number of values [-1 ⇒ any] + * @param fr random number generator ([0, 1)) + * @returns x[|x|-i..] if n<0; x[|x|-n..] otherwise | i ∈ 0..|x| */ -export function searchInfixRight(x: T[], y: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): number { - var fc = fc || COMPARE; - var fm = fm || IDENTITY; - var X = x.length, Y = y.length; - for (var i=X-Y; i>=0; --i) - if (isInfixAt(x, y, i, fc, fm)) return i; - return -1; +export function randomSuffix(x: T[], n: number=-1, fr: ReadFunction | null=Math.random): T[] { + var X = x.length; + if (n>X) return null; + var n = n>=0? n : Math.floor((X+1)*fr()); + return x.slice(X-n); } +export {randomSuffix as suffix}; // DEPRECATED /** - * Find indices of an infix. + * Pick an arbitrary infix. * @param x an array - * @param y search infix - * @param fc compare function (a, b) - * @param fm map function (v, i, x) - * @returns i₀, i₁, ... | x[j..j+|y|] = y; j ∈ [i₀, i₁, ...] + * @param n number of values [-1 ⇒ any] + * @param fr random number generator ([0, 1)) + * @returns x[i..j] if n<0; x[i..i+n] otherwise | i, j ∈ 0..|x| */ -export function searchInfixAll(x: T[], y: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): number[] { - var fc = fc || COMPARE; - var fm = fm || IDENTITY; - var X = x.length, Y = y.length, a = []; - for (var i=0; i<=X-Y; ++i) - if (isInfixAt(x, y, i, fc, fm)) a.push(i); - return a; +export function randomInfix(x: T[], n: number=-1, fr: ReadFunction | null=Math.random): T[] { + var X = x.length; + if (n>X) return null; + var n = n>=0? n : randomInfixLength(X, fr()); + var i = Math.floor((X+1-n)*fr()); + return x.slice(i, i+n); } +export {randomInfix as infix}; // DEPRECATED - -function isInfixAt(x: T[], y: T[], i: number, fc: CompareFunction, fm: MapFunction): boolean { - var Y = y.length; - for (var j=0; j(x: T[], y: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): number { - var fc = fc || COMPARE; - var fm = fm || IDENTITY; - var y1 = [...y].map(fm), Y = y1.length; - var a = -1, i = -1, j = 0; - for (var vx of x) { - var wx = fm(vx, ++i, x); - if (fc(wx, y1[j])!==0) continue; - if (a<0) a = i; - if (++j>=Y) return a; - } - return -1; +export function randomSubsequence(x: T[], n: number=-1, fr: ReadFunction | null=Math.random): T[] { + var X = x.length; + if (n>X) return null; + if (n>=0) return randomSubsequenceFixed(x, n, fr); + else return randomSubsequenceAll(x, fr); } +export {randomSubsequence as subsequence}; // DEPRECATED +function randomSubsequenceFixed(x: T[], n: number, fr: ReadFunction): T[] { + var is = fromRange(0, x.length); + randomPermutation$(is, n, fr).sort(); + return getAll(x, is); +} -/** - * Examine if array has a value. - * @param x an array - * @param v search value - * @param fc compare function (a, b) - * @param fm map function (v, i, x) - * @returns v ∈ x? - */ -export function hasValue(x: T[], v: T, fc: CompareFunction | null=null, fm: MapFunction | null=null): boolean { - return searchValue(x, v, fc, fm) >= 0; +function randomSubsequenceAll(x: T[], fr: ReadFunction): T[] { + var a = []; + for (var v of x) + if (fr()<0.5) a.push(v); + return a; } /** - * Examine if array has an unsorted value. + * Pick an arbitrary permutation. * @param x an array - * @param fc compare function (a, b) - * @param fm map function (v, i, x) - * @returns x is not sorted? + * @param n number of values [-1 ⇒ any] + * @param fr random number generator ([0, 1)) + * @returns x' | x' = x; values are randomly shuffled */ -export function hasUnsortedValue(x: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): boolean { - return searchUnsortedValue(x, fc, fm) >= 0; +export function randomPermutation(x: T[], n: number=-1, fr: ReadFunction | null=Math.random): T[] { + var X = x.length; + if (n>X) return null; + return randomPermutation$(x.slice(), n, fr); } +export {randomPermutation as permutation}; // DEPRECATED /** - * Examine if array starts with a prefix. - * @param x an array - * @param y search prefix - * @param fc compare function (a, b) - * @param fm map function (v, i, x) - * @returns x[0..|y|] = y? + * Pick an arbitrary permutation! + * @param x an array (updated!) + * @param n number of values [-1 ⇒ any] + * @param fr random number generator ([0, 1)) + * @returns x | values are randomly shuffled */ -export function hasPrefix(x: T[], y: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): boolean { - var Y = y.length; - return Y===0 || compare(x.slice(0, Y), y, fc, fm)===0; +export function randomPermutation$(x: T[], n: number=-1, fr: ReadFunction | null=Math.random): T[] { + var X = x.length; + if (n>X) return x; + var n = n>=0? n : Math.floor((X+1)*fr()); + for (var i=0; i(x: T[], y: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): boolean { - var Y = y.length; - return Y===0 || compare(x.slice(-Y), y, fc, fm)===0; -} -export {hasSuffix as endsWith}; +// #region FIND +// ------------ /** - * Examine if array contains an infix. + * Check if array has a value. * @param x an array - * @param y search infix - * @param fc compare function (a, b) - * @param fm map function (v, i, x) - * @returns x[i..I] = y for some i, I? + * @param v search value + * @param i start index [0] + * @returns v ∈ x[i..]? */ -export function hasInfix(x: T[], y: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): boolean { - return searchInfix(x, y, fc, fm) >= 0; +export function includes(x: T[], v: T, i: number=0): boolean { + return x.includes(v, i); } /** - * Examine if array has a subsequence. + * Find first index of a value. * @param x an array - * @param y search subsequence - * @param fc compare function (a, b) - * @param fm map function (v, i, x) - * @returns x[i₀] ⧺ x[i₁] ⧺ ... = y, for some i₀, i₁, ...? | i₀ < i₁ < ... + * @param v search value + * @param i start index [0] + * @returns index of v in x[i..] if found else -1 */ -export function hasSubsequence(x: T[], y: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): boolean { - return searchSubsequence(x, y, fc, fm) >= 0; +export function indexOf(x: T[], v: T, i: number=0): number { + return x.indexOf(v, i); } /** - * Examine if array has a permutation. + * Find last index of a value. * @param x an array - * @param y search permutation - * @param fc map function (v, i, x) - * @param fm compare function (a, b) - * @returns x contains a shuffled version of y? + * @param v search value + * @param i start index [|x|-1] + * @returns last index of v in x[0..i] if found else -1 */ -export function hasPermutation(x: T[], y: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): boolean { - var x1 = fm? x.map(fm) : x.slice(); - var y1 = fm? y.map(fm) : y.slice(); - return hasSubsequence(x1.sort(), y1.sort(), fc, fm); +export function lastIndexOf(x: T[], v: T, i: number=x.length-1) { + return x.lastIndexOf(v, i); } -// #endregion - - -// #region COMPARE -// --------------- - /** - * Compare two arrays (lexicographically). + * Find first value passing a test. * @param x an array - * @param y another array - * @param fc compare function (a, b) - * @param fm map function (v, i, x) - * @returns xy: +ve + * @param ft test function (v, i, x) + * @returns first v | ft(v) = true; v ∈ x */ -export function compare(x: T[], y: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): number { - var fc = fc || COMPARE; - var fm = fm || IDENTITY; - var X = x.length; - var Y = y.length; - for (var i=0, I=Math.min(X, Y); i(x: T[], ft: TestFunction): T { + return x.find(ft); } /** - * Examine if two arrays are equal. + * Find last value passing a test. * @param x an array - * @param y another array - * @param fc compare function (a, b) - * @param fm map function (v, i, x) - * @returns x = y? + * @param ft test function (v, i, x) + * @returns last v | ft(v) = true; v ∈ x */ -export function isEqual(x: T[], y: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): boolean { - var X = x.length, Y = y.length; - return X===Y && compare(x, y, fc, fm)===0; +export function findRight(x: T[], ft: TestFunction): T { + for (var i=x.length-1; i>=0; --i) + if (ft(x[i], i, x)) return x[i]; } -// #endregion - - -// #region FUNCTIONAL -// ------------------ - /** - * Call a function for each value. + * Scan from left, while a test passes. * @param x an array - * @param fp process function (v, i, x) + * @param ft test function (v, i, x) + * @returns first index where test fails */ -export function forEach(x: T[], fp: ProcessFunction): void { - x.forEach(fp); +export function scanWhile(x: T[], ft: TestFunction): number { + var i = -1; + for (var v of x) + if (!ft(v, ++i, x)) return i; + return ++i; } /** - * Examine if any value satisfies a test. + * Scan from right, while a test passes. * @param x an array * @param ft test function (v, i, x) - * @returns true if ft(vᵢ) = true for some vᵢ ∈ x + * @returns first index where test passes till end */ -export function some(x: T[], ft: TestFunction | null=null): boolean { - if (ft) return x.some(ft); - else return someBoolean(x); -} -export {some as anyOf}; - -function someBoolean(x: T[]): boolean { - for (var i=0, I=x.length; i(x: T[], ft: TestFunction): number { + for (var i=x.length-1; i>=0; --i) + if (!ft(x[i], i, x)) break; + return ++i; } /** - * Examine if all values satisfy a test. + * Scan from left, until a test passes. * @param x an array * @param ft test function (v, i, x) - * @returns true if ft(vᵢ) = true for all vᵢ ∈ x + * @returns first index where test passes */ -export function every(x: T[], ft: TestFunction | null=null): boolean { - if (ft) return x.every(ft); - else return everyBoolean(x); +export function scanUntil(x: T[], ft: TestFunction): number { + var i = -1; + for (var v of x) + if (ft(v, ++i, x)) return i; + return ++i; } -export {every as allOf}; -function everyBoolean(x: T[]): boolean { - for (var i=0, I=x.length; i(x: T[], ft: TestFunction): number { + for (var i=x.length-1; i>=0; --i) + if (ft(x[i], i, x)) break; + return ++i; } /** - * Transform values of an array. + * Find index of first value passing a test. * @param x an array - * @param fm map function (v, i, x) - * @returns [fm(v₀), fm(v₁), ...] | vᵢ ∈ x + * @param ft test function (v, i, x) + * @returns first index of value, -1 if not found */ -export function map(x: T[], fm: MapFunction): (T|U)[] { - return x.map(fm); +export function search(x: T[], ft: TestFunction): number { + return x.findIndex(ft); } +export {search as findIndex}; /** - * Transform values of an array! - * @param x an array (updated!) - * @param fm map function (v, i, x) - * @returns x = [fm(v₀), fm(v₁), ...]; vᵢ ∈ x + * Find index of last value passing a test. + * @param x an array + * @param ft test function (v, i, x) + * @returns last index of value, -1 if not found */ -export function map$(x: T[], fm: MapFunction): T[] { - for (var i=0, I=x.length; i(x: T[], ft: TestFunction): number { + for (var i=x.length-1; i>=0; --i) + if (ft(x[i], i, x)) return i; + return -1; } +export {searchRight as findLastIndex}; /** - * Reduce values of array to a single value. + * Find indices of values passing a test. * @param x an array - * @param fr reduce function (acc, v, i, x) - * @param acc initial value - * @returns fr(fr(acc, v₀), v₁)... | fr(acc, v₀) = v₀ if acc not given + * @param ft test function (v, i, x) + * @returns indices of value */ -export function reduce(x: T[], fr: ReduceFunction, acc?: T|U): T|U { - var init = arguments.length <= 2; - return init? x.reduce(fr as any) : x.reduce(fr, acc); +export function searchAll(x: T[], ft: TestFunction): number[] { + var i = -1, a = []; + for (var v of x) + if (ft(v, ++i, x)) a.push(i); + return a; } /** - * Reduce values from right, to a single value. + * Find first index of a value. * @param x an array - * @param fr reduce function (acc, v, i, x) - * @param acc initial value - * @returns fr(fr(acc, vₓ₋₀), vₓ₋₁)... | fr(acc, vₓ₋₀) = vₓ₋₀ if acc not given + * @param v search value + * @param fc compare function (a, b) + * @param fm map function (v, i, x) + * @returns first index of value, -1 if not found */ -export function reduceRight(x: T[], fr: ReduceFunction, acc?: T|U): T|U { - var init = arguments.length <= 2; - for (var i=x.length-1; i>=0; --i) { - if (init) { acc = x[i]; init = false; } - else acc = fr(acc, x[i], i, x); +export function searchValue(x: T[], v: T, fc: CompareFunction | null=null, fm: MapFunction | null=null): number { + var fc = fc || COMPARE; + var fm = fm || IDENTITY; + var w = fm(v, 0, null), i = -1; + for (var vx of x) { + var wx = fm(vx, ++i, x); + if (fc(wx, w)===0) return i; } - return acc; + return -1; } /** - * Perform exclusive prefix scan from left to right. + * Find last index of a value. * @param x an array - * @param fr reduce function (acc, v, i, x) - * @param acc initial value - * @returns [acc, fr(acc, v₀), fr(fr(acc, v₀), v₁)...] + * @param v search value + * @param fc compare function (a, b) + * @param fm map function (v, i, x) + * @returns last index of value, -1 if not found */ -export function exclusiveScan(x: T[], fr: ReduceFunction, acc: T|U): (T|U)[] { - var a = []; - for (var i=0, I=x.length; i(x: T[], v: T, fc: CompareFunction | null=null, fm: MapFunction | null=null): number { + var fc = fc || COMPARE; + var fm = fm || IDENTITY; + var w = fm(v, 0, null); + for (var i=x.length-1; i>=0; --i) { + var wx = fm(x[i], i, x); + if (fc(wx, w)===0) return i; } - return a; + return -1; } /** - * Perform exclusive prefix scan from left to right! - * @param x an array (updated!) - * @param fr reduce function (acc, v, i, x) - * @param acc initial value - * @returns x = [acc, fr(acc, v₀), fr(fr(acc, v₀), v₁)...] - */ -export function exclusiveScan$(x: T[], fr: ReduceFunction, acc: T): T[] { - for (var i=0, I=x.length; i(x: T[], v: T, fc: CompareFunction | null=null, fm: MapFunction | null=null): number[] { + var fc = fc || COMPARE; + var fm = fm || IDENTITY; + var w = fm(v, 0, null); + var i = -1, a = []; + for (var vx of x) { + var wx = fm(vx, ++i, x); + if (fc(wx, w)===0) a.push(i); } - return x; + return a; } /** - * Perform inclusive prefix scan from left to right. + * Find first index of an adjacent duplicate value. * @param x an array - * @param fr reduce function (acc, v, i, x) - * @param acc initial value - * @returns [fr(acc, v₀), fr(fr(acc, v₀), v₁)...] + * @param fc compare function (a, b) + * @param fm map function (v, i, x) + * @returns index of first adjacent duplicate value, -1 if none */ -export function inclusiveScan(x: T[], fr: ReduceFunction, acc: T|U): (T|U)[] { - var a = []; - for (var i=0, I=x.length; i(x: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): number { + var fc = fc || COMPARE; + var fm = fm || IDENTITY; + var X = x.length; + if (X<=1) return -1; + var w0 = fm(x[0], 0, x); + for (var i=1; i(x: T[], fr: ReduceFunction, acc: T): T[] { - for (var i=0, I=x.length; i(x: T[], y: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): number { + var fc = fc || COMPARE; + var fm = fm || IDENTITY; + var X = x.length; + var Y = y.length; + for (var i=0, I=Math.min(X, Y); i(x: T[], fc: CombineFunction, acc: T): T[] { - var a = []; - if (x.length>0) a.push(fc(acc, x[0])); - for (var i=1, I=x.length; i(x: T[], y: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): number { + var fc = fc || COMPARE; + var fm = fm || IDENTITY; + var X = x.length, Y = y.length; + for (var i=0; i<=X-Y; ++i) + if (isInfixAt(x, y, i, fc, fm)) return i; + return -1; } -// adjacentMap()? /** - * Combine adjacent values of an array! - * @param x an array (updated!) - * @param fc combine function (u, v) - * @param acc initial value - * @returns x = [fc(acc, v₀), fc(v₀, v₁)...] | vᵢ ∈ x + * Find last index of an infix. + * @param x an array + * @param y search infix + * @param fc compare function (a, b) + * @param fm map function (v, i, x) + * @returns first i | x[i..i+|y|] = y else -1 */ -export function adjacentCombine$(x: T[], fc: CombineFunction, acc: T): T[] { - var X = x.length; - if (X===0) return x; - var v = x[0]; - x[0] = fc(acc, v); - for (var i=1; i(x: T[], y: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): number { + var fc = fc || COMPARE; + var fm = fm || IDENTITY; + var X = x.length, Y = y.length; + for (var i=X-Y; i>=0; --i) + if (isInfixAt(x, y, i, fc, fm)) return i; + return -1; } /** - * Keep values which pass a test. + * Find indices of an infix. * @param x an array - * @param ft test function (v, i, x) - * @returns [v₀, v₁, ...] | ft(vᵢ) = true; vᵢ ∈ x + * @param y search infix + * @param fc compare function (a, b) + * @param fm map function (v, i, x) + * @returns i₀, i₁, ... | x[j..j+|y|] = y; j ∈ [i₀, i₁, ...] */ -export function filter(x: T[], ft: TestFunction): T[] { - return x.filter(ft); +export function searchInfixAll(x: T[], y: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): number[] { + var fc = fc || COMPARE; + var fm = fm || IDENTITY; + var X = x.length, Y = y.length, a = []; + for (var i=0; i<=X-Y; ++i) + if (isInfixAt(x, y, i, fc, fm)) a.push(i); + return a; +} + + +function isInfixAt(x: T[], y: T[], i: number, fc: CompareFunction, fm: MapFunction): boolean { + var Y = y.length; + for (var j=0; j(x: T[], ft: TestFunction): T[] { - for (var i=0, j=0, I=x.length; i(x: T[], y: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): number { + var fc = fc || COMPARE; + var fm = fm || IDENTITY; + var y1 = [...y].map(fm), Y = y1.length; + var a = -1, i = -1, j = 0; + for (var vx of x) { + var wx = fm(vx, ++i, x); + if (fc(wx, y1[j])!==0) continue; + if (a<0) a = i; + if (++j>=Y) return a; + } + return -1; } /** - * Keep values at given indices. + * Examine if array has a value. * @param x an array - * @param is indices - * @returns v₀, v₁, ... | vᵢ = x[i]; i ∈ is + * @param v search value + * @param fc compare function (a, b) + * @param fm map function (v, i, x) + * @returns v ∈ x? */ -export function filterAt(x: T[], is: number[]): T[] { - var X = x.length, a = []; - for (var i of is) - if (i>=0 && i(x: T[], v: T, fc: CompareFunction | null=null, fm: MapFunction | null=null): boolean { + return searchValue(x, v, fc, fm) >= 0; } /** - * Discard values which pass a test. + * Examine if array starts with a prefix. * @param x an array - * @param ft test function (v, i, x) - * @returns [v₀, v₁, ...] | ft(vᵢ) = false; vᵢ ∈ x + * @param y search prefix + * @param fc compare function (a, b) + * @param fm map function (v, i, x) + * @returns x[0..|y|] = y? */ -export function reject(x: T[], ft: TestFunction): T[] { - var i = -1, a = []; - for (var v of x) - if (!ft(v, ++i, x)) a.push(v); - return a; +export function hasPrefix(x: T[], y: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): boolean { + var Y = y.length; + return Y===0 || compare(x.slice(0, Y), y, fc, fm)===0; } +export {hasPrefix as startsWith}; /** - * Discard values which pass a test! - * @param x an array (updated!) - * @param ft test function (v, i, x) - * @returns x = [v₀, v₁, ...] | ft(vᵢ) = false; vᵢ ∈ x + * Examine if array ends with a suffix. + * @param x an array + * @param y search suffix + * @param fc compare function (a, b) + * @param fm map function (v, i, x) + * @returns x[|x|-|y|..] = y? */ -export function reject$(x: T[], ft: TestFunction): T[] { - for (var i=0, j=0, I=x.length; i(x: T[], y: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): boolean { + var Y = y.length; + return Y===0 || compare(x.slice(-Y), y, fc, fm)===0; } +export {hasSuffix as endsWith}; /** - * Discard values at given indices. + * Examine if array contains an infix. * @param x an array - * @param is indices - * @returns [v₀, v₁, ...] | vᵢ = x[i]; i ∉ is + * @param y search infix + * @param fc compare function (a, b) + * @param fm map function (v, i, x) + * @returns x[i..I] = y for some i, I? */ -export function rejectAt(x: T[], is: number[]): T[] { - var i = -1, a = []; - for (var v of x) - if (!is.includes(++i)) a.push(v); - return a; +export function hasInfix(x: T[], y: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): boolean { + return searchInfix(x, y, fc, fm) >= 0; } /** - * Produce accumulating values. + * Examine if array has a subsequence. * @param x an array - * @param fr reduce function (acc, v, i, x) - * @param acc initial value - * @returns [fr(acc, v₀), fr(fr(acc, v₀), v₁), ...] | fr(acc, v₀) = v₀ if acc not given + * @param y search subsequence + * @param fc compare function (a, b) + * @param fm map function (v, i, x) + * @returns x[i₀] ⧺ x[i₁] ⧺ ... = y, for some i₀, i₁, ...? | i₀ < i₁ < ... */ -export function accumulate(x: T[], fr: ReduceFunction, acc?: T|U): T|U[] { - var init = arguments.length <= 2; - var i = -1, a = []; - for (var v of x) { - if (init) { init = false; acc = v; ++i; } - else acc = fr(acc, v, ++i, x); - a.push(acc); - } - return a; +export function hasSubsequence(x: T[], y: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): boolean { + return searchSubsequence(x, y, fc, fm) >= 0; } /** - * Flatten nested array to given depth. - * @param x a nested array - * @param n maximum depth [-1 ⇒ all] - * @param fm map function (v, i, x) - * @param ft flatten test function (v, i, x) [is] - * @returns flat iterable + * Examine if array has a permutation. + * @param x an array + * @param y search permutation + * @param fc map function (v, i, x) + * @param fm compare function (a, b) + * @returns x contains a shuffled version of y? */ -export function flat(x: any[], n: number=-1, fm: MapFunction | null=null, ft: TestFunction | null=null): any[] { - var fm = fm || IDENTITY; - var ft = ft || is; - return flatTo$([], x, n, fm, ft); +export function hasPermutation(x: T[], y: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): boolean { + var x1 = fm? x.map(fm) : x.slice(); + var y1 = fm? y.map(fm) : y.slice(); + return hasSubsequence(x1.sort(), y1.sort(), fc, fm); } +// #endregion + -function flatTo$(a: any[], x: any[], n: number, fm: MapFunction, ft: TestFunction): any[] { - var i = -1; - for (var v of x) { - var w = fm(v, ++i, x); - if (n!==0 && ft(w, i, x)) flatTo$(a, v, n-1, fm, ft); - else a.push(w); - } - return a; -} +// #region FUNCTIONAL +// ------------------ + /** - * Flatten nested array, based on map function. + * Call a function for each value. * @param x an array - * @param fm map function (v, i, x) - * @param ft flatten test function (v, i, x) [is] - * @returns flat iterable + * @param fp process function (v, i, x) */ -export function flatMap(x: any[], fm: MapFunction | null=null, ft: TestFunction | null=null): any[] { - var fm = fm || IDENTITY; - var ft = ft || is; - var i = -1, a = []; - for (var v of x) { - var w = fm(v, ++i, x); - if (ft(w, i, x)) concat$(a, w); - else a.push(w); - } - return a; +export function forEach(x: T[], fp: ProcessFunction): void { + x.forEach(fp); } /** - * Combine values from arrays. - * @param xs arrays - * @param fm map function (vs, i) - * @param fe end function (dones) [some] - * @param vd default value - * @returns [fm([x₀[0], x₁[0], ...]), fm([x₀[1], x₁[1], ...]), ...] + * Examine if any value satisfies a test. + * @param x an array + * @param ft test function (v, i, x) + * @returns true if ft(vᵢ) = true for some vᵢ ∈ x */ -export function zip(xs: T[][], fm: MapFunction | null=null, fe: EndFunction=null, vd?: T): (T[]|U)[] { - var fm = fm || IDENTITY; - var fe = fe || some as EndFunction; - var X = xs.length, a = []; - if (X===0) return a; - var ds = new Array(X).fill(false); - var ls = xs.map(x => x.length); - for (var i=0;; ++i) { - for (var j=0, vs=[]; j=ls[j]; - vs[j] = ds[j]? vd : xs[j][i]; - } - if (fe(ds)) break; - a.push(fm(vs, i, null)); - } - return a; +export function some(x: T[], ft: TestFunction | null=null): boolean { + if (ft) return x.some(ft); + else return someBoolean(x); } +export {some as anyOf}; +function someBoolean(x: T[]): boolean { + for (var i=0, I=x.length; i(x: T[], v: T, i: number=0, I: number=x.length): T[] { - return x.slice().fill(v, i, I); +export function every(x: T[], ft: TestFunction | null=null): boolean { + if (ft) return x.every(ft); + else return everyBoolean(x); } +export {every as allOf}; - -/** - * Fill with given value! - * @param x an array (updated!) - * @param v value - * @param i start index [0] - * @param I end index [|x|] - * @returns x | x[i..I] = v - */ -export function fill$(x: T[], v: T, i: number=0, I: number=x.length): T[] { - return x.fill(v, i, I); +function everyBoolean(x: T[]): boolean { + for (var i=0, I=x.length; i(x: T[], ...vs: T[]): T[] { - return x.concat(vs); +export function map(x: T[], fm: MapFunction): (T|U)[] { + return x.map(fm); } -export {push as pushBack}; -export {push as append}; /** - * Add values to the end! + * Transform values of an array! * @param x an array (updated!) - * @param vs values to add - * @returns x | x = [...x, ...vs] + * @param fm map function (v, i, x) + * @returns x = [fm(v₀), fm(v₁), ...]; vᵢ ∈ x */ -export function push$(x: T[], ...vs: T[]): T[] { - x.push(...vs); +export function map$(x: T[], fm: MapFunction): T[] { + for (var i=0, I=x.length; i(x: T[]): T[] { - return x.slice(0, -1); +export function reduce(x: T[], fr: ReduceFunction, acc?: T|U): T|U { + var init = arguments.length <= 2; + return init? x.reduce(fr as any) : x.reduce(fr, acc); } -export {pop as popBack}; /** - * Remove last value! - * @param x an array (updated!) - * @returns x = x[0..|x|-1] + * Reduce values from right, to a single value. + * @param x an array + * @param fr reduce function (acc, v, i, x) + * @param acc initial value + * @returns fr(fr(acc, vₓ₋₀), vₓ₋₁)... | fr(acc, vₓ₋₀) = vₓ₋₀ if acc not given */ -export function pop$(x: T[]): T[] { - x.pop(); - return x; +export function reduceRight(x: T[], fr: ReduceFunction, acc?: T|U): T|U { + var init = arguments.length <= 2; + for (var i=x.length-1; i>=0; --i) { + if (init) { acc = x[i]; init = false; } + else acc = fr(acc, x[i], i, x); + } + return acc; } -export {pop as popBack$}; /** - * Remove first value. + * Perform exclusive prefix scan from left to right. * @param x an array - * @returns x[1..] + * @param fr reduce function (acc, v, i, x) + * @param acc initial value + * @returns [acc, fr(acc, v₀), fr(fr(acc, v₀), v₁)...] */ -export function shift(x: T[]): T[] { - return x.slice(1); +export function exclusiveScan(x: T[], fr: ReduceFunction, acc: T|U): (T|U)[] { + var a = []; + for (var i=0, I=x.length; i(x: T[]): T[] { - x.shift(); +export function exclusiveScan$(x: T[], fr: ReduceFunction, acc: T): T[] { + for (var i=0, I=x.length; i(x: Iterable, ...vs: T[]): T[] { - return concat$(vs, x); +export function inclusiveScan(x: T[], fr: ReduceFunction, acc: T|U): (T|U)[] { + var a = []; + for (var i=0, I=x.length; i(x: T[], ...vs: T[]): T[] { - x.unshift(...vs); +export function inclusiveScan$(x: T[], fr: ReduceFunction, acc: T): T[] { + for (var i=0, I=x.length; i(x: T[], y: T[], j: number=0, i: number=0, I: number=y.length): T[] { - return copy$(x.slice(), y, j, i, I); +export function adjacentCombine(x: T[], fc: CombineFunction, acc: T): T[] { + var a = []; + if (x.length>0) a.push(fc(acc, x[0])); + for (var i=1, I=x.length; i(x: T[], y: T[], j: number=0, i: number=0, I: number=y.length): T[] { - var j = index(x, j); - var [i, I] = indexRange(y, i, I); - for (; i(x: T[], fc: CombineFunction, acc: T): T[] { + var X = x.length; + if (X===0) return x; + var v = x[0]; + x[0] = fc(acc, v); + for (var i=1; i(x: T[], j: number=0, i: number=0, I: number=x.length): T[] { - var I = i + Math.min(length(x, i, I), length(x, j)); - var p = x.slice(0, j); - var q = x.slice(i, I); - var r = x.slice(j+q.length); - return p.concat(q, r); +export function filter(x: T[], ft: TestFunction): T[] { + return x.filter(ft); } +export {filter as findAll}; /** - * Copy part of array within! + * Keep values which pass a test! * @param x an array (updated!) - * @param j write index [0] - * @param i read start index [0] - * @param I read end index [|x|] - * @returns x = x[0..j] ⧺ x[i..I] ⧺ x[j+I-i..] + * @param ft test function (v, i, x) + * @returns x = [v₀, v₁, ...] | ft(vᵢ) = true; vᵢ ∈ x */ -export function copyWithin$(x: T[], j: number=0, i: number=0, I: number=x.length): T[] { - return x.copyWithin(j, i, I); +export function filter$(x: T[], ft: TestFunction): T[] { + for (var i=0, j=0, I=x.length; i(x: T[], j: number=0, i: number=0, I: number=x.length): T[] { - if (j(x: T[], i: number, j: number, k: number): T[] { - return x.slice(0, i).concat( - x.slice(j, k), - x.slice(i, j), - x.slice(k) - ); -} - - -/** - * Move part of array within! - * @param x an array (updated!) - * @param j write index [0] - * @param i read start index [0] - * @param I read end index [|x|] - * @returns x = x[0..j] ⧺ x[i..I] ⧺ x[j..i] ⧺ x[I..] + * @param is indices + * @returns v₀, v₁, ... | vᵢ = x[i]; i ∈ is */ -export function moveWithin$(x: T[], j: number=0, i: number=0, I: number=x.length): T[] { - var p = x.slice(i, I), P = p.length; - if (j(x: T[], is: number[]): T[] { + var X = x.length, a = []; + for (var i of is) + if (i>=0 && i(x: T[], i: number, n: number=x.length-i, ...vs: T[]): T[] { - return concat$(x.slice(0, i), vs, x.slice(i+n)); +export function reject(x: T[], ft: TestFunction): T[] { + var i = -1, a = []; + for (var v of x) + if (!ft(v, ++i, x)) a.push(v); + return a; } -export {splice as toSpliced}; /** - * Remove or replace existing values! + * Discard values which pass a test! * @param x an array (updated!) - * @param i remove index - * @param n number of values to remove [rest] - * @param vs values to insert - * @returns x = x[0..i] ⧺ vs ⧺ x[i+n..] + * @param ft test function (v, i, x) + * @returns x = [v₀, v₁, ...] | ft(vᵢ) = false; vᵢ ∈ x */ -export function splice$(x: T[], i: number, n: number=x.length-i, ...vs: T[]): T[] { - x.splice(i, n, ...vs); +export function reject$(x: T[], ft: TestFunction): T[] { + for (var i=0, j=0, I=x.length; i(x: T[], ft: TestFunction): T[][] { - var i = -1, a = [], b = []; - for (var v of x) { - if (!ft(v, ++i, x)) b.push(v); - else if (b.length) { a.push(b); b = []; } - } - if (b.length) a.push(b); +export function rejectAt(x: T[], is: number[]): T[] { + var i = -1, a = []; + for (var v of x) + if (!is.includes(++i)) a.push(v); return a; } /** - * Break array considering indices as separator. + * Produce accumulating values. * @param x an array - * @param is indices (sorted) - * @returns x[j..k] ⧺ x[l..m] ⧺ ... | ft(x[i]) = true; i = 0..j / k..l / ...; i ∈ is + * @param fr reduce function (acc, v, i, x) + * @param acc initial value + * @returns [fr(acc, v₀), fr(fr(acc, v₀), v₁), ...] | fr(acc, v₀) = v₀ if acc not given */ -export function splitAt(x: T[], is: number[]): T[][] { - var i = -1, a = [], b = []; +export function accumulate(x: T[], fr: ReduceFunction, acc?: T|U): T|U[] { + var init = arguments.length <= 2; + var i = -1, a = []; for (var v of x) { - if (!is.includes(++i)) b.push(v); - else if(b.length) { a.push(b); b = []; } + if (init) { init = false; acc = v; ++i; } + else acc = fr(acc, v, ++i, x); + a.push(acc); } - if (b.length) a.push(b); return a; } /** - * Break array when test passes. - * @param x an array - * @param ft test function (v, i, x) - * @returns x[0..j] ⧺ x[j..k] ⧺ ... | ft(x[i]) = true; i = j, k, ... + * Flatten nested array to given depth. + * @param x a nested array + * @param n maximum depth [-1 ⇒ all] + * @param fm map function (v, i, x) + * @param ft flatten test function (v, i, x) [is] + * @returns flat iterable */ -export function cut(x: T[], ft: TestFunction): T[][] { - var j = 0, a = []; - for (var i=0, I=x.length; i | null=null, ft: TestFunction | null=null): any[] { + var fm = fm || IDENTITY; + var ft = ft || is; + return flatTo$([], x, n, fm, ft); } - -/** - * Break array after test passes. - * @param x an array - * @param ft test function (v, i, x) - * @returns x[0..j+1] ⧺ x[j+1..k] ⧺ ... | ft(x[i]) = true; i = j, k, ... - */ -export function cutRight(x: T[], ft: TestFunction): T[][] { - var j = 0, a = []; - for (var i=0, I=x.length; i, ft: TestFunction): any[] { + var i = -1; + for (var v of x) { + var w = fm(v, ++i, x); + if (n!==0 && ft(w, i, x)) flatTo$(a, v, n-1, fm, ft); + else a.push(w); } - a.push(x.slice(j)); return a; } /** - * Break array at given indices. + * Flatten nested array, based on map function. * @param x an array - * @param is split indices (sorted) - * @returns x[0..j] ⧺ x[j..k] ⧺ ... | ft(x[i]) = true; i = j, k, ...; i ∈ is + * @param fm map function (v, i, x) + * @param ft flatten test function (v, i, x) [is] + * @returns flat iterable */ -export function cutAt(x: T[], is: number[]): T[][] { - var j = 0, a = []; - for (var i of is) { - i = Math.max(j, index(x, i)); - a.push(x.slice(j, i)); - j = i; +export function flatMap(x: any[], fm: MapFunction | null=null, ft: TestFunction | null=null): any[] { + var fm = fm || IDENTITY; + var ft = ft || is; + var i = -1, a = []; + for (var v of x) { + var w = fm(v, ++i, x); + if (ft(w, i, x)) concat$(a, w); + else a.push(w); } - a.push(x.slice(i)); return a; } /** - * Break array after given indices. - * @param x an array - * @param is split indices (sorted) - * @returns x[0..j+1] ⧺ x[j+1..k] ⧺ ... | ft(x[i]) = true; i = j, k, ...; i ∈ is - */ -export function cutAtRight(x: T[], is: number[]): T[][] { - return cutAt(x, is.map(i => i+1)); -} - - -/** - * Keep similar values together and in order. - * @param x an array - * @param fc compare function (a, b) - * @param fm map function (v, i, x) - * @returns x[0..k], x[k..l], ... | fc(x[i], x[j]) = 0; i, j = 0..k / k..l / ... + * Combine values from arrays. + * @param xs arrays + * @param fm map function (vs, i) + * @param fe end function (dones) [some] + * @param vd default value + * @returns [fm([x₀[0], x₁[0], ...]), fm([x₀[1], x₁[1], ...]), ...] */ -export function group(x: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): T[][] { - var fc = fc || COMPARE; +export function zip(xs: T[][], fm: MapFunction | null=null, fe: EndFunction=null, vd?: T): (T[]|U)[] { var fm = fm || IDENTITY; - var a = [], b = []; - var u: T|U, i = -1; - for (var v of x) { - var w = fm(v, ++i, x); - if (i===0 || fc(u, w)===0) b.push(v); - else { a.push(b); b = [v]; } - u = w; + var fe = fe || some as EndFunction; + var X = xs.length, a = []; + if (X===0) return a; + var ds = new Array(X).fill(false); + var ls = xs.map(x => x.length); + for (var i=0;; ++i) { + for (var j=0, vs=[]; j=ls[j]; + vs[j] = ds[j]? vd : xs[j][i]; + } + if (fe(ds)) break; + a.push(fm(vs, i, null)); } - a.push(b); return a; } -/** - * Segregate values by test result. - * @param x an array - * @param ft test function (v, i, x) - * @returns [satisfies, doesnt] - */ -export function partition(x: T[], ft: TestFunction): [T[], T[]] { - var t: T[] = [], f: T[] = [], i = -1; - for (var v of x) { - if (ft(v, ++i, x)) t.push(v); - else f.push(v); - } - return [t, f]; -} +// TODO: zip2()? +// #endregion + + + +// #region MANIPULATION +// -------------------- /** - * Segregate each distinct value. + * Fill with given value. * @param x an array - * @param fm map function (v, i, x) - * @returns Map \{key ⇒ values\} + * @param v value + * @param i start index [0] + * @param I end index [|x|] + * @returns x' | x' = x; x'[i..I] = v */ -export function partitionEach(x: T[], fm: MapFunction | null=null): Map { - var fm = fm || IDENTITY; - var i = -1, a = new Map(); - for (var v of x) { - var w = fm(v, ++i, x); - if (!a.has(w)) a.set(w, []); - a.get(w).push(v); - } - return a; +export function fill(x: T[], v: T, i: number=0, I: number=x.length): T[] { + return x.slice().fill(v, i, I); } -export {partitionEach as groupToMap}; -export {partitionEach as partitionAs}; // DEPRECATED /** - * Break array into chunks of given size. - * @param x an array - * @param n chunk size [1] - * @param s chunk step [n] - * @returns x[0..n], x[s..s+n], x[2s..2s+n], ... + * Fill with given value! + * @param x an array (updated!) + * @param v value + * @param i start index [0] + * @param I end index [|x|] + * @returns x | x[i..I] = v */ -export function chunk(x: T[], n: number=1, s: number=n): T[][] { - var a = []; - for (var i=0, I=x.length; i(x: T[], v: T, i: number=0, I: number=x.length): T[] { + return x.fill(v, i, I); } /** - * Obtain values that cycle through array. + * Add value to the end. * @param x an array - * @param i start index [0] - * @param n number of values [|x|] + * @param vs values to add + * @returns [...x, ...vs] */ -export function cycle(x: T[], i: number=0, n: number=x.length): T[] { - var X = x.length; - if (n<=0 || X===0) return []; - var i = index(x, i); - var a = x.slice(i, i+n); - n -= a.length; - for (var m=0, M=Math.floor(n/X); m(x: T[], ...vs: T[]): T[] { + return x.concat(vs); } +export {push as pushBack}; +export {push as append}; /** - * Repeat an array given times. - * @param x an array - * @param n times [1] - * @returns ...x, ...x, ...(n times) + * Add values to the end! + * @param x an array (updated!) + * @param vs values to add + * @returns x | x = [...x, ...vs] */ -export function repeat(x: T[], n: number=1): T[] { - for (var a=[]; n>0; --n) - concat$(a, x); - return a; +export function push$(x: T[], ...vs: T[]): T[] { + x.push(...vs); + return x; } +export {push$ as pushBack$}; +export {push$ as append$}; /** - * Reverse the values. + * Remove last value. * @param x an array - * @returns x[|x|-1], x[|x|-2], ..., x[1], x[0] + * @returns x[0..|x|-1] */ -export function reverse(x: T[]): T[] { - return x.slice().reverse(); +export function pop(x: T[]): T[] { + return x.slice(0, -1); } -export {reverse as toReversed}; +export {pop as popBack}; /** - * Reverse the values! + * Remove last value! * @param x an array (updated!) - * @returns x = [x[|x|-1], x[|x|-2], ..., x[1], x[0]] + * @returns x = x[0..|x|-1] */ -export function reverse$(x: T[]): T[] { - return x.reverse(); +export function pop$(x: T[]): T[] { + x.pop(); + return x; } +export {pop as popBack$}; /** - * Rotate values in array. + * Remove first value. * @param x an array - * @param n rotate amount (+ve: left, -ve: right) [0] - * @returns x[n..] ⧺ x[0..n] + * @returns x[1..] */ -export function rotate(x: T[], n: number=0): T[] { - var n = mod(n, x.length); - return concat$(x.slice(n), x.slice(0, n)); +export function shift(x: T[]): T[] { + return x.slice(1); } +export {shift as popFront}; /** - * Rotate values in array! + * Remove first value! * @param x an array (updated!) - * @param n rotate amount (+ve: left, -ve: right) [0] - * @returns x = x[n..] ⧺ x[0..n] + * @returns x = x[1..] */ -export function rotate$(x: T[], n: number=0): T[] { - var n = mod(n, x.length); - var y = x.slice(0, n); - x.copyWithin(0, n); - return copy$(x, y, x.length-n); +export function shift$(x: T[]): T[] { + x.shift(); + return x; } +export {shift$ as popFront$}; /** - * Place a separator between every value. + * Add values to the start. * @param x an array - * @param v separator - * @returns [x[0], v, x[1], v, ..., x[|x|-1]] + * @param vs values to add + * @returns [...vs, ...x] */ -export function intersperse(x: T[], v: T): T[] { - var a = [], i = -1; - for (var u of x) { - if (++i>0) a.push(v); - a.push(u); - } - return a; +export function unshift(x: Iterable, ...vs: T[]): T[] { + return concat$(vs, x); } +export {unshift as pushFront}; +export {unshift as prepend}; /** - * Estimate new values between existing ones. - * @param x an array - * @param fc combine function (a, b) - * @returns [x[0], fc(x[0], x[1]), x[1], fc(x[1], x[2]), ..., x[|x|-1]] + * Add values to the start! + * @param x an array (updated!) + * @param vs values to add + * @returns x = [...vs, ...x] */ -export function interpolate(x: T[], fc: CombineFunction): T[] { - var a = [], u: T, i = -1; - for (var v of x) { - if (++i>0) a.push(fc(u, v)); - a.push(u = v); - } - return a; +export function unshift$(x: T[], ...vs: T[]): T[] { + x.unshift(...vs); + return x; } +export {unshift$ as pushFront$}; +export {unshift$ as prepend$}; /** - * Place values of an array between another. - * @param x an array - * @param y another array - * @param m number of values from x [1] - * @param n number of values from y [1] - * @param s step size for x [m] - * @param t step size for y [n] - * @returns x[0..m], y[0..n], x[s..s+m], y[t..t+n], ..., x[k*s..|x|-1] | k ∈ W + * Copy part of array to another. + * @param x target array + * @param y source array + * @param j write index [0] + * @param i read start index [0] + * @param I read end index [|x|] + * @returns x[0..j] ⧺ y[i..I] ⧺ x[j+I-i..] */ -export function intermix(x: T[], y: T[], m: number=1, n: number=1, s: number=m, t: number=n): T[] { - var X = x.length, Y = y.length, a = []; - for (var i=0, j=0; i0) { - for (var k=j, K=k+n; k(x: T[], y: T[], j: number=0, i: number=0, I: number=y.length): T[] { + return copy$(x.slice(), y, j, i, I); } /** - * Place values from iterables alternately. - * @param xs arrays - * @returns x₀[0], x₁[0], ..., x₀[1], x₁[0], ... | [x₀, x₁, ...] = xs + * Copy part of array to another! + * @param x target array (updated!) + * @param y source array + * @param j write index [0] + * @param i read start index [0] + * @param I read end index [|x|] + * @returns x = x[0..j] ⧺ y[i..I] ⧺ x[j+I-i..] */ -export function interleave(xs: T[][]): T[] { - var a = []; - for (var i=0;; ++i) { - var n = 0; - for (var x of xs) - if (i(x: T[], y: T[], j: number=0, i: number=0, I: number=y.length): T[] { + var j = index(x, j); + var [i, I] = indexRange(y, i, I); + for (; i(...xs: T[][]): T[] { - return [].concat(...xs); +export function copyWithin(x: T[], j: number=0, i: number=0, I: number=x.length): T[] { + var I = i + Math.min(length(x, i, I), length(x, j)); + var p = x.slice(0, j); + var q = x.slice(i, I); + var r = x.slice(j+q.length); + return p.concat(q, r); } /** - * Append values from arrays! + * Copy part of array within! * @param x an array (updated!) - * @param ys arrays to append - * @returns x = [...x, ...y₀, ...y₁, ...] | [y₀, y₁, ...] = ys + * @param j write index [0] + * @param i read start index [0] + * @param I read end index [|x|] + * @returns x = x[0..j] ⧺ x[i..I] ⧺ x[j+I-i..] */ -export function concat$(x: T[], ...ys: Iterable[]): T[] { - for (var y of ys) - x.push(...y); - return x; +export function copyWithin$(x: T[], j: number=0, i: number=0, I: number=x.length): T[] { + return x.copyWithin(j, i, I); } /** - * Join values together into a string. + * Move part of array within. * @param x an array - * @param sep separator [,] - * @returns "$\{v₀\}$\{sep\}$\{v₁\}..." | vᵢ ∈ x + * @param j write index [0] + * @param i read start index [0] + * @param I read end index [|x|] + * @returns x[0..j] ⧺ x[i..I] ⧺ x[j..i] ⧺ x[I..] */ -export function join(x: T[], sep: string=","): string { - return x.join(sep); +export function moveWithin(x: T[], j: number=0, i: number=0, I: number=x.length): T[] { + if (j(x: T[], i: number, j: number, k: number): T[] { + return x.slice(0, i).concat( + x.slice(j, k), + x.slice(i, j), + x.slice(k) + ); +} +/** + * Move part of array within! + * @param x an array (updated!) + * @param j write index [0] + * @param i read start index [0] + * @param I read end index [|x|] + * @returns x = x[0..j] ⧺ x[i..I] ⧺ x[j..i] ⧺ x[I..] + */ +export function moveWithin$(x: T[], j: number=0, i: number=0, I: number=x.length): T[] { + var p = x.slice(i, I), P = p.length; + if (j(x: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): boolean { - if (fc) return isUniqueDual(x, fc, fm); - else return isUniqueMap (x, fm); +export function splice(x: T[], i: number, n: number=x.length-i, ...vs: T[]): T[] { + return concat$(x.slice(0, i), vs, x.slice(i+n)); } +export {splice as toSpliced}; -function isUniqueMap(x: T[], fm: MapFunction | null=null): boolean { - var fm = fm || IDENTITY; - var s = new Set(), i = -1; - for (var v of x) { - var w = fm(v, ++i, x); - if (s.has(w)) return false; - s.add(w); - } - return true; -} -function isUniqueDual(x: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): boolean { - var fc = fc || COMPARE; - var fm = fm || IDENTITY; - var x1 = [...x].map(fm); - for (var wx of x1) { - for (var wy of x1) - if (fc(wx, wy)===0) return false; - } - return true; +/** + * Remove or replace existing values! + * @param x an array (updated!) + * @param i remove index + * @param n number of values to remove [rest] + * @param vs values to insert + * @returns x = x[0..i] ⧺ vs ⧺ x[i+n..] + */ +export function splice$(x: T[], i: number, n: number=x.length-i, ...vs: T[]): T[] { + x.splice(i, n, ...vs); + return x; } /** - * Examine if arrays have no value in common. + * Break array considering test as separator. * @param x an array - * @param y another array - * @param fc compare function (a, b) - * @param fm map function (v, i, x) - * @returns x ∩ y = Φ? + * @param ft test function (v, i, x) + * @returns x[j..k] ⧺ x[l..m] ⧺ ... | ft(x[i]) = true; i = 0..j / k..l / ... */ -export function isDisjoint(x: T[], y: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): boolean { - if (fc) return isDisjointDual(x, y, fc, fm); - else return isDisjointMap (x, y, fm); -} - -function isDisjointMap(x: T[], y: T[], fm: MapFunction | null=null): boolean { - var fm = fm || IDENTITY; - var s = toSet(y, fm), i = -1; +export function split(x: T[], ft: TestFunction): T[][] { + var i = -1, a = [], b = []; for (var v of x) { - var w = fm(v, ++i, x); - if (s.has(w)) return false; + if (!ft(v, ++i, x)) b.push(v); + else if (b.length) { a.push(b); b = []; } } - return true; + if (b.length) a.push(b); + return a; } -function isDisjointDual(x: T[], y: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): boolean { - var fc = fc || COMPARE; - var fm = fm || IDENTITY; - var y1 = [...y].map(fm), i = -1; - for (var vx of x) { - var wx = fm(vx, ++i, x); - for (var wy of y1) - if (fc(wx, wy)===0) return false; + +/** + * Break array considering indices as separator. + * @param x an array + * @param is indices (sorted) + * @returns x[j..k] ⧺ x[l..m] ⧺ ... | ft(x[i]) = true; i = 0..j / k..l / ...; i ∈ is + */ +export function splitAt(x: T[], is: number[]): T[][] { + var i = -1, a = [], b = []; + for (var v of x) { + if (!is.includes(++i)) b.push(v); + else if(b.length) { a.push(b); b = []; } } - return true; + if (b.length) a.push(b); + return a; } /** - * Remove duplicate values. + * Break array when test passes. * @param x an array - * @param fc compare function (a, b) - * @param fm map function (v, i, x) - * @returns v₀, v₁, ... | vᵢ ∈ x; vᵢ ≠ vⱼ ∀ i, j + * @param ft test function (v, i, x) + * @returns x[0..j] ⧺ x[j..k] ⧺ ... | ft(x[i]) = true; i = j, k, ... */ -export function unique(x: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): T[] { - if (fc) return uniqueDual(x, fc, fm); - else return uniqueMap (x, fm); +export function cut(x: T[], ft: TestFunction): T[][] { + var j = 0, a = []; + for (var i=0, I=x.length; i(x: T[], fm: MapFunction | null=null): T[] { - var fm = fm || IDENTITY; - var s = new Set(); - var i = -1, a = []; - for (var v of x) { - var w = fm(v, ++i, x); - if (s.has(w)) continue; - s.add(w); a.push(v); + +/** + * Break array after test passes. + * @param x an array + * @param ft test function (v, i, x) + * @returns x[0..j+1] ⧺ x[j+1..k] ⧺ ... | ft(x[i]) = true; i = j, k, ... + */ +export function cutRight(x: T[], ft: TestFunction): T[][] { + var j = 0, a = []; + for (var i=0, I=x.length; i(x: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): T[] { - var fc = fc || COMPARE; - var fm = fm || IDENTITY; - var i = -1, s = [], a = []; - x: for (var vx of x) { - var wx = fm(vx, ++i, x); - for (var ws of s) - if (fc(ws, wx)===0) continue x; - s.push(wx); a.push(vx); + +/** + * Break array at given indices. + * @param x an array + * @param is split indices (sorted) + * @returns x[0..j] ⧺ x[j..k] ⧺ ... | ft(x[i]) = true; i = j, k, ...; i ∈ is + */ +export function cutAt(x: T[], is: number[]): T[][] { + var j = 0, a = []; + for (var i of is) { + i = Math.max(j, index(x, i)); + a.push(x.slice(j, i)); + j = i; } + a.push(x.slice(i)); return a; } /** - * Obtain values present in any array. + * Break array after given indices. * @param x an array - * @param y another array - * @param fc compare function (a, b) - * @param fm map function (v, i, x) - * @returns x ∪ y = \{v | v ∈ x or v ∈ y\} + * @param is split indices (sorted) + * @returns x[0..j+1] ⧺ x[j+1..k] ⧺ ... | ft(x[i]) = true; i = j, k, ...; i ∈ is */ -export function union(x: T[], y: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): T[] { - return union$(x.slice(), y, fc, fm); +export function cutAtRight(x: T[], is: number[]): T[][] { + return cutAt(x, is.map(i => i+1)); } /** - * Obtain values present in any array! - * @param x an array (updated!) - * @param y another array + * Keep similar values together and in order. + * @param x an array * @param fc compare function (a, b) * @param fm map function (v, i, x) - * @returns x = x ∪ y = \{v | v ∈ x or v ∈ y\} + * @returns x[0..k], x[k..l], ... | fc(x[i], x[j]) = 0; i, j = 0..k / k..l / ... */ -export function union$(x: T[], y: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): T[] { - if (fc) return unionDual$(x, y, fc, fm); - else return unionMap$ (x, y, fm); -} - -function unionMap$(x: T[], y: T[], fm: MapFunction | null=null): T[] { - var fm = fm || IDENTITY; - var s = toSet(x, fm), i = -1; - for (var vy of y) { - var wy = fm(vy, ++i, y); - if (!s.has(wy)) x.push(vy); - } - return x; -} - -function unionDual$(x: T[], y: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): T[] { +export function group(x: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): T[][] { var fc = fc || COMPARE; var fm = fm || IDENTITY; - var x1 = x.map(fm), i = -1; - y: for (var vy of y) { - var wy = fm(vy, ++i, y); - for (var wx of x1) - if (fc(wx, wy)===0) continue y; - x.push(vy); + var a = [], b = []; + var u: T|U, i = -1; + for (var v of x) { + var w = fm(v, ++i, x); + if (i===0 || fc(u, w)===0) b.push(v); + else { a.push(b); b = [v]; } + u = w; } - return x; + a.push(b); + return a; } /** - * Obtain values present in both arrays. + * Segregate values by test result. * @param x an array - * @param y another array - * @param fc compare function (a, b) - * @param fm map function (v, i, x) - * @returns x ∩ y = \{v | v ∈ x, v ∈ y\} + * @param ft test function (v, i, x) + * @returns [satisfies, doesnt] */ -export function intersection(x: T[], y: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): T[] { - if (fc) return intersectionDual(x, y, fc, fm); - else return intersectionMap (x, y, fm); -} - -function intersectionMap(x: T[], y: T[], fm: MapFunction | null=null): T[] { - var fm = fm || IDENTITY; - var s = toSet(y, fm); - var i = -1, a = []; - for (var vx of x) { - var wx = fm(vx, ++i, x); - if (s.has(wx)) a.push(vx); - } - return a; -} - -function intersectionDual(x: T[], y: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): T[] { - var fc = fc || COMPARE; - var fm = fm || IDENTITY; - var y1 = [...y].map(fm); - var i = -1, a = []; - x: for (var vx of x) { - var wx = fm(vx, ++i, x); - for (var wy of y1) - if (fc(wx, wy)===0) { a.push(vx); continue x; } +export function partition(x: T[], ft: TestFunction): [T[], T[]] { + var t: T[] = [], f: T[] = [], i = -1; + for (var v of x) { + if (ft(v, ++i, x)) t.push(v); + else f.push(v); } - return a; + return [t, f]; } /** - * Obtain values not present in another array. + * Segregate each distinct value. * @param x an array - * @param y another array - * @param fc compare function (a, b) * @param fm map function (v, i, x) - * @returns x - y = \{v | v ∈ x, v ∉ y\} + * @returns Map \{key ⇒ values\} */ -export function difference(x: T[], y: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): T[] { - if (fc) return differenceDual(x, y, fc, fm); - else return differenceMap (x, y, fm); -} - -function differenceMap(x: T[], y: T[], fm: MapFunction | null=null): T[] { +export function partitionEach(x: T[], fm: MapFunction | null=null): Map { var fm = fm || IDENTITY; - var s = toSet(y, fm); - var i = -1, a = []; - for (var vx of x) { - var wx = fm(vx, ++i, x); - if (!s.has(wx)) a.push(vx); + var i = -1, a = new Map(); + for (var v of x) { + var w = fm(v, ++i, x); + if (!a.has(w)) a.set(w, []); + a.get(w).push(v); } return a; } +export {partitionEach as groupToMap}; +export {partitionEach as partitionAs}; // DEPRECATED -function differenceDual(x: T[], y: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): T[] { - var fc = fc || COMPARE; - var fm = fm || IDENTITY; - var y1 = [...y].map(fm); - var i = -1, a = []; - x: for (var vx of x) { - var wx = fm(vx, ++i, x); - for (var wy of y1) - if (fc(wx, wy)===0) continue x; - a.push(vx); - } + +/** + * Break array into chunks of given size. + * @param x an array + * @param n chunk size [1] + * @param s chunk step [n] + * @returns x[0..n], x[s..s+n], x[2s..2s+n], ... + */ +export function chunk(x: T[], n: number=1, s: number=n): T[][] { + var a = []; + for (var i=0, I=x.length; i(x: T[], y: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): T[] { - var x0 = fromIterable$(x); - var y0 = fromIterable$(y); - var ax = difference(x0, y0, fc, fm); - var ay = difference(y0, x0, fc, fm); - return concat$(ax, ay); +export function cycle(x: T[], i: number=0, n: number=x.length): T[] { + var X = x.length; + if (n<=0 || X===0) return []; + var i = index(x, i); + var a = x.slice(i, i+n); + n -= a.length; + for (var m=0, M=Math.floor(n/X); m(xs: T[][], fm: MapFunction | null=null): (T[]|U)[] { - var fm = fm || IDENTITY; - var XS = xs.length, a = []; - if (XS===0) return a; - var is = new Array(XS).fill(0); - var ls = xs.map(x => x.length); - if (ls.some(l => l===0)) return a; - for (var i=0;; ++i) { - for (var j=0, vs=[]; j=0; --r) { - if (++is[r](x: T[], n: number=1): T[] { + for (var a=[]; n>0; --n) + concat$(a, x); return a; } -// #endregion - - -// #region SORT -// ------------ - /** - * Arrange values in order. + * Reverse the values. * @param x an array - * @param fc compare function (a, b) - * @param fm map function (v, i, x) - * @param fs swap function (x, i, j) - * @returns x' | x' = x; x'[i] ≤ x'[j] ∀ i ≤ j + * @returns x[|x|-1], x[|x|-2], ..., x[1], x[0] */ -export function sort(x: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null, fs: SwapFunction | null=null): T[] { - return sort$(x.slice(), fc, fm, fs); +export function reverse(x: T[]): T[] { + return x.slice().reverse(); } -export {sort as toSorted}; +export {reverse as toReversed}; /** - * Arrange values in order! + * Reverse the values! * @param x an array (updated!) - * @param fc compare function (a, b) - * @param fm map function (v, i, x) - * @param fs swap function (x, i, j) - * @returns x | x[i] ≤ x[j] ∀ i ≤ j + * @returns x = [x[|x|-1], x[|x|-2], ..., x[1], x[0]] */ -export function sort$(x: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null, fs: SwapFunction | null=null): T[] { - var fc = fc || COMPARE; - if (!fm) return x.sort(fc); - var X = x.length; - var fm = fm || IDENTITY; - var fs = fs || swapRaw$; - return partialIntroSort$(x, 0, X, X, fc, fm, fs); +export function reverse$(x: T[]): T[] { + return x.reverse(); } /** - * Partially arrange values in order. + * Rotate values in array. * @param x an array - * @param i start index - * @param I end index (exclusive) - * @param n minimum number of values to sort - * @param fc compare function (a, b) - * @param fm map function (v, i, x) - * @param fs swap function (x, i, j) - * @returns x' | x' = x; x'[i] ≤ x'[j] ∀ i ≤ j + * @param n rotate amount (+ve: left, -ve: right) [0] + * @returns x[n..] ⧺ x[0..n] */ -export function partialSort(x: T[], i: number, I: number, n: number, fc: CompareFunction | null=null, fm: MapFunction | null=null, fs: SwapFunction | null=null): T[] { - return partialSort$(x.slice(), i, I, n, fc, fm, fs); +export function rotate(x: T[], n: number=0): T[] { + var n = mod(n, x.length); + return concat$(x.slice(n), x.slice(0, n)); } /** - * Partially arrange values in order! + * Rotate values in array! * @param x an array (updated!) - * @param i start index - * @param I end index (exclusive) - * @param n minimum number of values to sort - * @param fc compare function (a, b) - * @param fm map function (v, i, x) - * @param fs swap function (x, i, j) - * @returns x | x[i] ≤ x[j] ∀ i ≤ j + * @param n rotate amount (+ve: left, -ve: right) [0] + * @returns x = x[n..] ⧺ x[0..n] */ -export function partialSort$(x: T[], i: number, I: number, n: number, fc: CompareFunction | null=null, fm: MapFunction | null=null, fs: SwapFunction | null=null): T[] { - var fc = fc || COMPARE; - var fm = fm || IDENTITY; - var fs = fs || swapRaw$; - // TODO: Check various sort functions. - return partialIntroSort$(x, i, I, n, fc, fm, fs); +export function rotate$(x: T[], n: number=0): T[] { + var n = mod(n, x.length); + var y = x.slice(0, n); + x.copyWithin(0, n); + return copy$(x, y, x.length-n); } /** - * Partially arrange values in order! - * @param x an array (updated!) - * @param i start index - * @param I end index (exclusive) - * @param n minimum number of values to sort - * @param fc compare function (a, b) - * @param fm map function (v, i, x) - * @param fs swap function (x, i, j) - * @returns x | x[i] ≤ x[j] ∀ i ≤ j + * Place a separator between every value. + * @param x an array + * @param v separator + * @returns [x[0], v, x[1], v, ..., x[|x|-1]] */ -function partialIntroSort$(x: T[], i: number, I: number, n: number, fc: CompareFunction, fm: MapFunction, fs: SwapFunction): T[] { - var d = Math.floor(Math.log2(I-i)*2); // Maximum depth of recursion - var s = 16; // When to switch to insertion sort - return partialIntroSortHelper$(x, i, I, d, s, n, fc, fm, fs); +export function intersperse(x: T[], v: T): T[] { + var a = [], i = -1; + for (var u of x) { + if (++i>0) a.push(v); + a.push(u); + } + return a; } -// Partially arrange values in order with hybrid quick sort, heap sort, and insertion sort. -function partialIntroSortHelper$(x: T[], i: number, I: number, d: number, s: number, n: number, fc: CompareFunction, fm: MapFunction, fs: SwapFunction): T[] { - if (n<=0 || I-i<=1) return x; // Nothing to sort - if (I-i<=s) return partialInsertionSort$(x, i, I, n, fc, fm, fs); // Insertion sort - if (d<=0) return partialHeapSort$(x, i, I, n, fc, fm, fs); // Heap sort - var p = i + Math.floor((I-i)*Math.random()); // Choose pivot - var p = quickSortPartition$(x, i, I, p, fc, fm, fs); // Partition array - partialIntroSortHelper$(x, i, p, d, s, Math.min(p-i, n), fc, fm, fs); // Sort left part - partialIntroSortHelper$(x, p+1, I, d, s, Math.min(I-p-1, n), fc, fm, fs); // Sort right part - return x; +/** + * Estimate new values between existing ones. + * @param x an array + * @param fc combine function (a, b) + * @returns [x[0], fc(x[0], x[1]), x[1], fc(x[1], x[2]), ..., x[|x|-1]] + */ +export function interpolate(x: T[], fc: CombineFunction): T[] { + var a = [], u: T, i = -1; + for (var v of x) { + if (++i>0) a.push(fc(u, v)); + a.push(u = v); + } + return a; } /** - * Partially arrange values in order! + * Place values of an array between another. + * @param x an array + * @param y another array + * @param m number of values from x [1] + * @param n number of values from y [1] + * @param s step size for x [m] + * @param t step size for y [n] + * @returns x[0..m], y[0..n], x[s..s+m], y[t..t+n], ..., x[k*s..|x|-1] | k ∈ W + */ +export function intermix(x: T[], y: T[], m: number=1, n: number=1, s: number=m, t: number=n): T[] { + var X = x.length, Y = y.length, a = []; + for (var i=0, j=0; i0) { + for (var k=j, K=k+n; k(xs: T[][]): T[] { + var a = []; + for (var i=0;; ++i) { + var n = 0; + for (var x of xs) + if (i(...xs: T[][]): T[] { + return [].concat(...xs); +} + + +/** + * Append values from arrays! * @param x an array (updated!) - * @param i start index - * @param I end index (exclusive) - * @param n minimum number of values to sort + * @param ys arrays to append + * @returns x = [...x, ...y₀, ...y₁, ...] | [y₀, y₁, ...] = ys + */ +export function concat$(x: T[], ...ys: Iterable[]): T[] { + for (var y of ys) + x.push(...y); + return x; +} + + +/** + * Join values together into a string. + * @param x an array + * @param sep separator [,] + * @returns "$\{v₀\}$\{sep\}$\{v₁\}..." | vᵢ ∈ x + */ +export function join(x: T[], sep: string=","): string { + return x.join(sep); +} +// #endregion + + + + +// #region SET OPERATIONS +// ------------------------ + +/** + * Examine if there are no duplicate values. + * @param x an array * @param fc compare function (a, b) * @param fm map function (v, i, x) - * @param fs swap function (x, i, j) - * @returns x | x[i] ≤ x[j] ∀ i ≤ j + * @returns ∀ vᵢ, vⱼ ∈ x, is vᵢ ≠ vⱼ? */ -function partialQuickSort$(x: T[], i: number, I: number, n: number, fc: CompareFunction, fm: MapFunction, fs: SwapFunction): T[] { - if (n<=0 || I-i<=1) return x; // Nothing to sort - var p = i + Math.floor((I-i)*Math.random()); // Choose pivot - var p = quickSortPartition$(x, i, I, p, fc, fm, fs); // Partition array - partialQuickSort$(x, i, p, Math.min(p-i, n), fc, fm, fs); // Sort left part - partialQuickSort$(x, p+1, I, Math.min(I-p-1, n), fc, fm, fs); // Sort right part - return x; +export function isUnique(x: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): boolean { + if (fc) return isUniqueDual(x, fc, fm); + else return isUniqueMap (x, fm); } +function isUniqueMap(x: T[], fm: MapFunction | null=null): boolean { + var fm = fm || IDENTITY; + var s = new Set(), i = -1; + for (var v of x) { + var w = fm(v, ++i, x); + if (s.has(w)) return false; + s.add(w); + } + return true; +} -// TODO: Make this a generic function. -// Partition the array into two parts, such that values in the first part are less than values in the second part. -function quickSortPartition$(x: T[], i: number, I: number, p: number, fc: CompareFunction, fm: MapFunction, fs: SwapFunction): number { - var wp = fm(x[p], p, x); // Pivot value - var j = i-1; // Last index of values ≤ pivot - fs(x, p, I-1); // Move pivot to end - for (var k=i; k 0) continue; - fs(x, ++j, k); // Move value ≤ pivot to left +function isUniqueDual(x: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): boolean { + var fc = fc || COMPARE; + var fm = fm || IDENTITY; + var x1 = [...x].map(fm); + for (var wx of x1) { + for (var wy of x1) + if (fc(wx, wy)===0) return false; } - fs(x, ++j, I-1); // Move pivot to middle - return j; // Return pivot index + return true; } /** - * Partially arrange values in order! - * @param x an array (updated!) - * @param i start index - * @param I end index (exclusive) - * @param n minimum number of values to sort + * Examine if arrays have no value in common. + * @param x an array + * @param y another array * @param fc compare function (a, b) * @param fm map function (v, i, x) - * @param fs swap function (x, i, j) - * @returns x | x[i] ≤ x[j] ∀ i ≤ j + * @returns x ∩ y = Φ? */ -function partialHeapSort$(x: T[], i: number, I: number, n: number, fc: CompareFunction, fm: MapFunction, fs: SwapFunction): T[] { - buildReverseMinHeap$(x, i, I, fc, fm, fs); - for (var r=I-1; n>0 && i(x: T[], y: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): boolean { + if (fc) return isDisjointDual(x, y, fc, fm); + else return isDisjointMap (x, y, fm); } +function isDisjointMap(x: T[], y: T[], fm: MapFunction | null=null): boolean { + var fm = fm || IDENTITY; + var s = toSet(y, fm), i = -1; + for (var v of x) { + var w = fm(v, ++i, x); + if (s.has(w)) return false; + } + return true; +} -// Build a reverse min-heap, where root node is the smallest and placed at the end. -function buildReverseMinHeap$(x: T[], i: number, I: number, fc: CompareFunction, fm: MapFunction, fs: SwapFunction): void { - for (var r=I-Math.floor((I-i)/2); r(x: T[], y: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): boolean { + var fc = fc || COMPARE; + var fm = fm || IDENTITY; + var y1 = [...y].map(fm), i = -1; + for (var vx of x) { + var wx = fm(vx, ++i, x); + for (var wy of y1) + if (fc(wx, wy)===0) return false; + } + return true; } /** - * Reverse min-heapify values, such that root node is the smallest and placed at the end. - * @param x an array (updated!) - * @param i start index - * @param I end index (exclusive) - * @param r root index + * Remove duplicate values. + * @param x an array * @param fc compare function (a, b) * @param fm map function (v, i, x) - * @param fs swap function (x, i, j) + * @returns v₀, v₁, ... | vᵢ ∈ x; vᵢ ≠ vⱼ ∀ i, j */ -function reverseMinHeapify$(x: T[], i: number, I: number, r: number, fc: CompareFunction, fm: MapFunction, fs: SwapFunction): void { - var s = r; // Index of smallest value - var lt = 2*r - I; // Left child, reverse of lt = 2*r+1 - var rt = lt - 1; // Right child, reverse of rt = 2*r+2 - if (lt>=i && fc(fm(x[lt], lt, x), fm(x[s], s, x)) < 0) s = lt; // Left child is smaller? - if (rt>=i && fc(fm(x[rt], rt, x), fm(x[s], s, x)) < 0) s = rt; // Right child is smaller? - if (s !== r) { // Smallest is not root? - fs(x, s, r); // Swap root with smallest - reverseMinHeapify$(x, i, I, s, fc, fm, fs); // Rebuild heap +export function unique(x: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): T[] { + if (fc) return uniqueDual(x, fc, fm); + else return uniqueMap (x, fm); +} + +function uniqueMap(x: T[], fm: MapFunction | null=null): T[] { + var fm = fm || IDENTITY; + var s = new Set(); + var i = -1, a = []; + for (var v of x) { + var w = fm(v, ++i, x); + if (s.has(w)) continue; + s.add(w); a.push(v); + } + return a; +} + +function uniqueDual(x: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): T[] { + var fc = fc || COMPARE; + var fm = fm || IDENTITY; + var i = -1, s = [], a = []; + x: for (var vx of x) { + var wx = fm(vx, ++i, x); + for (var ws of s) + if (fc(ws, wx)===0) continue x; + s.push(wx); a.push(vx); } + return a; } -// Build a max-heap, where root node is the smallest and placed at the beginning. -function buildMaxHeap$(x: T[], i: number, I: number, fc: CompareFunction, fm: MapFunction, fs: SwapFunction): void { - for (var r=i+Math.floor((I-i)/2)-1; r>=i; --r) - maxHeapify$(x, i, I, r, fc, fm, fs); +/** + * Obtain values present in any array. + * @param x an array + * @param y another array + * @param fc compare function (a, b) + * @param fm map function (v, i, x) + * @returns x ∪ y = \{v | v ∈ x or v ∈ y\} + */ +export function union(x: T[], y: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): T[] { + return union$(x.slice(), y, fc, fm); } /** - * Max-heapify values, such that root node is the largest and placed at the beginning. + * Obtain values present in any array! * @param x an array (updated!) - * @param i start index - * @param I end index (exclusive) - * @param r root index + * @param y another array * @param fc compare function (a, b) * @param fm map function (v, i, x) - * @param fs swap function (x, i, j) + * @returns x = x ∪ y = \{v | v ∈ x or v ∈ y\} */ -function maxHeapify$(x: T[], i: number, I: number, r: number, fc: CompareFunction, fm: MapFunction, fs: SwapFunction): void { - var s = r; // Index of largest value - var lt = 2*r - i + 1; // Left child, like lt = 2*r+1 - var rt = lt + 1; // Right child, like rt = 2*r+2 - if (lt 0) s = lt; // Left child is larger? - if (rt 0) s = rt; // Right child is larger? - if (s !== r) { // Largest is not root? - fs(x, s, r); // Swap root with largest - maxHeapify$(x, i, I, s, fc, fm, fs); // Rebuild heap +export function union$(x: T[], y: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): T[] { + if (fc) return unionDual$(x, y, fc, fm); + else return unionMap$ (x, y, fm); +} + +function unionMap$(x: T[], y: T[], fm: MapFunction | null=null): T[] { + var fm = fm || IDENTITY; + var s = toSet(x, fm), i = -1; + for (var vy of y) { + var wy = fm(vy, ++i, y); + if (!s.has(wy)) x.push(vy); + } + return x; +} + +function unionDual$(x: T[], y: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): T[] { + var fc = fc || COMPARE; + var fm = fm || IDENTITY; + var x1 = x.map(fm), i = -1; + y: for (var vy of y) { + var wy = fm(vy, ++i, y); + for (var wx of x1) + if (fc(wx, wy)===0) continue y; + x.push(vy); } + return x; } /** - * Partially arrange values in order! - * @param x an array (updated!) - * @param i start index - * @param I end index (exclusive) - * @param n minimum number of values to sort (ignored) + * Obtain values present in both arrays. + * @param x an array + * @param y another array * @param fc compare function (a, b) * @param fm map function (v, i, x) - * @param fs swap function (x, i, j) - * @returns x | x[i] ≤ x[j] ∀ i ≤ j + * @returns x ∩ y = \{v | v ∈ x, v ∈ y\} */ -function partialInsertionSort$(x: T[], i: number, I: number, n: number, fc: CompareFunction, fm: MapFunction, fs: SwapFunction): T[] { - // NOTE: Insertion sort does not support partial sorting, so we ignore n. - if (fs===swapRaw$) return partialInsertionSortSwapless$(x, i, I, n, fc, fm, fs); - else return partialInsertionSortSwap$ (x, i, I, n, fc, fm, fs); +export function intersection(x: T[], y: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): T[] { + if (fc) return intersectionDual(x, y, fc, fm); + else return intersectionMap (x, y, fm); } +function intersectionMap(x: T[], y: T[], fm: MapFunction | null=null): T[] { + var fm = fm || IDENTITY; + var s = toSet(y, fm); + var i = -1, a = []; + for (var vx of x) { + var wx = fm(vx, ++i, x); + if (s.has(wx)) a.push(vx); + } + return a; +} -// Sort values in order with swap-enabled version of insertion sort. -function partialInsertionSortSwap$(x: T[], i: number, I: number, n: number, fc: CompareFunction, fm: MapFunction, fs: SwapFunction): T[] { - for (var j=i+1; j=i && fc(fm(x[k], k, x), wkey)>0; --k) - fs(x, k, k+1); +function intersectionDual(x: T[], y: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): T[] { + var fc = fc || COMPARE; + var fm = fm || IDENTITY; + var y1 = [...y].map(fm); + var i = -1, a = []; + x: for (var vx of x) { + var wx = fm(vx, ++i, x); + for (var wy of y1) + if (fc(wx, wy)===0) { a.push(vx); continue x; } } - return x; + return a; } -// Sort values in order with swapless version of insertion sort. -function partialInsertionSortSwapless$(x: T[], i: number, I: number, n: number, fc: CompareFunction, fm: MapFunction, fs: SwapFunction): T[] { - for (var j=i+1; j=i && fc(fm(x[k], k, x), wkey)>0; --k) - x[k+1] = x[k]; - x[k+1] = key; +/** + * Obtain values not present in another array. + * @param x an array + * @param y another array + * @param fc compare function (a, b) + * @param fm map function (v, i, x) + * @returns x - y = \{v | v ∈ x, v ∉ y\} + */ +export function difference(x: T[], y: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): T[] { + if (fc) return differenceDual(x, y, fc, fm); + else return differenceMap (x, y, fm); +} + +function differenceMap(x: T[], y: T[], fm: MapFunction | null=null): T[] { + var fm = fm || IDENTITY; + var s = toSet(y, fm); + var i = -1, a = []; + for (var vx of x) { + var wx = fm(vx, ++i, x); + if (!s.has(wx)) a.push(vx); } - return x; + return a; +} + +function differenceDual(x: T[], y: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): T[] { + var fc = fc || COMPARE; + var fm = fm || IDENTITY; + var y1 = [...y].map(fm); + var i = -1, a = []; + x: for (var vx of x) { + var wx = fm(vx, ++i, x); + for (var wy of y1) + if (fc(wx, wy)===0) continue x; + a.push(vx); + } + return a; } /** - * Partially arrange values in order! - * @param x an array (updated!) - * @param i start index - * @param I end index (exclusive) - * @param n minimum number of values to sort + * Obtain values not present in both arrays. + * @param x an array + * @param y another array * @param fc compare function (a, b) * @param fm map function (v, i, x) - * @param fs swap function (x, i, j) - * @returns x | x[i] ≤ x[j] ∀ i ≤ j + * @returns x-y ∪ y-x */ -function partialSelectionSort$(x: T[], i: number, I: number, n: number, fc: CompareFunction, fm: MapFunction, fs: SwapFunction): T[] { - for (var j=i; n>0 && j 0) { l = k; wl = wk; } +export function symmetricDifference(x: T[], y: T[], fc: CompareFunction | null=null, fm: MapFunction | null=null): T[] { + var x0 = fromIterable$(x); + var y0 = fromIterable$(y); + var ax = difference(x0, y0, fc, fm); + var ay = difference(y0, x0, fc, fm); + return concat$(ax, ay); +} + + +/** + * Obtain cartesian product of arrays. + * @param xs arrays + * @param fm map function (vs, i) + * @returns x₀ × x₁ × ... = \{[v₀, v₁, ...] | v₀ ∈ x₀, v₁ ∈ x₁, ...] \} + */ +export function cartesianProduct(xs: T[][], fm: MapFunction | null=null): (T[]|U)[] { + var fm = fm || IDENTITY; + var XS = xs.length, a = []; + if (XS===0) return a; + var is = new Array(XS).fill(0); + var ls = xs.map(x => x.length); + if (ls.some(l => l===0)) return a; + for (var i=0;; ++i) { + for (var j=0, vs=[]; j=0; --r) { + if (++is[r]