src/index.ts
import {mod} from "extra-math";
import {
IDENTITY,
COMPARE,
} from "extra-function";
// #region TYPES
// =============
/** Entries is an array of index-value pairs, with unique indices. */
export type Entries<T> = [number, T][];
/** IEntries is a list of index-value pairs, with unique indices. */
export type IEntries<T> = Iterable<[number, T]>;
/** Lists is a pair of index array and value array, with unique indices. */
export type Lists<T> = [number[], T[]];
/** ILists is a pair of index iterable list and value iterable list, with unique indices. */
export type ILists<T> = [Iterable<number>, Iterable<T>];
/**
* Handle reading of a single value.
* @returns value
*/
export type ReadFunction<T> = () => T;
/**
* Handle combining of two values.
* @param a a value
* @param b another value
* @returns combined value
*/
export type CombineFunction<T> = (a: T, b: T) => T;
/**
* Handle comparison of two values.
* @param a a value
* @param b another value
* @returns a<b: -ve, a=b: 0, a>b: +ve
*/
export type CompareFunction<T> = (a: T, b: T) => number;
/**
* Handle processing of values in an array.
* @param v value in array
* @param i index of value in array
* @param x array containing the value
*/
export type ProcessFunction<T> = (v: T, i: number, x: T[]) => void;
/**
* Handle selection of values in an array.
* @param v value in array
* @param i index of value in array
* @param x array containing the value
* @returns selected?
*/
export type TestFunction<T> = (v: T, i: number, x: T[]) => boolean;
/**
* Handle transformation of a value to another.
* @param v value in array
* @param i index of value in array
* @param x array containing the value
* @returns transformed value
*/
export type MapFunction<T, U> = (v: T, i: number, x: T[]) => U;
/**
* Handle reduction of multiple values into a single value.
* @param acc accumulator (temporary result)
* @param v value in array
* @param i index of value in array
* @param x array containing the value
* @returns reduced value
*/
export type ReduceFunction<T, U> = (acc: U, v: T, i: number, x: T[]) => U;
/**
* Handle ending of a combined array.
* @param dones iᵗʰ array done?
* @returns combined array done?
*/
export type EndFunction = (dones: boolean[]) => boolean;
/**
* Handle swapping of two values in an array.
* @param x an array (updated!)
* @param i an index
* @param j another index
* @returns x | x[i] ⇔ x[j]
*/
export type SwapFunction<T> = (x: T[], i: number, j: number) => T[];
// #endregion
// #region METHODS
// ===============
// #region HELPERS
// ---------------
/** Convert an iterable to set. */
function toSet<T, U=T>(x: T[], fm: MapFunction<T, U> | null=null): Set<T|U> {
if (!fm) return new Set(x);
var a = new Set<U>(), i = -1;
for (var v of x)
a.add(fm(v, ++i, x));
return a;
}
// #endregion
// #region GENERATE
// ----------------
/**
* Generate array from given number range.
* @param v start number
* @param V end number, excluding
* @param dv step size [1]
* @returns [v, v+dv, v+2dv, ...]
*/
export function fromRange(v: number, V: number, dv: number=1): number[] {
var n = (V - v)/dv, a = [];
for (var i=0; i<n; ++i, v+=dv)
a.push(v);
return a;
}
/**
* Generate array from repeated function invocation.
* @param fn function
* @param n number of values
* @returns [fn(), fn(), ...]
*/
export function fromInvocation<T>(fn: Function, n: number): T[] {
var a = [];
for (var i=0; i<n; ++i)
a.push(fn());
return a;
}
export {fromInvocation as fromCall};
/**
* Generate array from repeated function application.
* @param fm map function (v, i)
* @param v start value
* @param n number of values
* @returns [v, fm(v), fm(fm(v)), ...]
*/
export function fromApplication<T>(fm: MapFunction<T, T>, v: T, n: number): T[] {
var a = [];
if (n!==0) a.push(v);
for (var i=1; i!==n; ++i)
a.push(v = fm(v, i, null));
return a;
}
export {fromApplication as fromApply};
/**
* Convert an iterable to array.
* @param x an iterable
* @returns x as array
*/
export function fromIterable<T>(x: Iterable<T>): 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$<T>(x: Iterable<T>): T[] {
return Array.isArray(x)? x : [...x];
}
export {fromIterable$ as from$};
// #endregion
// #region CLONE
// -------------
/**
* Shallow clone an array.
* @param x an array
* @returns shallow clone of x
*/
export function shallowClone<T>(x: T[]): T[] {
return x.slice();
}
export {shallowClone as clone};
/**
* Deep clone an array.
* @param x an array
* @returns deep clone of x
*/
export function deepClone<T>(x: T[]): T[] {
return structuredClone(x);
}
// #endregion
// #region ABOUT
// -------------
/**
* Check if value is an array.
* @param v a value
* @returns v is an array?
*/
export function is(v: any): v is any[] {
return Array.isArray(v);
}
/**
* Obtain all indices.
* @param x an array
* @returns [0, 1, ..., |x|-1]
*/
export function keys<T>(x: T[]): number[] {
return [...x.keys()];
}
/**
* List all indices.
* @param x an array
* @returns 0, 1, ..., |x|-1
*/
export function ikeys<T>(x: T[]): IterableIterator<number> {
return x.keys();
}
/**
* Get all values.
* @param x an array
* @returns [v₀, v₁, ...] | vᵢ = x[i]
*/
export function values<T>(x: T[]): T[] {
return x.slice();
}
/**
* List all values.
* @param x an array
* @returns v₀, v₁, ... | vᵢ = x[i]
*/
export function ivalues<T>(x: T[]): IterableIterator<T> {
return x.values();
}
/**
* Obtain all index-value pairs.
* @param x an array
* @returns [[0, v₀], [1, v₁], ...] | vᵢ = x[i]
*/
export function entries<T>(x: T[]): Entries<T> {
return [...x.entries()];
}
/**
* List all index-value pairs.
* @param x an array
* @returns [0, v₀], [1, v₁], ... | vᵢ = x[i]
*/
export function ientries<T>(x: T[]): IEntries<T> {
return x.entries();
}
// #endregion
// #region INDEX
// -------------
/**
* Get zero-based index for an element in array.
* @param x an array
* @param i ±index
* @returns i' | x[i'] = x[i]; i' ∈ [0, |x|]
*/
export function index<T>(x: T[], i: number): number {
var X = x.length;
return i>=0? Math.min(i, X) : Math.max(X+i, 0);
}
/**
* Get zero-based index range for part of array.
* @param x an array
* @param i begin ±index [0]
* @param I end ±index (exclusive) [|x|]
* @returns [i', I'] | i' ≤ I'; i', I' ∈ [0, |x|]
*/
export function indexRange<T>(x: T[], i: number=0, I: number=x.length): [number, number] {
var X = x.length;
var i = i>=0? Math.min(i, X) : Math.max(X+i, 0);
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<T>(x: T[]): boolean {
return x.length===0;
}
/**
* Find the length of an array.
* @param x an array
* @param i begin ±index [0]
* @param I end ±index (exclusive) [X]
* @returns |x[i..I]|
*/
export function length<T>(x: T[], i: number=0, I: number=x.length): number {
var [i, I] = indexRange(x, i, I);
return I-i;
}
export {length as size};
/**
* Resize an array to given length!
* @param x an array
* @param n new length
* @param vd default value
* @returns resized x
*/
export function resize$<T>(x: T[], n: number, vd: T) {
var X = x.length; x.length = n;
if (n>X) x.fill(vd, X);
return x;
}
/**
* Remove all elements from an array!
* @param x an array (updated!)
* @returns cleared x
*/
export function clear$<T>(x: T[]) {
x.length = 0;
return x;
}
// #endregion
// #region GET/SET
// ---------------
/**
* Get value at index.
* @param x an array
* @param i index
* @returns x[i]
*/
export function get<T>(x: T[], i: number): T {
return x[index(x, i)];
}
export {get as at};
// Get values at index range.
// export {slice as getRange};
/**
* Get values at indices.
* @param x an array
* @param is indices
* @returns [x[i₀], x[i₁], ...] | [i₀, i₁, ...] = is
*/
export function getAll<T>(x: T[], is: number[]): T[] {
return is.map(i => get(x, i));
}
/**
* Get value at path in a nested array.
* @param x a nested array
* @param p path
* @returns x[i₀][i₁][...] | [i₀, i₁, ...] = p
*/
export function getPath(x: any[], p: number[]): any {
for (var i of p)
x = is(x)? get(x, i) : undefined;
return x;
}
/**
* Check if nested array has a path.
* @param x a nested array
* @param p path
* @returns x[i₀][i₁][...] exists? | [i₀, i₁, ...] = p
*/
export function hasPath(x: any[], p: number[]): boolean {
for (var i of p) {
if (!is(x)) return false;
x = get(x, i);
}
return true;
}
/**
* Set value at index.
* @param x an array
* @param i index
* @param v value
* @returns x' | x' = x; x'[i] = v
*/
export function set<T>(x: T[], i: number, v: T): T[] {
return set$(x.slice(), i, v);
}
export {set as with};
/**
* Set value at index!
* @param x an array (updated!)
* @param i index
* @param v value
* @returns x | x[i] = v
*/
export function set$<T>(x: T[], i: number, v: T): T[] {
x[index(x, i)] = v;
return x;
}
// TODO: setRange$()
// TODO: setAll$()
/**
* Set value at path in a nested array!
* @param x a nested array (updated!)
* @param p path
* @param v value
* @returns x | x[i₀][i₁][...] = v; [i₀, i₁, ...] = p
*/
export function setPath$(x: any[], p: number[], v: any): any[] {
var y = getPath(x, p.slice(0, -1));
if (is(y)) set$(y, last(p), v);
return x;
}
/**
* Exchange two values.
* @param x an array
* @param i an index
* @param j another index
* @returns x' | x' = x; x'[i] = x[j]; x'[j] = x[i]
*/
export function swap<T>(x: T[], i: number, j: number): T[] {
return swap$(x.slice(), i, j);
}
/**
* Exchange two values!
* @param x an array (updated!)
* @param i an index
* @param j another index
* @returns x | x[i] ⇔ x[j]
*/
export function swap$<T>(x: T[], i: number, j: number): T[] {
var i = index(x, i), j = index(x, j);
var t = x[i]; x[i] = x[j]; x[j] = t;
return x;
}
/**
* Exchange two values!
* @param x an array (updated!)
* @param i an +ve index
* @param j another +ve index
* @returns x | x[i] ⇔ x[j]
*/
function swapRaw$<T>(x: T[], i: number, j: number): T[] {
var t = x[i]; x[i] = x[j]; x[j] = t;
return x;
}
// NOTE: May also be called swapUnchecked$().
/**
* Exchange two ranges of values.
* @param x an array
* @param i begin index of first range
* @param I end index of first range (exclusive)
* @param j begin index of second range
* @param J end index of second range (exclusive)
* @returns x' | x' = x; x'[i..I] = x[j..J]; x'[j..J] = x[i..I]
*/
export function swapRanges<T>(x: T[], i: number, I: number, j: number, J: number): T[] {
var [i, I] = indexRange(x, i, I);
var [j, J] = indexRange(x, j, J);
if (j<i) [i, I, j, J] = [j, J, i, I];
if (j<I) return x.slice(); // Skip if ranges overlap!
return x.slice(0, i).concat(x.slice(j, J), x.slice(i, j), x.slice(I));
}
/**
* Exchange two ranges of values!
* @param x an array (updated!)
* @param i begin index of first range
* @param I end index of first range (exclusive)
* @param j begin index of second range
* @param J end index of second range (exclusive)
* @returns x | x[i..I] ⇔ x[j..J]
*/
export function swapRanges$<T>(x: T[], i: number, I: number, j: number, J: number): T[] {
var [i, I] = indexRange(x, i, I);
var [j, J] = indexRange(x, j, J);
if (j<i) [i, I, j, J] = [j, J, i, I];
if (j<I) return x; // Skip if ranges overlap!
var t = x.slice(i, I);
x.splice(i, I-i, ...x.slice(j, J));
x.splice(j, J-j, ...t);
return x;
}
// TODO: swapAll$()
/**
* Remove value at index.
* @param x an array
* @param i index
* @returns x[0..i] ⧺ x[i+1..]
*/
export function remove<T>(x: T[], i: number): T[] {
var i = index(x, i);
return x.slice(0, i).concat(x.slice(i+1));
}
/**
* Remove value at index!
* @param x an array (updated!)
* @param i index
* @returns x \\: [i]
*/
export function remove$<T>(x: T[], i: number): T[] {
x.splice(i, 1);
return x;
}
/**
* Remove value at path in a nested array!
* @param x a nested array (updated!)
* @param p path
* @returns x \\: [i₀][i₁][...] | [i₀, i₁, ...] = p
*/
export function removePath$(x: any[], p: number[]): any[] {
var y = getPath(x, p.slice(0, -1));
if (is(y)) y.splice(last(p), 1);
return x;
}
// #endregion
// #region SORT
// ------------
/**
* 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<T, U=T>(x: T[], fc: CompareFunction<T|U> | null=null, fm: MapFunction<T, T|U> | null=null): boolean {
return searchUnsortedValue(x, fc, fm) === -1;
}
/**
* 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 x is not sorted?
*/
export function hasUnsortedValue<T, U=T>(x: T[], fc: CompareFunction<T|U> | null=null, fm: MapFunction<T, T|U> | null=null): boolean {
return searchUnsortedValue(x, fc, fm) >= 0;
}
/**
* 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 index of first unsorted value, -1 if sorted
*/
export function searchUnsortedValue<T, U=T>(x: T[], fc: CompareFunction<T|U> | null=null, fm: MapFunction<T, T|U> | 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; ++i) {
var w = fm(x[i], i, x);
if (fc(w0, w)>0) return i;
w0 = w;
}
return -1;
}
/**
* Arrange values in order.
* @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
*/
export function sort<T, U=T>(x: T[], fc: CompareFunction<T|U> | null=null, fm: MapFunction<T, T|U> | null=null, fs: SwapFunction<T> | null=null): T[] {
return sort$(x.slice(), fc, fm, fs);
}
export {sort as toSorted};
/**
* Arrange values in order!
* @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
*/
export function sort$<T, U=T>(x: T[], fc: CompareFunction<T|U> | null=null, fm: MapFunction<T, T|U> | null=null, fs: SwapFunction<T> | null=null): T[] {
var fc = fc || COMPARE;
if (!fm && !fs) return x.sort(fc);
var X = x.length;
var fm = fm || IDENTITY;
var fs = fs || swapRaw$;
return rangedPartialIntroSort$(x, 0, X, X, fc, fm, fs);
}
/**
* Arrange a range of values in order.
* @param x an array
* @param i begin index
* @param I end index (exclusive)
* @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
*/
export function rangedSort<T, U=T>(x: T[], i: number, I: number, fc: CompareFunction<T|U> | null=null, fm: MapFunction<T, T|U> | null=null, fs: SwapFunction<T> | null=null): T[] {
return rangedSort$(x.slice(), i, I, fc, fm, fs);
}
/**
* Arrange a range of values in order!
* @param x an array (updated!)
* @param i begin index
* @param I end index (exclusive)
* @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 rangedSort$<T, U=T>(x: T[], i: number, I: number, fc: CompareFunction<T|U> | null=null, fm: MapFunction<T, T|U> | null=null, fs: SwapFunction<T> | null=null): T[] {
var fc = fc || COMPARE;
var fm = fm || IDENTITY;
var fs = fs || swapRaw$;
var [i, I] = indexRange(x, i, I);
return rangedPartialIntroSort$(x, i, I, I-i, fc, fm, fs);
}
/**
* Partially arrange values in order.
* @param x an array
* @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
*/
export function partialSort<T, U=T>(x: T[], n: number, fc: CompareFunction<T|U> | null=null, fm: MapFunction<T, T|U> | null=null, fs: SwapFunction<T> | null=null): T[] {
return partialSort$(x.slice(), n, fc, fm, fs);
}
/**
* Partially arrange values in order!
* @param x an array (updated!)
* @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 partialSort$<T, U=T>(x: T[], n: number, fc: CompareFunction<T|U> | null=null, fm: MapFunction<T, T|U> | null=null, fs: SwapFunction<T> | null=null): T[] {
return rangedPartialSort$(x, 0, x.length, n, fc, fm, fs);
}
/**
* Partially arrange a range of values in order.
* @param x an array
* @param i begin 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
*/
export function rangedPartialSort<T, U=T>(x: T[], i: number, I: number, n: number, fc: CompareFunction<T|U> | null=null, fm: MapFunction<T, T|U> | null=null, fs: SwapFunction<T> | null=null): T[] {
return rangedPartialSort$(x.slice(), i, I, n, fc, fm, fs);
}
/**
* Partially arrange a range of values in order!
* @param x an array (updated!)
* @param i begin 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 rangedPartialSort$<T, U=T>(x: T[], i: number, I: number, n: number, fc: CompareFunction<T|U> | null=null, fm: MapFunction<T, T|U> | null=null, fs: SwapFunction<T> | null=null): T[] {
var fc = fc || COMPARE;
var fm = fm || IDENTITY;
var fs = fs || swapRaw$;
var [i, I] = indexRange(x, i, I);
return rangedPartialIntroSort$(x, i, I, n, fc, fm, fs);
}
/**
* Partially arrange values in order!
* @param x an array (updated!)
* @param i begin 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
*/
function rangedPartialIntroSort$<T, U=T>(x: T[], i: number, I: number, n: number, fc: CompareFunction<T|U>, fm: MapFunction<T, T|U>, fs: SwapFunction<T>): T[] {
var d = Math.floor(Math.log2(I-i)*2); // Maximum depth of recursion
var s = 16; // When to switch to insertion sort
return rangedPartialIntroSortDo$(x, i, I, d, s, n, fc, fm, fs);
}
// Partially arrange a range of values in order with hybrid quick sort, heap sort, and insertion sort.
function rangedPartialIntroSortDo$<T, U=T>(x: T[], i: number, I: number, d: number, s: number, n: number, fc: CompareFunction<T|U>, fm: MapFunction<T, T|U>, fs: SwapFunction<T>): T[] {
if (n<=0 || I-i<=1) return x; // Nothing to sort
if (I-i<=s) return rangedPartialInsertionSort$(x, i, I, n, fc, fm, fs); // Insertion sort
if (d<=0) return rangedPartialHeapSort$(x, i, I, n, fc, fm, fs); // Heap sort
var p = i + Math.floor((I-i)*Math.random()); // Choose pivot
var p = rangedQuickSortPartition$(x, i, I, p, fc, fm, fs); // Partition array
rangedPartialIntroSortDo$(x, i, p, d, s, Math.min(p-i, n), fc, fm, fs); // Sort left part
rangedPartialIntroSortDo$(x, p+1, I, d, s, Math.min(I-p-1, n), fc, fm, fs); // Sort right part
return x;
}
/**
* Partially arrange a range of values in order!
* @param x an array (updated!)
* @param i begin 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
*/
function rangedPartialQuickSort$<T, U=T>(x: T[], i: number, I: number, n: number, fc: CompareFunction<T|U>, fm: MapFunction<T, T|U>, fs: SwapFunction<T>): 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 = rangedQuickSortPartition$(x, i, I, p, fc, fm, fs); // Partition array
rangedPartialQuickSort$(x, i, p, Math.min(p-i, n), fc, fm, fs); // Sort left part
rangedPartialQuickSort$(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 rangedQuickSortPartition$<T, U=T>(x: T[], i: number, I: number, p: number, fc: CompareFunction<T|U>, fm: MapFunction<T, T|U>, fs: SwapFunction<T>): 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<I-1; ++k) {
var wk = fm(x[k], k, x);
if (fc(wk, wp) > 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
}
/**
* Partially arrange a range of values in order!
* @param x an array (updated!)
* @param i begin 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
*/
function rangedPartialHeapSort$<T, U=T>(x: T[], i: number, I: number, n: number, fc: CompareFunction<T|U>, fm: MapFunction<T, T|U>, fs: SwapFunction<T>): T[] {
rangedBuildReverseMinHeap$(x, i, I, fc, fm, fs);
for (var r=I-1; n>0 && i<I; ++i, --n) {
fs(x, i, r); // Move root to the beginning
rangedReverseMinHeapify$(x, i+1, I, r, fc, fm, fs); // Rebuild heap
}
return x;
}
// Build a reverse min-heap from a range of values, where root node is the smallest and placed at the end.
function rangedBuildReverseMinHeap$<T, U=T>(x: T[], i: number, I: number, fc: CompareFunction<T|U>, fm: MapFunction<T, T|U>, fs: SwapFunction<T>): void {
for (var r=I-Math.floor((I-i)/2); r<I; ++r) // Reverse of r = X/2-1 .. 0
rangedReverseMinHeapify$(x, i, I, r, fc, fm, fs);
}
/**
* Reverse min-heapify a range of values, such that root node is the smallest and placed at the end.
* @param x an array (updated!)
* @param i begin index
* @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)
*/
function rangedReverseMinHeapify$<T, U=T>(x: T[], i: number, I: number, r: number, fc: CompareFunction<T|U>, fm: MapFunction<T, T|U>, fs: SwapFunction<T>): 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
rangedReverseMinHeapify$(x, i, I, s, fc, fm, fs); // Rebuild heap
}
}
// Build a max-heap from a range of values, where root node is the smallest and placed at the beginning.
function rangedBuildMaxHeap$<T, U=T>(x: T[], i: number, I: number, fc: CompareFunction<T|U>, fm: MapFunction<T, T|U>, fs: SwapFunction<T>): void {
for (var r=i+Math.floor((I-i)/2)-1; r>=i; --r)
rangedMaxHeapify$(x, i, I, r, fc, fm, fs);
}
/**
* Max-heapify a range of values, such that root node is the largest and placed at the beginning.
* @param x an array (updated!)
* @param i begin index
* @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)
*/
function rangedMaxHeapify$<T, U=T>(x: T[], i: number, I: number, r: number, fc: CompareFunction<T|U>, fm: MapFunction<T, T|U>, fs: SwapFunction<T>): 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<I && fc(fm(x[lt], lt, x), fm(x[s], s, x)) > 0) s = lt; // Left child is larger?
if (rt<I && fc(fm(x[rt], rt, x), fm(x[s], s, x)) > 0) s = rt; // Right child is larger?
if (s !== r) { // Largest is not root?
fs(x, s, r); // Swap root with largest
rangedMaxHeapify$(x, i, I, s, fc, fm, fs); // Rebuild heap
}
}
/**
* Partially arrange a range of values in order!
* @param x an array (updated!)
* @param i begin 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
*/
function rangedPartialInsertionSort$<T, U=T>(x: T[], i: number, I: number, n: number, fc: CompareFunction<T|U>, fm: MapFunction<T, T|U>, fs: SwapFunction<T>): T[] {
// NOTE: Insertion sort does not support partial sorting, so we ignore n.
if (fs===swapRaw$) return rangedPartialInsertionSortSwapless$(x, i, I, n, fc, fm, fs);
else return rangedPartialInsertionSortSwap$ (x, i, I, n, fc, fm, fs);
}
// Sort a range of values in order with swap-enabled version of insertion sort.
function rangedPartialInsertionSortSwap$<T, U=T>(x: T[], i: number, I: number, n: number, fc: CompareFunction<T|U>, fm: MapFunction<T, T|U>, fs: SwapFunction<T>): T[] {
for (var j=i+1; j<I; ++j) {
var key = x[j];
var wkey = fm(key, j, x);
for (var k=j-1; k>=i && fc(fm(x[k], k, x), wkey)>0; --k)
fs(x, k, k+1);
}
return x;
}
// Sort a range of values in order with swapless version of insertion sort.
function rangedPartialInsertionSortSwapless$<T, U=T>(x: T[], i: number, I: number, n: number, fc: CompareFunction<T|U>, fm: MapFunction<T, T|U>, fs: SwapFunction<T>): T[] {
for (var j=i+1; j<I; ++j) {
var key = x[j];
var wkey = fm(key, j, x);
for (var k=j-1; k>=i && fc(fm(x[k], k, x), wkey)>0; --k)
x[k+1] = x[k];
x[k+1] = key;
}
return x;
}
/**
* Partially arrange a range of values in order!
* @param x an array (updated!)
* @param i begin 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
*/
function rangedPartialSelectionSort$<T, U=T>(x: T[], i: number, I: number, n: number, fc: CompareFunction<T|U>, fm: MapFunction<T, T|U>, fs: SwapFunction<T>): T[] {
for (var j=i; n>0 && j<I; ++j, --n) {
var l = j;
var wl = fm(x[l], l, x);
for (var k=j+1; k<I; ++k) {
var wk = fm(x[k], k, x);
if (fc(wl, wk) > 0) { l = k; wl = wk; }
}
fs(x, j, l);
}
return x;
}
// #endregion
// #region MINIMUM/MAXIMUM
// -----------------------
/**
* Find first smallest value.
* @param x an array
* @param fc compare function (a, b)
* @param fm map function (v, i, x)
* @returns v | v ≤ vᵢ; vᵢ ∈ x
*/
export function minimum<T, U=T>(x: T[], fc: CompareFunction<T|U> | null=null, fm: MapFunction<T, T|U> | null=null): T {
var i = searchMinimumValue(x, fc, fm);
return x[i];
}
export {minimum as min};
/**
* Find first smallest entry.
* @param x an array
* @param fc compare function (a, b)
* @param fm map function (v, i, x)
* @returns [min_index, min_value]
*/
export function minimumEntry<T, U=T>(x: T[], fc: CompareFunction<T|U> | null=null, fm: MapFunction<T, T|U> | null=null): [number, T] {
var i = searchMinimumValue(x, fc, fm);
return [i, x[i]];
}
export {minimumEntry as minEntry};
/**
* Find first largest value.
* @param x an array
* @param fc compare function (a, b)
* @param fm map function (v, i, x)
* @returns v | v ≥ vᵢ; vᵢ ∈ x
*/
export function maximum<T, U=T>(x: T[], fc: CompareFunction<T|U> | null=null, fm: MapFunction<T, T|U> | null=null): T {
var i = searchMaximumValue(x, fc, fm);
return x[i];
}
export {maximum as max};
/**
* Find first largest entry.
* @param x an array
* @param fc compare function (a, b)
* @param fm map function (v, i, x)
* @returns [max_index, max_value]
*/
export function maximumEntry<T, U=T>(x: T[], fc: CompareFunction<T|U> | null=null, fm: MapFunction<T, T|U> | null=null): [number, T] {
var i = searchMaximumValue(x, fc, fm);
return [i, x[i]];
}
export {maximumEntry as maxEntry};
/**
* Find smallest and largest values.
* @param x an array
* @param fc compare function (a, b)
* @param fm map function (v, i, x)
* @returns [min_value, max_value]
*/
export function range<T, U=T>(x: T[], fc: CompareFunction<T|U> | null=null, fm: MapFunction<T, T|U> | null=null): [T, T] {
var [a, b] = rangeEntries(x, fc, fm);
return [a[1], b[1]];
}
/**
* Find smallest and largest entries.
* @param x an array
* @param fc compare function (a, b)
* @param fm map function (v, i, x)
* @returns [min_entry, max_entry]
*/
export function rangeEntries<T, U=T>(x: T[], fc: CompareFunction<T|U> | null=null, fm: MapFunction<T, T|U> | 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; i<X; ++i) {
var v = x[i], w = fm(v, i, x);
if (fc(w, mw)<0) { mi = i; mv = v; mw = w; }
if (fc(w, nw)>0) { ni = i; nv = v; nw = w; }
}
return [[mi, mv], [ni, nv]];
}
/**
* Find smallest values.
* @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 values in ascending order
*/
export function minimums<T, U=T>(x: T[], n: number, fc: CompareFunction<T|U> | null=null, fm: MapFunction<T, T|U> | null=null): T[] {
var is = searchMinimumValues(x, n, fc, fm);
return is.map(i => x[i]);
}
/**
* 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<T, U=T>(x: T[], n: number, fc: CompareFunction<T|U> | null=null, fm: MapFunction<T, T|U> | 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
* @param fc compare function (a, b)
* @param fm map function (v, i, x)
* @returns n largest values in descending order
*/
export function maximums<T, U=T>(x: T[], n: number, fc: CompareFunction<T|U> | null=null, fm: MapFunction<T, T|U> | null=null): T[] {
var is = searchMaximumValues(x, n, fc, fm);
return is.map(i => x[i]);
}
/**
* Find largest 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 largest entries in descending order
*/
export function maximumEntries<T, U=T>(x: T[], n: number, fc: CompareFunction<T|U> | null=null, fm: MapFunction<T, T|U> | null=null): [number, T][] {
var is = searchMaximumValues(x, n, fc, fm);
return is.map(i => [i, x[i]]);
}
/**
* Find first index of minimum value.
* @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
*/
export function searchMinimumValue<T, U=T>(x: T[], fc: CompareFunction<T|U> | null=null, fm: MapFunction<T, T|U> | 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; ++i) {
var w = fm(x[i], i, x);
if (fc(w, mw)<0) { mi = i; mw = w; }
}
return mi;
}
/**
* Find first index of maximum value.
* @param x an array
* @param fc compare function (a, b)
* @param fm map function (v, i, x)
* @returns first index of maximum value, -1 if empty
*/
export function searchMaximumValue<T, U=T>(x: T[], fc: CompareFunction<T|U> | null=null, fm: MapFunction<T, T|U> | 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; i<X; ++i) {
var w = fm(x[i], i, x);
if (fc(w, nw)>0) { ni = i; nw = w; }
}
return ni;
}
/**
* Find indices of minimum values.
* @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 minimum values in ascending order
*/
export function searchMinimumValues<T, U=T>(x: T[], n: number, fc: CompareFunction<T|U> | null=null, fm: MapFunction<T, T|U> | 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);
rangedBuildMaxHeap$(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<X; ++i) {
var w = fm(x[i], i, x);
if (fc(w, wr) >= 0) continue;
ih[0] = i;
rangedMaxHeapify$(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;
}
/**
* Find indices of maximum values.
* @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
*/
export function searchMaximumValues<T, U=T>(x: T[], n: number, fc: CompareFunction<T|U> | null=null, fm: MapFunction<T, T|U> | 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
// ---------------
/**
* Examine if two arrays are equal.
* @param x an array
* @param y another array
* @param fc compare function (a, b)
* @param fm map function (v, i, x)
* @returns x = y?
*/
export function isEqual<T, U=T>(x: T[], y: T[], fc: CompareFunction<T|U> | null=null, fm: MapFunction<T, T|U> | null=null): boolean {
var X = x.length, Y = y.length;
return X===Y && compare(x, y, fc, fm)===0;
}
/**
* Compare two arrays (lexicographically).
* @param x an array
* @param y another array
* @param fc compare function (a, b)
* @param fm map function (v, i, x)
* @returns x<y: -ve, x=y: 0, x>y: +ve
*/
export function compare<T, U=T>(x: T[], y: T[], fc: CompareFunction<T|U> | null=null, fm: MapFunction<T, T|U> | 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<I; ++i) {
var wx = fm(x[i], i, x);
var wy = fm(y[i], i, y);
var c = fc(wx, wy);
if (c!==0) return c;
}
return Math.sign(X-Y);
}
// #endregion
// #region PART
// ------------
/**
* Get first value.
* @param x an array
* @param vd default value
* @returns x[0] || vd
*/
export function head<T>(x: T[], vd?: T): T {
return x.length>0? x[0] : vd;
}
export {head as front};
export {head as first};
/**
* Get values except first.
* @param x an array
* @returns x[1..|x|]
*/
export function tail<T>(x: T[]): T[] {
return x.slice(1);
}
/**
* Get values except last.
* @param x an array
* @returns x[0..|x|-1]
*/
export function init<T>(x: T[]): T[] {
return x.slice(0, -1);
}
/**
* Get last value.
* @param x an array
* @param vd default value
* @returns x[|x|-1] || vd
*/
export function last<T>(x: T[], vd?: T): T {
return x.length>0? x[x.length-1] : vd;
}
export {last as back};
/**
* Get values from middle.
* @param x an array
* @param i begin index
* @param n number of values [1]
* @returns x[i..i+n]
*/
export function middle<T>(x: T[], i: number, n: number=1): T[] {
var i = index(x, i);
var n = Math.max(n, 0);
return x.slice(i, i+n);
}
/**
* Get part of an array.
* @param x an array
* @param i begin index [0]
* @param I end index [|x|]
* @returns x[i..I]
*/
export function slice<T>(x: T[], i: number=0, I: number=x.length): T[] {
return x.slice(i, I);
}
/**
* Get part of an array!
* @param x an array (updated!)
* @param i begin index [0]
* @param I end index [|x|]
* @returns x = x[i..I]
*/
export function slice$<T>(x: T[], i: number=0, I: number=x.length): T[] {
x.copyWithin(0, i, I);
x.length = length(x, i, I);
return x;
}
// #endregion
// #region SEARCH VALUE
// --------------------
/**
* Check if array has a value.
* @param x an array
* @param v search value
* @param i begin index [0]
* @returns v ∈ x[i..]?
*/
export function includes<T>(x: T[], v: T, i: number=0): boolean {
return x.includes(v, i);
}
/**
* 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<T, U=T>(x: T[], v: T, fc: CompareFunction<T|U> | null=null, fm: MapFunction<T, T|U> | null=null): boolean {
return searchValue(x, v, fc, fm) >= 0;
}
/**
* Find first index of a value.
* @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
*/
export function searchValue<T, U=T>(x: T[], v: T, fc: CompareFunction<T|U> | null=null, fm: MapFunction<T, T|U> | 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;
}
/**
* Find last index of a value.
* @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
*/
export function searchValueRight<T, U=T>(x: T[], v: T, fc: CompareFunction<T|U> | null=null, fm: MapFunction<T, T|U> | 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;
}
/**
* Find indices of value.
* @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
*/
export function searchValueAll<T, U=T>(x: T[], v: T, fc: CompareFunction<T|U> | null=null, fm: MapFunction<T, T|U> | 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;
}
/**
* Find first index of an adjacent duplicate value.
* @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
*/
export function searchAdjacentDuplicateValue<T, U=T>(x: T[], fc: CompareFunction<T|U> | null=null, fm: MapFunction<T, T|U> | 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; ++i) {
var w = fm(x[i], i, x);
if (fc(w0, w)===0) return i;
w0 = w;
}
return -1;
}
export {searchAdjacentDuplicateValue as searchAdjacentDuplicate};
/**
* Find first index where two arrays differ.
* @param x an array
* @param y another array
* @param fc compare function (a, b)
* @param fm map function (v, i, x)
* @returns first index where x[i] ≠ y[i], or -1
*/
export function searchMismatchedValue<T, U=T>(x: T[], y: T[], fc: CompareFunction<T|U> | null=null, fm: MapFunction<T, T|U> | 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<I; ++i) {
var wx = fm(x[i], i, x);
var wy = fm(y[i], i, y);
if (fc(wx, wy)!==0) return i;
}
return X===Y? -1 : I;
}
export {searchMismatchedValue as searchMismatch};
// #endregion
// #region ARRANGEMENTS
// --------------------
/**
* 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?
*/
export function hasPrefix<T, U=T>(x: T[], y: T[], fc: CompareFunction<T|U> | null=null, fm: MapFunction<T, T|U> | null=null): boolean {
var Y = y.length;
return Y===0 || compare(x.slice(0, Y), y, fc, fm)===0;
}
export {hasPrefix as startsWith};
/**
* 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 hasSuffix<T, U=T>(x: T[], y: T[], fc: CompareFunction<T|U> | null=null, fm: MapFunction<T, T|U> | null=null): boolean {
var Y = y.length;
return Y===0 || compare(x.slice(-Y), y, fc, fm)===0;
}
export {hasSuffix as endsWith};
/**
* Examine if array contains an infix.
* @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?
*/
export function hasInfix<T, U=T>(x: T[], y: T[], fc: CompareFunction<T|U> | null=null, fm: MapFunction<T, T|U> | null=null): boolean {
return searchInfix(x, y, fc, fm) >= 0;
}
/**
* Examine if array has a subsequence.
* @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₁ < ...
*/
export function hasSubsequence<T, U=T>(x: T[], y: T[], fc: CompareFunction<T|U> | null=null, fm: MapFunction<T, T|U> | null=null): boolean {
return searchSubsequence(x, y, fc, fm) >= 0;
}
/**
* 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 hasPermutation<T, U=T>(x: T[], y: T[], fc: CompareFunction<T|U> | null=null, fm: MapFunction<T, T|U> | 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);
}
/**
* Obtain all possible prefixes.
* @param x an array
* @param n number of values [-1 ⇒ any]
* @returns [[], x[..1], x[..2], ...] if n<0; [x[..n]] otherwise
*/
export function prefixes<T>(x: T[], n: number=-1): T[][] {
return [...iprefixes(x, n)];
}
/**
* List all possible prefixes.
* @param x an array
* @param n number of values [-1 ⇒ any]
* @returns [], x[..1], x[..2], ... if n<0; x[..n] otherwise
*/
export function* iprefixes<T>(x: T[], n: number=-1): IterableIterator<T[]> {
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);
}
/**
* Obtain all possible suffixes.
* @param x an array
* @param n number of values [-1 ⇒ any]
* @returns [x[0..], x[1..], x[2..], ...] if n<0; [x[-n..]] otherwise
*/
export function suffixes<T>(x: T[], n: number=-1): T[][] {
return [...isuffixes(x, n)];
}
/**
* List all possible suffixes.
* @param x an array
* @param n number of values [-1 ⇒ any]
* @returns x[0..], x[1..], x[2..], ... if n<0; x[-n..] otherwise
*/
export function* isuffixes<T>(x: T[], n: number=-1): IterableIterator<T[]> {
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);
}
/**
* Obtain all possible infixes.
* @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
*/
export function infixes<T>(x: T[], n: number=-1): T[][] {
return [...iinfixes(x, n)];
}
/**
* List all possible infixes.
* @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
*/
export function iinfixes<T>(x: T[], n: number=-1): IterableIterator<T[]> {
if (n>=0) return infixesFixed(x, n);
else return infixesAll(x);
}
function* infixesFixed<T>(x: T[], n: number): IterableIterator<T[]> {
var X = x.length;
if (n>X) return;
if (n===0) { yield []; return; }
for (var i=0, I=X-n+1; i<I; ++i)
yield x.slice(i, i+n);
}
function* infixesAll<T>(x: T[]): IterableIterator<T[]> {
var X = x.length; yield [];
for (var i=0; i<X; ++i) {
for (var j=i+1; j<=X; ++j)
yield x.slice(i, j);
}
}
/**
* Obtain all possible subsequences.
* @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
*/
export function subsequences<T>(x: T[], n: number=-1): T[][] {
return [...isubsequences(x, n)];
}
/**
* List all possible subsequences.
* @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
*/
export function* isubsequences<T>(x: T[], n: number=-1): IterableIterator<T[]> {
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;
}
}
/**
* Obtain all possible permutations.
* @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<T>(x: T[], n: number=-1): T[][] {
return [...ipermutations(x, n)];
}
/**
* List all possible permutations.
* @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* ipermutations<T>(x: T[], n: number=-1): IterableIterator<T[]> {
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<T>(x: T[], n: number): IterableIterator<T[]> {
var X = x.length;
if (X===0 || n===0) { yield []; return; }
for (var i=0; i<X; ++i) {
var y = splice(x, i, 1);
for (var p of ipermutationsFixed(y, n-1))
yield [x[i], ...p];
}
}
/**
* Find first 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 searchInfix<T, U=T>(x: T[], y: T[], fc: CompareFunction<T|U> | null=null, fm: MapFunction<T, T|U> | 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;
}
/**
* 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 searchInfixRight<T, U=T>(x: T[], y: T[], fc: CompareFunction<T|U> | null=null, fm: MapFunction<T, T|U> | 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;
}
/**
* Find indices 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 i₀, i₁, ... | x[j..j+|y|] = y; j ∈ [i₀, i₁, ...]
*/
export function searchInfixAll<T, U=T>(x: T[], y: T[], fc: CompareFunction<T|U> | null=null, fm: MapFunction<T, T|U> | 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<T, U=T>(x: T[], y: T[], i: number, fc: CompareFunction<T|U>, fm: MapFunction<T, T|U>): boolean {
var Y = y.length;
for (var j=0; j<Y; ++j) {
var wx = fm(x[i+j], i+j, x);
var wy = fm(y[j], j, y);
if (fc(wx, wy)!==0) return false;
}
return true;
}
/**
* Find first index of a subsequence.
* @param x an array
* @param y search subsequence
* @param fc compare function (a, b)
* @param fm map function (v, i, x)
* @returns begin index of subsequence, -1 if not found
*/
export function searchSubsequence<T, U=T>(x: T[], y: T[], fc: CompareFunction<T|U> | null=null, fm: MapFunction<T, T|U> | 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;
}
// #endregion
// #region RANDOM ARRANGEMENTS
// ---------------------------
/**
* Pick an arbitrary value.
* @param x an array
* @param fr random number generator ([0, 1))
* @returns x[i] | i ∈ 0..|x|
*/
export function randomValue<T>(x: T[], fr: ReadFunction<number> | null=Math.random): T {
var i = Math.floor(fr() * x.length);
return x[i];
}
export {randomValue as value}; // DEPRECATED
/**
* Pick an arbitrary prefix.
* @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|
*/
export function randomPrefix<T>(x: T[], n: number=-1, fr: ReadFunction<number> | 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
/**
* Pick an arbitrary suffix.
* @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|
*/
export function randomSuffix<T>(x: T[], n: number=-1, fr: ReadFunction<number> | 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
/**
* Pick an arbitrary infix.
* @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|
*/
export function randomInfix<T>(x: T[], n: number=-1, fr: ReadFunction<number> | 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);
}
/**
* Pick an arbitrary subsequence.
* @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
*/
export function randomSubsequence<T>(x: T[], n: number=-1, fr: ReadFunction<number> | 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<T>(x: T[], n: number, fr: ReadFunction<number>): T[] {
var is = fromRange(0, x.length);
randomPermutation$(is, n, fr).sort();
return getAll(x, is);
}
function randomSubsequenceAll<T>(x: T[], fr: ReadFunction<number>): T[] {
var a = [];
for (var v of x)
if (fr()<0.5) a.push(v);
return a;
}
/**
* Pick an arbitrary permutation.
* @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
*/
export function randomPermutation<T>(x: T[], n: number=-1, fr: ReadFunction<number> | null=Math.random): T[] {
var X = x.length;
if (n>X) return null;
return randomPermutation$(x.slice(), n, fr);
}
export {randomPermutation as permutation}; // DEPRECATED
/**
* 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 randomPermutation$<T>(x: T[], n: number=-1, fr: ReadFunction<number> | 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<n; ++i) {
var j = i + Math.floor((X-i)*fr());
var t = x[i]; x[i] = x[j]; x[j] = t;
}
x.length = n;
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
// ------------
/**
* Find first value passing a test.
* @param x an array
* @param ft test function (v, i, x)
* @returns first v | ft(v) = true; v ∈ x
*/
export function find<T>(x: T[], ft: TestFunction<T>): T {
return x.find(ft);
}
/**
* Find last value passing a test.
* @param x an array
* @param ft test function (v, i, x)
* @returns last v | ft(v) = true; v ∈ x
*/
export function findRight<T>(x: T[], ft: TestFunction<T>): T {
for (var i=x.length-1; i>=0; --i)
if (ft(x[i], i, x)) return x[i];
}
// #endregion
// #region TAKE/DROP
// -----------------
/**
* Keep first n values only.
* @param x an array
* @param n number of values [1]
* @returns x[0..n]
*/
export function take<T>(x: T[], n: number=1): T[] {
var n = Math.max(n, 0);
return x.slice(0, n);
}
export {take as left};
/**
* Keep last n values only.
* @param x an array
* @param n number of values [1]
* @returns x[0..n]
*/
export function takeRight<T>(x: T[], n: number=1): T[] {
var X = x.length;
var n = Math.min(Math.max(n, 0), X);
return x.slice(X-n);
}
export {takeRight as right};
/**
* Keep values from left, while a test passes.
* @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
*/
export function takeWhile<T>(x: T[], ft: TestFunction<T>): T[] {
return x.slice(0, scanWhile(x, ft));
}
/**
* Keep values from right, while a test passes.
* @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
*/
export function takeWhileRight<T>(x: T[], ft: TestFunction<T>): T[] {
return x.slice(scanWhileRight(x, ft));
}
/**
* Discard first n values only.
* @param x an array
* @param n number of values [1]
* @returns x[n..]
*/
export function drop<T>(x: T[], n: number=1): T[] {
var n = Math.max(n, 0);
return x.slice(n);
}
/**
* Discard last n values only.
* @param x an array
* @param n number of values [1]
* @returns x[0..-n]
*/
export function dropRight<T>(x: T[], n: number=1): T[] {
var X = x.length;
var n = Math.min(Math.max(n, 0), X);
return x.slice(0, X-n);
}
/**
* Discard values from left, while a test passes.
* @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
*/
export function dropWhile<T>(x: T[], ft: TestFunction<T>): T[] {
return x.slice(scanWhile(x, ft));
}
/**
* Discard values from right, while a test passes.
* @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
*/
export function dropWhileRight<T>(x: T[], ft: TestFunction<T>): T[] {
return x.slice(0, scanWhileRight(x, ft));
}
// #endregion
// #region SCAN
// ------------
/**
* Scan from left, while a test passes.
* @param x an array
* @param ft test function (v, i, x)
* @returns first index where test fails
*/
export function scanWhile<T>(x: T[], ft: TestFunction<T>): number {
var i = -1;
for (var v of x)
if (!ft(v, ++i, x)) return i;
return ++i;
}
/**
* Scan from right, while a test passes.
* @param x an array
* @param ft test function (v, i, x)
* @returns first index where test passes till end
*/
export function scanWhileRight<T>(x: T[], ft: TestFunction<T>): number {
for (var i=x.length-1; i>=0; --i)
if (!ft(x[i], i, x)) break;
return ++i;
}
/**
* Scan from left, until a test passes.
* @param x an array
* @param ft test function (v, i, x)
* @returns first index where test passes
*/
export function scanUntil<T>(x: T[], ft: TestFunction<T>): number {
var i = -1;
for (var v of x)
if (ft(v, ++i, x)) return i;
return ++i;
}
/**
* Scan from right, until a test passes.
* @param x an array
* @param ft test function (v, i, x)
* @returns first index where test fails till end
*/
export function scanUntilRight<T>(x: T[], ft: TestFunction<T>): number {
for (var i=x.length-1; i>=0; --i)
if (ft(x[i], i, x)) break;
return ++i;
}
// #endregion
// #region SEARCH
// --------------
/**
* Find first index of a value.
* @param x an array
* @param v search value
* @param i begin index [0]
* @returns index of v in x[i..] if found else -1
*/
export function indexOf<T>(x: T[], v: T, i: number=0): number {
return x.indexOf(v, i);
}
/**
* Find last index of a value.
* @param x an array
* @param v search value
* @param i begin index [|x|-1]
* @returns last index of v in x[0..i] if found else -1
*/
export function lastIndexOf<T>(x: T[], v: T, i: number=x.length-1) {
return x.lastIndexOf(v, i);
}
/**
* Find index of first value passing a test.
* @param x an array
* @param ft test function (v, i, x)
* @returns first index of value, -1 if not found
*/
export function search<T>(x: T[], ft: TestFunction<T>): number {
return x.findIndex(ft);
}
export {search as findIndex};
/**
* 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 searchRight<T>(x: T[], ft: TestFunction<T>): number {
for (var i=x.length-1; i>=0; --i)
if (ft(x[i], i, x)) return i;
return -1;
}
export {searchRight as findLastIndex};
/**
* Find indices of values passing a test.
* @param x an array
* @param ft test function (v, i, x)
* @returns indices of value
*/
export function searchAll<T>(x: T[], ft: TestFunction<T>): number[] {
var i = -1, a = [];
for (var v of x)
if (ft(v, ++i, x)) a.push(i);
return a;
}
// #endregion
// #region FUNCTIONAL
// ------------------
/**
* Call a function for each value.
* @param x an array
* @param fp process function (v, i, x)
*/
export function forEach<T>(x: T[], fp: ProcessFunction<T>): void {
x.forEach(fp);
}
/**
* 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 some<T>(x: T[], ft: TestFunction<T> | null=null): boolean {
if (ft) return x.some(ft);
else return someBoolean(x);
}
export {some as anyOf};
function someBoolean<T>(x: T[]): boolean {
for (var i=0, I=x.length; i<I; ++i)
if (x[i]) return true;
return false;
}
/**
* Examine if all values satisfy a test.
* @param x an array
* @param ft test function (v, i, x)
* @returns true if ft(vᵢ) = true for all vᵢ ∈ x
*/
export function every<T>(x: T[], ft: TestFunction<T> | null=null): boolean {
if (ft) return x.every(ft);
else return everyBoolean(x);
}
export {every as allOf};
function everyBoolean<T>(x: T[]): boolean {
for (var i=0, I=x.length; i<I; ++i)
if (!x[i]) return false;
return true;
}
/**
* Transform values of an array.
* @param x an array
* @param fm map function (v, i, x)
* @returns [fm(v₀), fm(v₁), ...] | vᵢ ∈ x
*/
export function map<T, U=T>(x: T[], fm: MapFunction<T, T|U>): (T|U)[] {
return x.map(fm);
}
/**
* 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
*/
export function map$<T>(x: T[], fm: MapFunction<T, T>): T[] {
for (var i=0, I=x.length; i<I; ++i)
x[i] = fm(x[i], i, x);
return x;
}
/**
* Reduce values of array 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 reduce<T, U=T>(x: T[], fr: ReduceFunction<T, T|U>, acc?: T|U): T|U {
var init = arguments.length <= 2;
return init? x.reduce(fr as any) : x.reduce(fr, acc);
}
/**
* 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 reduceRight<T, U=T>(x: T[], fr: ReduceFunction<T, T|U>, 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;
}
/**
* Keep values which pass a test.
* @param x an array
* @param ft test function (v, i, x)
* @returns [v₀, v₁, ...] | ft(vᵢ) = true; vᵢ ∈ x
*/
export function filter<T>(x: T[], ft: TestFunction<T>): T[] {
return x.filter(ft);
}
export {filter as findAll};
/**
* Keep values which pass a test!
* @param x an array (updated!)
* @param ft test function (v, i, x)
* @returns x = [v₀, v₁, ...] | ft(vᵢ) = true; vᵢ ∈ x
*/
export function filter$<T>(x: T[], ft: TestFunction<T>): T[] {
for (var i=0, j=0, I=x.length; i<I; ++i)
if (ft(x[i], i, x)) x[j++] = x[i];
x.length = j;
return x;
}
/**
* Keep values at given indices.
* @param x an array
* @param is indices
* @returns v₀, v₁, ... | vᵢ = x[i]; i ∈ is
*/
export function filterAt<T>(x: T[], is: number[]): T[] {
var X = x.length, a = [];
for (var i of is)
if (i>=0 && i<X) a.push(x[i]);
return a;
}
/**
* Discard values which pass a test.
* @param x an array
* @param ft test function (v, i, x)
* @returns [v₀, v₁, ...] | ft(vᵢ) = false; vᵢ ∈ x
*/
export function reject<T>(x: T[], ft: TestFunction<T>): T[] {
var i = -1, a = [];
for (var v of x)
if (!ft(v, ++i, x)) a.push(v);
return a;
}
/**
* 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
*/
export function reject$<T>(x: T[], ft: TestFunction<T>): T[] {
for (var i=0, j=0, I=x.length; i<I; ++i)
if (!ft(x[i], i, x)) x[j++] = x[i];
x.length = j;
return x;
}
/**
* Discard values at given indices.
* @param x an array
* @param is indices
* @returns [v₀, v₁, ...] | vᵢ = x[i]; i ∉ is
*/
export function rejectAt<T>(x: T[], is: number[]): T[] {
var i = -1, a = [];
for (var v of x)
if (!is.includes(++i)) a.push(v);
return a;
}
// #endregion
// #region FLATTEN
// ---------------
/**
* 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 flat(x: any[], n: number=-1, fm: MapFunction<any, any> | null=null, ft: TestFunction<any> | null=null): any[] {
var fm = fm || IDENTITY;
var ft = ft || is;
return flatTo$([], x, n, fm, ft);
}
function flatTo$(a: any[], x: any[], n: number, fm: MapFunction<any, any>, ft: TestFunction<any>): 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;
}
/**
* Flatten nested array, based on map function.
* @param x an array
* @param fm map function (v, i, x)
* @param ft flatten test function (v, i, x) [is]
* @returns flat iterable
*/
export function flatMap(x: any[], fm: MapFunction<any, any> | null=null, ft: TestFunction<any> | 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;
}
// #endregion
// #region PREFIX SUM
// ------------------
/**
* Perform exclusive prefix scan from left to right.
* @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₁)...]
*/
export function exclusiveScan<T, U=T>(x: T[], fr: ReduceFunction<T, T|U>, acc: T|U): (T|U)[] {
var a = [];
for (var i=0, I=x.length; i<I; ++i) {
a.push(acc);
acc = fr(acc, x[i], i, x);
}
return a;
}
/**
* 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$<T>(x: T[], fr: ReduceFunction<T, T>, acc: T): T[] {
for (var i=0, I=x.length; i<I; ++i) {
var v = x[i];
x[i] = acc;
acc = fr(acc, v, i, x);
}
return x;
}
/**
* Perform inclusive prefix scan from left to right.
* @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₁)...]
*/
export function inclusiveScan<T, U=T>(x: T[], fr: ReduceFunction<T, T|U>, acc?: T|U): (T|U)[] {
var init = arguments.length <= 2, a = [];
for (var i=0, I=x.length; i<I; ++i) {
acc = init? x[i] : fr(acc, x[i], i, x);
a.push(acc);
init = false;
}
return a;
}
export {inclusiveScan as accumulate};
/**
* Perform inclusive 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 = [fr(acc, v₀), fr(fr(acc, v₀), v₁)...]
*/
export function inclusiveScan$<T>(x: T[], fr: ReduceFunction<T, T>, acc: T): T[] {
for (var i=0, I=x.length; i<I; ++i)
acc = x[i] = fr(acc, x[i], i, x);
return x;
}
/**
* Combine adjacent values of an array.
* @param x an array
* @param fc combine function (u, v)
* @param acc initial value
* @returns [fc(acc, v₀), fc(v₀, v₁)...] | vᵢ ∈ x
*/
export function adjacentCombine<T>(x: T[], fc: CombineFunction<T>, acc: T): T[] {
var a = [];
if (x.length>0) a.push(fc(acc, x[0]));
for (var i=1, I=x.length; i<I; ++i)
a.push(fc(x[i-1], x[i]));
return a;
}
// 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
*/
export function adjacentCombine$<T>(x: T[], fc: CombineFunction<T>, 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; ++i) {
var w = x[i];
x[i] = fc(v, w);
v = w;
}
return x;
}
// #endregion
// #region COMBINE
// ---------------
/**
* Place a separator between every value.
* @param x an array
* @param v separator
* @returns [x[0], v, x[1], v, ..., x[|x|-1]]
*/
export function intersperse<T>(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;
}
/**
* 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<T>(x: T[], fc: CombineFunction<T>): 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;
}
/**
* 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<T>(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 = [];
var m = Math.max(m, 0);
var n = Math.max(n, 0);
var s = Math.max(s, 1);
var t = Math.max(t, 1);
for (var i=0, j=0; i<X; i+=s) {
if (i>0) {
for (var k=j, K=k+n; k<K; ++k)
a.push(y[k % Y]);
j += t;
}
concat$(a, x.slice(i, i+m));
}
return a;
}
/**
* Place values from iterables alternately.
* @param xs arrays
* @returns [x₀[0], x₁[0], ..., x₀[1], x₁[0], ...] | [x₀, x₁, ...] = xs
*/
export function interleave<T>(xs: T[][]): T[] {
var a = [];
for (var i=0;; ++i) {
var n = 0;
for (var x of xs)
if (i<x.length) { a.push(x[i]); ++n; }
if (n===0) break;
}
return a;
}
/**
* 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 zip<T, U=T[]>(xs: T[][], fm: MapFunction<T[], T[]|U> | 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<X; ++j) {
ds[j] = i>=ls[j];
vs[j] = ds[j]? vd : xs[j][i];
}
if (fe(ds)) break;
a.push(fm(vs, i, null));
}
return a;
}
// TODO: zip2()?
// #endregion
// #region MANIPULATION
// --------------------
/**
* Fill with given value.
* @param x an array
* @param v value
* @param i begin index [0]
* @param I end index [|x|]
* @returns x' | x' = x; x'[i..I] = v
*/
export function fill<T>(x: T[], v: T, i: number=0, I: number=x.length): T[] {
return x.slice().fill(v, i, I);
}
/**
* Fill with given value!
* @param x an array (updated!)
* @param v value
* @param i begin index [0]
* @param I end index [|x|]
* @returns x | x[i..I] = v
*/
export function fill$<T>(x: T[], v: T, i: number=0, I: number=x.length): T[] {
return x.fill(v, i, I);
}
/**
* Add value to the end.
* @param x an array
* @param vs values to add
* @returns x ⧺ vs
*/
export function push<T>(x: T[], ...vs: T[]): T[] {
return x.concat(vs);
}
export {push as pushBack};
export {push as append};
/**
* Add values to the end!
* @param x an array (updated!)
* @param vs values to add
* @returns x = x ⧺ vs
*/
export function push$<T>(x: T[], ...vs: T[]): T[] {
x.push(...vs);
return x;
}
export {push$ as pushBack$};
export {push$ as append$};
/**
* Remove last value.
* @param x an array
* @returns x[0..|x|-1]
*/
export function pop<T>(x: T[]): T[] {
return x.slice(0, -1);
}
export {pop as popBack};
/**
* Remove last value!
* @param x an array (updated!)
* @returns x = x[0..|x|-1]
*/
export function pop$<T>(x: T[]): T[] {
x.pop();
return x;
}
export {pop as popBack$};
/**
* Remove first value.
* @param x an array
* @returns x[1..]
*/
export function shift<T>(x: T[]): T[] {
return x.slice(1);
}
export {shift as popFront};
/**
* Remove first value!
* @param x an array (updated!)
* @returns x = x[1..]
*/
export function shift$<T>(x: T[]): T[] {
x.shift();
return x;
}
export {shift$ as popFront$};
/**
* Add values to the start.
* @param x an array
* @param vs values to add
* @returns vs ⧺ x
*/
export function unshift<T>(x: Iterable<T>, ...vs: T[]): T[] {
return concat$(vs, x);
}
export {unshift as pushFront};
export {unshift as prepend};
/**
* Add values to the start!
* @param x an array (updated!)
* @param vs values to add
* @returns x = vs ⧺ x
*/
export function unshift$<T>(x: T[], ...vs: T[]): T[] {
x.unshift(...vs);
return x;
}
export {unshift$ as pushFront$};
export {unshift$ as prepend$};
/**
* Copy part of array to another.
* @param x target array
* @param y source array
* @param j write index [0]
* @param i read begin index [0]
* @param I read end index [|x|]
* @returns x[0..j] ⧺ y[i..I] ⧺ x[j+I-i..]
*/
export function copy<T>(x: T[], y: T[], j: number=0, i: number=0, I: number=y.length): T[] {
return copy$(x.slice(), y, j, i, I);
}
/**
* Copy part of array to another!
* @param x target array (updated!)
* @param y source array
* @param j write index [0]
* @param i read begin index [0]
* @param I read end index [|x|]
* @returns x = x[0..j] ⧺ y[i..I] ⧺ x[j+I-i..]
*/
export function copy$<T>(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<I; ++i, ++j)
x[j] = y[i];
return x;
}
/**
* Copy part of array within.
* @param x an array
* @param j write index [0]
* @param i read begin index [0]
* @param I read end index [|x|]
* @returns x[0..j] ⧺ x[i..I] ⧺ x[j+I-i..]
*/
export function copyWithin<T>(x: T[], j: number=0, i: number=0, I: number=x.length): T[] {
return x.slice().copyWithin(j, i, I);
}
/**
* Copy part of array within!
* @param x an array (updated!)
* @param j write index [0]
* @param i read begin index [0]
* @param I read end index [|x|]
* @returns x = x[0..j] ⧺ x[i..I] ⧺ x[j+I-i..]
*/
export function copyWithin$<T>(x: T[], j: number=0, i: number=0, I: number=x.length): T[] {
return x.copyWithin(j, i, I);
}
/**
* Move part of array within.
* @param x an array
* @param j write index [0]
* @param i read begin index [0]
* @param I read end index [|x|]
* @returns x[0..j] ⧺ x[i..I] ⧺ x[j..i] ⧺ x[I..]
*/
export function moveWithin<T>(x: T[], j: number=0, i: number=0, I: number=x.length): T[] {
var j = index(x, j);
var [i, I] = indexRange(x, i, I);
if (j<i) return movePart(x, j, i, I);
else return movePart(x, i, I, j);
}
function movePart<T>(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 begin ±index [0]
* @param I read end ±index [|x|]
* @returns x = x[0..j] ⧺ x[i..I] ⧺ x[j..i] ⧺ x[I..]
*/
export function moveWithin$<T>(x: T[], j: number=0, i: number=0, I: number=x.length): T[] {
var j = index(x, j);
var [i, I] = indexRange(x, i, I);
var p = x.slice(i, I), P = p.length;
if (j<i) x.copyWithin(j+P, j, i);
else x.copyWithin(i, I, j);
return copy$(x, p, j<i? j : j-P);
}
// DOUBT: Are negative indices any helpful (other than basic shortcuts)?
// DOUBT: It is not easy to use a negative index supporting function from another negative index supporting function.
// DOUBT: Seems to me they are only complicating implementation of algorithms, and should be removed.
/**
* Remove or replace existing values.
* @param x an array
* @param i remove ±index
* @param n number of values to remove [rest]
* @param vs values to insert
* @returns x[0..i] ⧺ vs ⧺ x[i+n..]
*/
export function splice<T>(x: T[], i: number, n: number=x.length, ...vs: T[]): T[] {
var i = index(x, i);
var n = Math.max(n, 0);
return concat$(x.slice(0, i), vs, x.slice(i+n));
}
export {splice as toSpliced};
/**
* 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$<T>(x: T[], i: number, n: number=x.length, ...vs: T[]): T[] {
x.splice(i, n, ...vs);
return x;
}
// #endregion
// #region COUNT/PARTITION
// -----------------------
/**
* 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<T>(x: T[], ft: TestFunction<T>): number {
var i = -1, a = 0;
for (var v of x)
if (ft(v, ++i, x)) ++a;
return a;
}
/**
* Count occurrences of each distinct value.
* @param x an array
* @param fm map function (v, i, x)
* @returns Map \{value ⇒ count\}
*/
export function countEach<T, U=T>(x: T[], fm: MapFunction<T, T|U> | null=null): Map<T|U, number> {
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 {countEach as countAs}; // DEPRECATED
/**
* Segregate values by test result.
* @param x an array
* @param ft test function (v, i, x)
* @returns [satisfies, doesnt]
*/
export function partition<T>(x: T[], ft: TestFunction<T>): [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];
}
/**
* Segregate each distinct value.
* @param x an array
* @param fm map function (v, i, x)
* @returns Map \{key ⇒ values\}
*/
export function partitionEach<T, U=T>(x: T[], fm: MapFunction<T, T|U> | null=null): Map<T|U, T[]> {
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 {partitionEach as groupToMap};
export {partitionEach as partitionAs}; // DEPRECATED
// #endregion
// #region SPLITS
// --------------
/**
* Break array considering test as separator.
* @param x an array
* @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 split<T>(x: T[], ft: TestFunction<T>): 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);
return a;
}
/**
* 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<T>(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 = []; }
}
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, ...
*/
export function cut<T>(x: T[], ft: TestFunction<T>): T[][] {
var j = 0, a = [];
for (var i=0, I=x.length; i<I; ++i) {
if (!ft(x[i], i, x)) continue;
a.push(x.slice(j, i));
j = i;
}
a.push(x.slice(j));
return a;
}
/**
* 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<T>(x: T[], ft: TestFunction<T>): T[][] {
var j = 0, a = [];
for (var i=0, I=x.length; i<I; ++i) {
if (!ft(x[i], i, x)) continue;
a.push(x.slice(j, i+1));
j = i+1;
}
a.push(x.slice(j));
return a;
}
/**
* Break array at given indices.
* @param x an array
* @param is split ±indices (left to right)
* @returns [x[0..j], x[j..k], ...] | ft(x[i]) = true; i = j, k, ...; i ∈ is
*/
export function cutAt<T>(x: T[], is: number[]): T[][] {
var X = x.length;
var j = 0, a = [];
for (var i of is) {
var i = i<0? X+i : i;
a.push(x.slice(j, i));
j = Math.max(j, i);
}
a.push(x.slice(j));
return a;
}
/**
* Break array after given indices.
* @param x an array
* @param is split ±indices (left to right)
* @returns [x[0..j+1], x[j+1..k], ...] | ft(x[i]) = true; i = j, k, ...; i ∈ is
*/
export function cutAtRight<T>(x: T[], is: number[]): T[][] {
var X = x.length;
var j = 0, a = [];
for (var i of is) {
var i = i<0? X+i : i;
a.push(x.slice(j, i+1));
j = Math.max(j, i+1);
}
a.push(x.slice(j));
return a;
}
/**
* 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 / ...
*/
export function group<T, U=T>(x: T[], fc: CompareFunction<T|U> | null=null, fm: MapFunction<T, T|U> | null=null): T[][] {
var fc = fc || COMPARE;
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;
}
a.push(b);
return a;
}
/**
* 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<T>(x: T[], n: number=1, s: number=n): T[][] {
var a = [];
if (n<0) return a;
var s = Math.max(s, 1);
for (var i=0, I=x.length; i<I; i+=s)
a.push(x.slice(i, i+n));
return a;
}
// #endregion
// #region CONCAT/JOIN
// -------------------
/**
* Append values from arrays.
* @param xs arrays
* @returns x₀ ⧺ x₁ ⧺ ... | [x₀, x₁, ...] = xs
*/
export function concat<T>(...xs: T[][]): T[] {
return [].concat(...xs);
}
/**
* Append values from arrays!
* @param x an array (updated!)
* @param ys arrays to append
* @returns x = x ⧺ y₀ ⧺ y₁ ⧺ ...] | [y₀, y₁, ...] = ys
*/
export function concat$<T>(x: T[], ...ys: Iterable<T>[]): 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<T>(x: T[], sep: string=","): string {
return x.join(sep);
}
// #endregion
// #region REARRANGE
// -----------------
/**
* Obtain values that cycle through array.
* @param x an array
* @param i begin ±index [0]
* @param n number of values [|x|]
*/
export function cycle<T>(x: T[], i: number=0, n: number=x.length): T[] {
var X = x.length;
if (n<=0 || X===0) return [];
var i = mod(i, X);
var a = x.slice(i, i+n);
n -= a.length;
for (var m=0, M=Math.floor(n/X); m<M; ++m)
concat$(a, x);
return concat$(a, x.slice(0, n % X));
}
/**
* Repeat an array given times.
* @param x an array
* @param n times [1]
* @returns x ⧺ x ⧺ ...(n times)
*/
export function repeat<T>(x: T[], n: number=1): T[] {
for (var a=[]; n>0; --n)
concat$(a, x);
return a;
}
/**
* Reverse the values.
* @param x an array
* @returns [x[|x|-1], x[|x|-2], ..., x[1], x[0]]
*/
export function reverse<T>(x: T[]): T[] {
return x.slice().reverse();
}
export {reverse as toReversed};
/**
* Reverse the values!
* @param x an array (updated!)
* @returns x = [x[|x|-1], x[|x|-2], ..., x[1], x[0]]
*/
export function reverse$<T>(x: T[]): T[] {
return x.reverse();
}
/**
* Rotate values in array.
* @param x an array
* @param n rotate amount (+ve: left, -ve: right) [0]
* @returns x[n..] ⧺ x[0..n]
*/
export function rotate<T>(x: T[], n: number=0): T[] {
var n = mod(n, x.length);
return concat$(x.slice(n), x.slice(0, n));
}
/**
* Rotate values in array!
* @param x an array (updated!)
* @param n rotate amount (+ve: left, -ve: right) [0]
* @returns x = x[n..] ⧺ x[0..n]
*/
export function rotate$<T>(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);
}
// #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)
* @returns ∀ vᵢ, vⱼ ∈ x, is vᵢ ≠ vⱼ?
*/
export function isUnique<T, U=T>(x: T[], fc: CompareFunction<T|U> | null=null, fm: MapFunction<T, T|U> | null=null): boolean {
if (fc) return isUniqueDual(x, fc, fm);
else return isUniqueMap (x, fm);
}
function isUniqueMap<T, U=T>(x: T[], fm: MapFunction<T, T|U> | 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<T, U=T>(x: T[], fc: CompareFunction<T|U> | null=null, fm: MapFunction<T, T|U> | 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;
}
/**
* 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)
* @returns x ∩ y = Φ?
*/
export function isDisjoint<T, U=T>(x: T[], y: T[], fc: CompareFunction<T|U> | null=null, fm: MapFunction<T, T|U> | null=null): boolean {
if (fc) return isDisjointDual(x, y, fc, fm);
else return isDisjointMap (x, y, fm);
}
function isDisjointMap<T, U=T>(x: T[], y: T[], fm: MapFunction<T, T|U> | 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;
}
function isDisjointDual<T, U=T>(x: T[], y: T[], fc: CompareFunction<T|U> | null=null, fm: MapFunction<T, T|U> | 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;
}
/**
* Remove duplicate values.
* @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
*/
export function unique<T, U=T>(x: T[], fc: CompareFunction<T|U> | null=null, fm: MapFunction<T, T|U> | null=null): T[] {
if (fc) return uniqueDual(x, fc, fm);
else return uniqueMap (x, fm);
}
function uniqueMap<T, U=T>(x: T[], fm: MapFunction<T, T|U> | 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<T, U=T>(x: T[], fc: CompareFunction<T|U> | null=null, fm: MapFunction<T, T|U> | 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;
}
/**
* 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<T, U=T>(x: T[], y: T[], fc: CompareFunction<T|U> | null=null, fm: MapFunction<T, T|U> | null=null): T[] {
return union$(x.slice(), y, fc, fm);
}
/**
* Obtain values present in any array!
* @param x an array (updated!)
* @param y another array
* @param fc compare function (a, b)
* @param fm map function (v, i, x)
* @returns x = x ∪ y = \{v | v ∈ x or v ∈ y\}
*/
export function union$<T, U=T>(x: T[], y: T[], fc: CompareFunction<T|U> | null=null, fm: MapFunction<T, T|U> | null=null): T[] {
if (fc) return unionDual$(x, y, fc, fm);
else return unionMap$ (x, y, fm);
}
function unionMap$<T, U=T>(x: T[], y: T[], fm: MapFunction<T, T|U> | 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$<T, U=T>(x: T[], y: T[], fc: CompareFunction<T|U> | null=null, fm: MapFunction<T, T|U> | 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;
}
/**
* 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)
* @returns x ∩ y = \{v | v ∈ x, v ∈ y\}
*/
export function intersection<T, U=T>(x: T[], y: T[], fc: CompareFunction<T|U> | null=null, fm: MapFunction<T, T|U> | null=null): T[] {
if (fc) return intersectionDual(x, y, fc, fm);
else return intersectionMap (x, y, fm);
}
function intersectionMap<T, U=T>(x: T[], y: T[], fm: MapFunction<T, T|U> | 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<T, U=T>(x: T[], y: T[], fc: CompareFunction<T|U> | null=null, fm: MapFunction<T, T|U> | 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 a;
}
/**
* 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<T, U=T>(x: T[], y: T[], fc: CompareFunction<T|U> | null=null, fm: MapFunction<T, T|U> | null=null): T[] {
if (fc) return differenceDual(x, y, fc, fm);
else return differenceMap (x, y, fm);
}
function differenceMap<T, U=T>(x: T[], y: T[], fm: MapFunction<T, T|U> | 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 differenceDual<T, U=T>(x: T[], y: T[], fc: CompareFunction<T|U> | null=null, fm: MapFunction<T, T|U> | 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;
}
/**
* 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)
* @returns x-y ∪ y-x
*/
export function symmetricDifference<T, U=T>(x: T[], y: T[], fc: CompareFunction<T|U> | null=null, fm: MapFunction<T, T|U> | 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<T, U=T>(xs: T[][], fm: MapFunction<T[], T[]|U> | 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<XS; ++j)
vs.push(xs[j][is[j]]);
a.push(fm(vs, i, null));
for (var r=XS-1; r>=0; --r) {
if (++is[r]<ls[r]) break;
is[r] = 0;
}
if (r<0) break;
}
return a;
}
// #endregion
// #endregion