@thi.ng/arrays
Advanced tools
Comparing version 2.7.7 to 2.7.8
import { compare } from "@thi.ng/compare/compare"; | ||
import { fillRange } from "./fill-range.js"; | ||
import { sortByCachedKey } from "./sort-cached.js"; | ||
/** | ||
* Returns a new array of numeric indices in same order as sorted values of | ||
* `src` using given (optional) comparator (default: thi.ng/compare). The `src` | ||
* array will remain unmodified. | ||
* | ||
* @remarks | ||
* Also see {@link swizzle} to read an array in custom order. | ||
* | ||
* @example | ||
* ```ts | ||
* const src = ["a", "c", "d", "b"]; | ||
* | ||
* argSort(src) | ||
* // [ 0, 3, 1, 2 ] | ||
* | ||
* // src[0] => "a" | ||
* // src[3] => "b" | ||
* // src[1] => "c" | ||
* // src[2] => "d" | ||
* | ||
* swizzle(argSort(src))(src) | ||
* // [ 'a', 'b', 'c', 'd' ] | ||
* ``` | ||
* | ||
* @param src - | ||
* @param cmp - | ||
*/ | ||
export const argSort = (src, cmp = compare) => sortByCachedKey(fillRange(new Array(src.length)), src.slice(), cmp); | ||
const argSort = (src, cmp = compare) => sortByCachedKey(fillRange(new Array(src.length)), src.slice(), cmp); | ||
export { | ||
argSort | ||
}; |
@@ -1,42 +0,16 @@ | ||
/** | ||
* Takes an array of numbers and returns the index of the item which is | ||
* considered the minimum value according to the given initial `min` value | ||
* (default: ∞) and predicate (both optional). Returns -1 if `items` is empty. | ||
* | ||
* @remarks | ||
* See {@link argMax}. | ||
* | ||
* @example | ||
* ```ts | ||
* argMin([42, 11, 66, 23]) | ||
* // 1 | ||
* | ||
* // same as argmax() with defaults | ||
* argMin([42, 11, 66, 23], -Infinity, (a, b) => a > b) | ||
* // 2 | ||
* ``` | ||
* | ||
* @param buf | ||
* @param min | ||
* @param pred | ||
*/ | ||
export const argMin = (buf, min = Infinity, pred = (a, b) => a < b) => { | ||
let id = -1; | ||
for (let i = 0, n = buf.length; i < n; i++) { | ||
const x = buf[i]; | ||
if (pred(x, min)) { | ||
min = x; | ||
id = i; | ||
} | ||
const argMin = (buf, min = Infinity, pred = (a, b) => a < b) => { | ||
let id = -1; | ||
for (let i = 0, n = buf.length; i < n; i++) { | ||
const x = buf[i]; | ||
if (pred(x, min)) { | ||
min = x; | ||
id = i; | ||
} | ||
return id; | ||
} | ||
return id; | ||
}; | ||
/** | ||
* Similar to {@link argMin}, but selects index of maximum item. Returns -1 if | ||
* `items` is empty. | ||
* | ||
* @param items | ||
* @param min | ||
* @param pred | ||
*/ | ||
export const argMax = (items, min = -Infinity, pred = (a, b) => a > b) => argMin(items, min, pred); | ||
const argMax = (items, min = -Infinity, pred = (a, b) => a > b) => argMin(items, min, pred); | ||
export { | ||
argMax, | ||
argMin | ||
}; |
import { compare } from "@thi.ng/compare/compare"; | ||
import { compareNumAsc } from "@thi.ng/compare/numeric"; | ||
/** | ||
* Returns the supposed index of `x` in pre-sorted array-like collection `buf`. | ||
* | ||
* @remarks | ||
* If `x` can't be found, returns `-index-1`, representing the negative of the | ||
* index, were `x` to be inserted into `buf`. E.g if the return value is -3, `x` | ||
* would appear/insert at index 2. | ||
* | ||
* The optional `key` function is used to obtain the actual sort value of `x` | ||
* and each array item (default: identity). | ||
* | ||
* The optional `cmp` comparator (default: | ||
* [`compare()`](https://docs.thi.ng/umbrella/compare/functions/compare.html)) | ||
* is then used to identify the index of `x`. The sort order of `buf` MUST be | ||
* compatible with that of `cmp`. | ||
* | ||
* @example | ||
* ```ts | ||
* binarySearch([2, 4, 6], 5); | ||
* // -3 | ||
* ``` | ||
* | ||
* @param buf - array | ||
* @param x - search value | ||
* @param key - key function | ||
* @param cmp - comparator | ||
* @param low - min index | ||
* @param high - max index | ||
*/ | ||
export const binarySearch = (buf, x, key = (x) => x, cmp = compare, low = 0, high = buf.length - 1) => { | ||
const kx = key(x); | ||
while (low <= high) { | ||
const mid = (low + high) >>> 1; | ||
const c = cmp(key(buf[mid]), kx); | ||
if (c < 0) { | ||
low = mid + 1; | ||
} | ||
else if (c > 0) { | ||
high = mid - 1; | ||
} | ||
else { | ||
return mid; | ||
} | ||
const binarySearch = (buf, x, key = (x2) => x2, cmp = compare, low = 0, high = buf.length - 1) => { | ||
const kx = key(x); | ||
while (low <= high) { | ||
const mid = low + high >>> 1; | ||
const c = cmp(key(buf[mid]), kx); | ||
if (c < 0) { | ||
low = mid + 1; | ||
} else if (c > 0) { | ||
high = mid - 1; | ||
} else { | ||
return mid; | ||
} | ||
return -low - 1; | ||
} | ||
return -low - 1; | ||
}; | ||
/** | ||
* Similar to {@link binarySearch}, but optimized for numeric arrays and | ||
* supporting custom comparators (default: | ||
* [`compareNumAsc()`](https://docs.thi.ng/umbrella/compare/functions/compareNumAsc.html)). | ||
* | ||
* @param buf - array | ||
* @param x - search value | ||
* @param cmp - comparator | ||
* @param low - min index | ||
* @param high - max index | ||
*/ | ||
export const binarySearchNumeric = (buf, x, cmp = compareNumAsc, low = 0, high = buf.length - 1) => { | ||
while (low <= high) { | ||
const mid = (low + high) >>> 1; | ||
const c = cmp(buf[mid], x); | ||
if (c < 0) { | ||
low = mid + 1; | ||
} | ||
else if (c > 0) { | ||
high = mid - 1; | ||
} | ||
else { | ||
return mid; | ||
} | ||
const binarySearchNumeric = (buf, x, cmp = compareNumAsc, low = 0, high = buf.length - 1) => { | ||
while (low <= high) { | ||
const mid = low + high >>> 1; | ||
const c = cmp(buf[mid], x); | ||
if (c < 0) { | ||
low = mid + 1; | ||
} else if (c > 0) { | ||
high = mid - 1; | ||
} else { | ||
return mid; | ||
} | ||
return -low - 1; | ||
} | ||
return -low - 1; | ||
}; | ||
export const binarySearch2 = (buf, x) => { | ||
let idx = buf[1] <= x ? 1 : 0; | ||
return buf[idx] === x ? idx : buf[0] < x ? -idx - 2 : -1; | ||
const binarySearch2 = (buf, x) => { | ||
let idx = buf[1] <= x ? 1 : 0; | ||
return buf[idx] === x ? idx : buf[0] < x ? -idx - 2 : -1; | ||
}; | ||
/** | ||
* Non-recursive, optimized binary search for fixed size numeric arrays of 4 | ||
* values. Returns index of `x` or `-index-1` if not found. | ||
* | ||
* @param buf - | ||
* @param x - | ||
*/ | ||
export const binarySearch4 = (buf, x) => { | ||
let idx = buf[2] <= x ? 2 : 0; | ||
idx |= buf[idx + 1] <= x ? 1 : 0; | ||
return buf[idx] === x ? idx : buf[0] < x ? -idx - 2 : -1; | ||
const binarySearch4 = (buf, x) => { | ||
let idx = buf[2] <= x ? 2 : 0; | ||
idx |= buf[idx + 1] <= x ? 1 : 0; | ||
return buf[idx] === x ? idx : buf[0] < x ? -idx - 2 : -1; | ||
}; | ||
/** | ||
* Non-recursive, optimized binary search for fixed size numeric arrays of 8 | ||
* values. Returns index of `x` or `-index-1` if not found. | ||
* | ||
* @param buf - | ||
* @param x - | ||
*/ | ||
export const binarySearch8 = (buf, x) => { | ||
let idx = buf[4] <= x ? 4 : 0; | ||
idx |= buf[idx + 2] <= x ? 2 : 0; | ||
idx |= buf[idx + 1] <= x ? 1 : 0; | ||
return buf[idx] === x ? idx : buf[0] < x ? -idx - 2 : -1; | ||
const binarySearch8 = (buf, x) => { | ||
let idx = buf[4] <= x ? 4 : 0; | ||
idx |= buf[idx + 2] <= x ? 2 : 0; | ||
idx |= buf[idx + 1] <= x ? 1 : 0; | ||
return buf[idx] === x ? idx : buf[0] < x ? -idx - 2 : -1; | ||
}; | ||
/** | ||
* Non-recursive, optimized binary search for fixed size numeric arrays of 16 | ||
* values. Returns index of `x` or `-index-1` if not found. | ||
* | ||
* @param buf - | ||
* @param x - | ||
*/ | ||
export const binarySearch16 = (buf, x) => { | ||
let idx = buf[8] <= x ? 8 : 0; | ||
idx |= buf[idx + 4] <= x ? 4 : 0; | ||
idx |= buf[idx + 2] <= x ? 2 : 0; | ||
idx |= buf[idx + 1] <= x ? 1 : 0; | ||
return buf[idx] === x ? idx : buf[0] < x ? -idx - 2 : -1; | ||
const binarySearch16 = (buf, x) => { | ||
let idx = buf[8] <= x ? 8 : 0; | ||
idx |= buf[idx + 4] <= x ? 4 : 0; | ||
idx |= buf[idx + 2] <= x ? 2 : 0; | ||
idx |= buf[idx + 1] <= x ? 1 : 0; | ||
return buf[idx] === x ? idx : buf[0] < x ? -idx - 2 : -1; | ||
}; | ||
/** | ||
* Non-recursive, optimized binary search for fixed size numeric arrays of 32 | ||
* values. Returns index of `x` or `-index-1` if not found. | ||
* | ||
* @param buf - | ||
* @param x - | ||
*/ | ||
export const binarySearch32 = (buf, x) => { | ||
let idx = buf[16] <= x ? 16 : 0; | ||
idx |= buf[idx + 4] <= x ? 8 : 0; | ||
idx |= buf[idx + 4] <= x ? 4 : 0; | ||
idx |= buf[idx + 2] <= x ? 2 : 0; | ||
idx |= buf[idx + 1] <= x ? 1 : 0; | ||
return buf[idx] === x ? idx : buf[0] < x ? -idx - 2 : -1; | ||
const binarySearch32 = (buf, x) => { | ||
let idx = buf[16] <= x ? 16 : 0; | ||
idx |= buf[idx + 4] <= x ? 8 : 0; | ||
idx |= buf[idx + 4] <= x ? 4 : 0; | ||
idx |= buf[idx + 2] <= x ? 2 : 0; | ||
idx |= buf[idx + 1] <= x ? 1 : 0; | ||
return buf[idx] === x ? idx : buf[0] < x ? -idx - 2 : -1; | ||
}; | ||
/** | ||
* {@link binarySearch} result index classifier for predecessor queries. | ||
* Returns index of last item less than search value or -1 if no such | ||
* values exist. | ||
* | ||
* @example | ||
* ```ts | ||
* bsLT(binarySearch([10, 20, 30, 40], 25)) | ||
* // 1 | ||
* ``` | ||
* | ||
* @param i - binarySearch result index | ||
*/ | ||
export const bsLT = (i) => (i < 0 ? -i - 2 : i - 1); | ||
/** | ||
* Similar to {@link bsLT}, but for less-than-equals queries. | ||
* | ||
* @param i - binarySearch result index | ||
*/ | ||
export const bsLE = (i) => (i < 0 ? -i - 2 : i); | ||
/** | ||
* {@link binarySearch} result index classifier for successor queries. | ||
* Returns index of first item greater than search value or -1 if no | ||
* such values exist. | ||
* | ||
* @example | ||
* ```ts | ||
* src = [10, 20, 30, 40]; | ||
* | ||
* bsGT(binarySearch(src, 25), src.length) | ||
* // 2 | ||
* | ||
* bsGT(binarySearch(src, 40), src.length) | ||
* // -1 | ||
* ``` | ||
* | ||
* @param i - binarySearch result index | ||
* @param n - array length | ||
*/ | ||
export const bsGT = (i, n) => ((i = i < 0 ? -i - 1 : i + 1), i < n ? i : -1); | ||
/** | ||
* Similar to {@link bsGT}, but for greater-than-equals queries. | ||
* | ||
* @param i - binarySearch result index | ||
* @param n - array length | ||
*/ | ||
export const bsGE = (i, n) => ((i = i < 0 ? -i - 1 : i), i < n ? i : -1); | ||
/** | ||
* {@link binarySearch} result index classifier for equals queries. | ||
* Merely syntax sugar, casting any non-found result indices to -1. | ||
* | ||
* @param i - binarySearch result index | ||
*/ | ||
export const bsEQ = (i) => (i < 0 ? -1 : i); | ||
const bsLT = (i) => i < 0 ? -i - 2 : i - 1; | ||
const bsLE = (i) => i < 0 ? -i - 2 : i; | ||
const bsGT = (i, n) => (i = i < 0 ? -i - 1 : i + 1, i < n ? i : -1); | ||
const bsGE = (i, n) => (i = i < 0 ? -i - 1 : i, i < n ? i : -1); | ||
const bsEQ = (i) => i < 0 ? -1 : i; | ||
export { | ||
binarySearch, | ||
binarySearch16, | ||
binarySearch2, | ||
binarySearch32, | ||
binarySearch4, | ||
binarySearch8, | ||
binarySearchNumeric, | ||
bsEQ, | ||
bsGE, | ||
bsGT, | ||
bsLE, | ||
bsLT | ||
}; |
@@ -1,23 +0,12 @@ | ||
/** | ||
* Splits array at given index (default: floor(src.length/2)) and returns tuple of [lhs, rhs]. | ||
* | ||
* @param src - | ||
* @param i - | ||
*/ | ||
export const bisect = (src, i = src.length >>> 1) => [ | ||
src.slice(0, i), | ||
src.slice(i), | ||
const bisect = (src, i = src.length >>> 1) => [ | ||
src.slice(0, i), | ||
src.slice(i) | ||
]; | ||
/** | ||
* Similar to {@link bisect}, but first finds split index via provided | ||
* predicate. The item for which the predicate first returns a truthy result, | ||
* will be the first item in the RHS array. If the predicate never succeeds, the | ||
* function returns `[src, []]`, i.e. all items will remain in the LHS. | ||
* | ||
* @param src - | ||
* @param pred - | ||
*/ | ||
export const bisectWith = (src, pred) => { | ||
const i = src.findIndex(pred); | ||
return i >= 0 ? bisect(src, i) : [src, []]; | ||
const bisectWith = (src, pred) => { | ||
const i = src.findIndex(pred); | ||
return i >= 0 ? bisect(src, i) : [src, []]; | ||
}; | ||
export { | ||
bisect, | ||
bisectWith | ||
}; |
63
blit.js
@@ -1,36 +0,39 @@ | ||
export function blit1d(dest, x, src, mask) { | ||
const [sx, sw, dx, dw] = __clip(0, src.length, x, dest.length); | ||
if (sw < 1 || dx >= dw) | ||
return dest; | ||
for (let i = 0; i < sw; i++) { | ||
const val = src[sx + i]; | ||
val !== mask && (dest[dx + i] = val); | ||
} | ||
function blit1d(dest, x, src, mask) { | ||
const [sx, sw, dx, dw] = __clip(0, src.length, x, dest.length); | ||
if (sw < 1 || dx >= dw) | ||
return dest; | ||
for (let i = 0; i < sw; i++) { | ||
const val = src[sx + i]; | ||
val !== mask && (dest[dx + i] = val); | ||
} | ||
return dest; | ||
} | ||
export function blit2d(dest, dpos, dsize, src, ssize, mask) { | ||
const [sx, sw, dx, dw] = __clip(0, ssize[0], dpos[0], dsize[0]); | ||
const [sy, sh, dy, dh] = __clip(0, ssize[1], dpos[1], dsize[1]); | ||
if (sw < 1 || sh < 1 || dx >= dw || dy >= dh) | ||
return dest; | ||
const sstride = ssize[0]; | ||
const dstride = dsize[0]; | ||
for (let y = 0; y < sh; y++) { | ||
for (let x = 0, soff = (sy + y) * sstride + sx, doff = (dy + y) * dstride + dx; x < sw; x++) { | ||
const val = src[soff + x]; | ||
val !== mask && (dest[doff + x] = val); | ||
} | ||
function blit2d(dest, dpos, dsize, src, ssize, mask) { | ||
const [sx, sw, dx, dw] = __clip(0, ssize[0], dpos[0], dsize[0]); | ||
const [sy, sh, dy, dh] = __clip(0, ssize[1], dpos[1], dsize[1]); | ||
if (sw < 1 || sh < 1 || dx >= dw || dy >= dh) | ||
return dest; | ||
const sstride = ssize[0]; | ||
const dstride = dsize[0]; | ||
for (let y = 0; y < sh; y++) { | ||
for (let x = 0, soff = (sy + y) * sstride + sx, doff = (dy + y) * dstride + dx; x < sw; x++) { | ||
const val = src[soff + x]; | ||
val !== mask && (dest[doff + x] = val); | ||
} | ||
return dest; | ||
} | ||
return dest; | ||
} | ||
const __clip = (sx, sw, dx, dw) => { | ||
if (dx < 0) { | ||
sx -= dx; | ||
sw += dx; | ||
dx = 0; | ||
} | ||
else if (dx + sw > dw) { | ||
sw = dw - dx; | ||
} | ||
return [sx, sw, dx, dw]; | ||
if (dx < 0) { | ||
sx -= dx; | ||
sw += dx; | ||
dx = 0; | ||
} else if (dx + sw > dw) { | ||
sw = dw - dx; | ||
} | ||
return [sx, sw, dx, dw]; | ||
}; | ||
export { | ||
blit1d, | ||
blit2d | ||
}; |
# Change Log | ||
- **Last updated**: 2023-12-09T19:12:03Z | ||
- **Last updated**: 2023-12-11T10:07:09Z | ||
- **Generator**: [thi.ng/monopub](https://thi.ng/monopub) | ||
@@ -5,0 +5,0 @@ |
import { equiv as _eq } from "@thi.ng/equiv"; | ||
/** | ||
* Returns true if the last items of `buf` are the same items as in `needle`. | ||
* | ||
* @remarks | ||
* This means `buf` should have at least the same length as `needle` for this to | ||
* be true. | ||
* | ||
* By default, uses | ||
* [`equiv()`](https://docs.thi.ng/umbrella/equiv/functions/equiv.html) for | ||
* equality checking. | ||
* | ||
* {@link startsWith} | ||
* | ||
* @param buf - array | ||
* @param needle - search values (array) | ||
* @param equiv - equivalence predicate | ||
*/ | ||
export const endsWith = (buf, needle, equiv = _eq) => { | ||
let i = buf.length; | ||
let j = needle.length; | ||
if (i < j) | ||
return false; | ||
while ((--i, j-- > 0 && equiv(buf[i], needle[j]))) { } | ||
return j < 0; | ||
const endsWith = (buf, needle, equiv = _eq) => { | ||
let i = buf.length; | ||
let j = needle.length; | ||
if (i < j) | ||
return false; | ||
while (--i, j-- > 0 && equiv(buf[i], needle[j])) { | ||
} | ||
return j < 0; | ||
}; | ||
export { | ||
endsWith | ||
}; |
import { isArray } from "@thi.ng/checks/is-array"; | ||
import { isArrayLike } from "@thi.ng/checks/is-arraylike"; | ||
import { ensureIterable } from "./ensure-iterable.js"; | ||
/** | ||
* Helper function to avoid unnecessary copying if `x` is already an | ||
* array. | ||
* | ||
* @remarks | ||
* First checks if `x` is an array and if so returns it. Else attempts | ||
* to obtain an iterator from `x` and if successful collects it as array | ||
* and returns it. Throws error if `x` isn't iterable. | ||
* | ||
* @param x - | ||
*/ | ||
export const ensureArray = (x) => isArray(x) ? x : [...ensureIterable(x)]; | ||
/** | ||
* Similar to {@link ensureArray}, but for `ArrayLike` types. | ||
* | ||
* {@link ensureArray} | ||
* | ||
* @param x - | ||
*/ | ||
export const ensureArrayLike = (x) => isArrayLike(x) ? x : [...ensureIterable(x)]; | ||
const ensureArray = (x) => isArray(x) ? x : [...ensureIterable(x)]; | ||
const ensureArrayLike = (x) => isArrayLike(x) ? x : [...ensureIterable(x)]; | ||
export { | ||
ensureArray, | ||
ensureArrayLike | ||
}; |
import { illegalArgs } from "@thi.ng/errors/illegal-arguments"; | ||
/** | ||
* Attempts to obtain an iterator from `x` and throws error if `x` is | ||
* not iterable. | ||
* | ||
* @param x - | ||
*/ | ||
export const ensureIterable = (x) => { | ||
(x == null || !x[Symbol.iterator]) && | ||
illegalArgs(`value is not iterable: ${x}`); | ||
return x; | ||
const ensureIterable = (x) => { | ||
(x == null || !x[Symbol.iterator]) && illegalArgs(`value is not iterable: ${x}`); | ||
return x; | ||
}; | ||
export { | ||
ensureIterable | ||
}; |
@@ -1,34 +0,13 @@ | ||
/** | ||
* Fills given array with values in [start .. end) interval from `index` | ||
* and with optional `step` size. | ||
* | ||
* @remarks | ||
* `start` and `end` default to 0 and array length, `step` defaults to 1 | ||
* or -1 (depending on range). Returns array. | ||
* | ||
* @example | ||
* ```ts | ||
* fillRange(new Array(5)) | ||
* // [ 0, 1, 2, 3, 4 ] | ||
* | ||
* fillRange(fillRange([], 0, 10, 20, 2), 5, 20, 8, -2) | ||
* // [ 10, 12, 14, 16, 18, 20, 18, 16, 14, 12, 10 ] | ||
* ``` | ||
* | ||
* @param buf - | ||
* @param index - | ||
* @param start - | ||
* @param end - | ||
* @param step - | ||
*/ | ||
export const fillRange = (buf, index = 0, start = 0, end = buf.length, step = end > start ? 1 : -1) => { | ||
if (step > 0) { | ||
for (; start < end; start += step) | ||
buf[index++] = start; | ||
} | ||
else { | ||
for (; start > end; start += step) | ||
buf[index++] = start; | ||
} | ||
return buf; | ||
const fillRange = (buf, index = 0, start = 0, end = buf.length, step = end > start ? 1 : -1) => { | ||
if (step > 0) { | ||
for (; start < end; start += step) | ||
buf[index++] = start; | ||
} else { | ||
for (; start > end; start += step) | ||
buf[index++] = start; | ||
} | ||
return buf; | ||
}; | ||
export { | ||
fillRange | ||
}; |
39
find.js
import { equiv as _equiv } from "@thi.ng/equiv"; | ||
/** | ||
* Similar to `Array.find()`, but uses [`equiv()`](https://docs.thi.ng/umbrella/equiv/functions/equiv.html) as | ||
* default predicate. | ||
* | ||
* @param buf - array | ||
* @param x - search value | ||
* @param equiv - equivalence predicate | ||
*/ | ||
export const find = (buf, x, equiv = _equiv) => { | ||
const i = findIndex(buf, x, equiv); | ||
return i !== -1 ? buf[i] : undefined; | ||
const find = (buf, x, equiv = _equiv) => { | ||
const i = findIndex(buf, x, equiv); | ||
return i !== -1 ? buf[i] : void 0; | ||
}; | ||
/** | ||
* Similar to `Array.findIndex()`, but uses | ||
* [`equiv()`](https://docs.thi.ng/umbrella/equiv/functions/equiv.html) as | ||
* default predicate. | ||
* | ||
* @param buf - array | ||
* @param x - search value | ||
* @param equiv - equivalence predicate | ||
*/ | ||
export const findIndex = (buf, x, equiv = _equiv) => { | ||
for (let i = buf.length; i-- > 0;) { | ||
if (equiv(x, buf[i])) | ||
return i; | ||
} | ||
return -1; | ||
const findIndex = (buf, x, equiv = _equiv) => { | ||
for (let i = buf.length; i-- > 0; ) { | ||
if (equiv(x, buf[i])) | ||
return i; | ||
} | ||
return -1; | ||
}; | ||
export { | ||
find, | ||
findIndex | ||
}; |
import { compare } from "@thi.ng/compare/compare"; | ||
import { swap } from "./swap.js"; | ||
/** | ||
* Implementation of the Floyd-Rivest selection algorithm to partially find & | ||
* sort the `k`th smallest elements in given array, according to supplied | ||
* comparator. | ||
* | ||
* @remarks | ||
* `k` is the desired index value, where `buf[k]` is the `(k+1)`th smallest | ||
* element when `left=0` (default). | ||
* | ||
* **IMPORTANT:** Mutates (partially sorts) given array such that all items in | ||
* the `[left, k]` interval are the smallest. | ||
* | ||
* @example | ||
* ```ts | ||
* floydRivest([5, 3, -1, -10, 20, 7, 0, 4, 2], 3); | ||
* // [ -10, 0, -1, 2, 3, 4, 5, 20, 7 ] | ||
* ``` | ||
* | ||
* Based on pseudo-code from: | ||
* - https://en.wikipedia.org/wiki/Floyd%E2%80%93Rivest_algorithm | ||
* | ||
* @param buf | ||
* @param k | ||
* @param cmp | ||
* @param left | ||
* @param right | ||
*/ | ||
export const floydRivest = (buf, k = 1, cmp = compare, left = 0, right = buf.length - 1) => { | ||
while (right > left) { | ||
// constants 600 & 0.5 are from original paper | ||
if (right - left > 600) { | ||
const n = right - left + 1; | ||
const i = k - left + 1; | ||
const z = Math.log(n); | ||
const s = 0.5 * Math.exp(z * (2 / 3)); | ||
const sd = 0.5 * Math.sqrt(z * s * ((n - s) / n)) * Math.sign(i - n / 2); | ||
floydRivest(buf, k, cmp, Math.max(left, (k - (i * s) / n + sd) | 0), Math.min(right, (k + ((n - i) * s) / n + sd) | 0)); | ||
} | ||
const t = buf[k]; | ||
let i = left; | ||
let j = right; | ||
swap(buf, left, k); | ||
if (cmp(buf[right], t) > 0) | ||
swap(buf, right, left); | ||
while (i < j) { | ||
swap(buf, i, j); | ||
i++; | ||
j--; | ||
while (compare(buf[i], t) < 0) | ||
i++; | ||
while (compare(buf[j], t) > 0) | ||
j--; | ||
} | ||
if (compare(buf[left], t) === 0) { | ||
swap(buf, left, j); | ||
} | ||
else { | ||
j++; | ||
swap(buf, j, right); | ||
} | ||
if (j <= k) | ||
left = j + 1; | ||
if (k <= j) | ||
right = j - 1; | ||
const floydRivest = (buf, k = 1, cmp = compare, left = 0, right = buf.length - 1) => { | ||
while (right > left) { | ||
if (right - left > 600) { | ||
const n = right - left + 1; | ||
const i2 = k - left + 1; | ||
const z = Math.log(n); | ||
const s = 0.5 * Math.exp(z * (2 / 3)); | ||
const sd = 0.5 * Math.sqrt(z * s * ((n - s) / n)) * Math.sign(i2 - n / 2); | ||
floydRivest( | ||
buf, | ||
k, | ||
cmp, | ||
Math.max(left, k - i2 * s / n + sd | 0), | ||
Math.min(right, k + (n - i2) * s / n + sd | 0) | ||
); | ||
} | ||
return buf; | ||
const t = buf[k]; | ||
let i = left; | ||
let j = right; | ||
swap(buf, left, k); | ||
if (cmp(buf[right], t) > 0) | ||
swap(buf, right, left); | ||
while (i < j) { | ||
swap(buf, i, j); | ||
i++; | ||
j--; | ||
while (compare(buf[i], t) < 0) | ||
i++; | ||
while (compare(buf[j], t) > 0) | ||
j--; | ||
} | ||
if (compare(buf[left], t) === 0) { | ||
swap(buf, left, j); | ||
} else { | ||
j++; | ||
swap(buf, j, right); | ||
} | ||
if (j <= k) | ||
left = j + 1; | ||
if (k <= j) | ||
right = j - 1; | ||
} | ||
return buf; | ||
}; | ||
export { | ||
floydRivest | ||
}; |
import { equiv as _eq } from "@thi.ng/equiv"; | ||
/** | ||
* Performs a fuzzy search of `query` in `domain` and returns `true` if | ||
* successful. | ||
* | ||
* @remarks | ||
* The optional `equiv` predicate can be used to customize item equality | ||
* checking. Uses | ||
* [`equiv()`](https://docs.thi.ng/umbrella/equiv/functions/equiv.html) by | ||
* default. | ||
* | ||
* Adapted and generalized from: https://github.com/bevacqua/fufuzzyzzysearch | ||
* (MIT licensed) | ||
* | ||
* [`filterFuzzy()`](https://docs.thi.ng/umbrella/transducers/functions/filterFuzzy.html) | ||
* | ||
* @param domain - array | ||
* @param query - search value | ||
* @param equiv - equivalence predicate | ||
*/ | ||
export const fuzzyMatch = (domain, query, equiv = _eq) => { | ||
const nd = domain.length; | ||
const nq = query.length; | ||
if (nq > nd) { | ||
return false; | ||
} | ||
if (nq === nd) { | ||
return equiv(query, domain); | ||
} | ||
next: for (let i = 0, j = 0; i < nq; i++) { | ||
const q = query[i]; | ||
while (j < nd) { | ||
if (equiv(domain[j++], q)) { | ||
continue next; | ||
} | ||
const fuzzyMatch = (domain, query, equiv = _eq) => { | ||
const nd = domain.length; | ||
const nq = query.length; | ||
if (nq > nd) { | ||
return false; | ||
} | ||
if (nq === nd) { | ||
return equiv(query, domain); | ||
} | ||
next: | ||
for (let i = 0, j = 0; i < nq; i++) { | ||
const q = query[i]; | ||
while (j < nd) { | ||
if (equiv(domain[j++], q)) { | ||
continue next; | ||
} | ||
return false; | ||
} | ||
return false; | ||
} | ||
return true; | ||
return true; | ||
}; | ||
export { | ||
fuzzyMatch | ||
}; |
@@ -1,35 +0,12 @@ | ||
/** | ||
* Inserts `x` into `buf` at index `i` and ensures that array length doesn't | ||
* grow beyond max `k` items (default: unbounded). | ||
* | ||
* @remarks | ||
* The function will have no effect iff `i<0` or `i>=k` or `k<1`. If | ||
* `buf.length` is larger than `k`, only the index range [i..k) will be | ||
* modified. | ||
* | ||
* In benchmarking with 4, 8, 16, 32, 64 element arrays, this function is | ||
* consistently 7-16x faster than `Array.prototype.copyWithin()` and 1.5-2x | ||
* faster than `Array.prototype.splice()` (for sizes < ~32). See | ||
* `/bench/insert.ts` | ||
* | ||
* @param buf - | ||
* @param x - | ||
* @param i - | ||
* @param k - | ||
*/ | ||
export const insert = (buf, x, i, k = Infinity) => i < 0 || i >= k || k < 1 ? buf : insertUnsafe(buf, x, i, k); | ||
/** | ||
* Same as {@link insert} but without any bounds/index checks. | ||
* | ||
* @param buf - | ||
* @param x - | ||
* @param i - | ||
* @param k - | ||
*/ | ||
export const insertUnsafe = (buf, x, i, k = Infinity) => { | ||
let j = buf.length < k ? buf.length + 1 : k; | ||
for (; --j > i;) | ||
buf[j] = buf[j - 1]; | ||
buf[i] = x; | ||
return buf; | ||
const insert = (buf, x, i, k = Infinity) => i < 0 || i >= k || k < 1 ? buf : insertUnsafe(buf, x, i, k); | ||
const insertUnsafe = (buf, x, i, k = Infinity) => { | ||
let j = buf.length < k ? buf.length + 1 : k; | ||
for (; --j > i; ) | ||
buf[j] = buf[j - 1]; | ||
buf[i] = x; | ||
return buf; | ||
}; | ||
export { | ||
insert, | ||
insertUnsafe | ||
}; |
25
into.js
@@ -1,16 +0,11 @@ | ||
/** | ||
* Appends `max` items (default: all) from `src` iterable to `dest` array. | ||
* Returns `dest`. | ||
* | ||
* @param dest - | ||
* @param src - | ||
* @param max - | ||
*/ | ||
export const into = (dest, src, max = Infinity) => { | ||
for (let x of src) { | ||
if (--max < 0) | ||
break; | ||
dest.push(x); | ||
} | ||
return dest; | ||
const into = (dest, src, max = Infinity) => { | ||
for (let x of src) { | ||
if (--max < 0) | ||
break; | ||
dest.push(x); | ||
} | ||
return dest; | ||
}; | ||
export { | ||
into | ||
}; |
import { compare } from "@thi.ng/compare/compare"; | ||
/** | ||
* Returns true if the given array and its elements in the selected index range | ||
* (entire array, by default) are in the order defined by the given comparator | ||
* ([`compare()`](https://docs.thi.ng/umbrella/compare/functions/compare.html) | ||
* by default). | ||
* | ||
* @remarks | ||
* Always returns true, if effective index range (or array length) has less than | ||
* two elements. No bounds checking. | ||
* | ||
* @example | ||
* ```ts | ||
* isSorted([3, 2, 1]) | ||
* // false | ||
* | ||
* // w/ custom comparator | ||
* isSorted([3, 2, 1], (a, b) => b - a) | ||
* // true | ||
* ``` | ||
* | ||
* @param arr - array | ||
* @param cmp - comparator | ||
* @param start - start index | ||
* @param end - end index | ||
*/ | ||
export const isSorted = (arr, cmp = compare, start = 0, end = arr.length) => { | ||
let prev = arr[start]; | ||
while (++start < end) { | ||
const curr = arr[start]; | ||
if (cmp(prev, curr) > 0) | ||
return false; | ||
prev = curr; | ||
} | ||
return true; | ||
const isSorted = (arr, cmp = compare, start = 0, end = arr.length) => { | ||
let prev = arr[start]; | ||
while (++start < end) { | ||
const curr = arr[start]; | ||
if (cmp(prev, curr) > 0) | ||
return false; | ||
prev = curr; | ||
} | ||
return true; | ||
}; | ||
export { | ||
isSorted | ||
}; |
@@ -1,23 +0,13 @@ | ||
/** | ||
* Returns iterator of nullable array w/ optional index range support | ||
* and/or reverse iteration order. The default range covers the entire | ||
* array. | ||
* | ||
* @remarks | ||
* If `start` > `end`, yields values in reverse order. No bounds | ||
* checking is performed. | ||
* | ||
* @param buf - array or null | ||
* @param start - start index | ||
* @param end - end index (excluded) | ||
*/ | ||
export function* arrayIterator(buf, start = 0, end) { | ||
if (!buf) | ||
return; | ||
start = start; | ||
end === undefined && (end = buf.length); | ||
const step = start <= end ? 1 : -1; | ||
for (; start !== end; start += step) { | ||
yield buf[start]; | ||
} | ||
function* arrayIterator(buf, start = 0, end) { | ||
if (!buf) | ||
return; | ||
start = start; | ||
end === void 0 && (end = buf.length); | ||
const step = start <= end ? 1 : -1; | ||
for (; start !== end; start += step) { | ||
yield buf[start]; | ||
} | ||
} | ||
export { | ||
arrayIterator | ||
}; |
const eqStrict = (a, b) => a === b; | ||
/** | ||
* Computes Levenshtein distance w/ optionally given `maxDist` (for | ||
* early termination, default: ∞) and equality predicate (default: | ||
* `===`). Returns 0 if both `a` and `b` are equal (based on predicate). | ||
* Returns `Infinity` if actual distance > `maxDist`. | ||
* | ||
* @remarks | ||
* | ||
* Based on: | ||
* - https://en.wikipedia.org/wiki/Levenshtein_distance | ||
* - https://en.wikipedia.org/wiki/Wagner%E2%80%93Fischer_algorithm | ||
* - https://github.com/gustf/js-levenshtein/blob/develop/index.js | ||
* | ||
* @example | ||
* ```ts | ||
* levenshtein([1, 2, 3, 4, 5], [1, 2, 4, 3, 5]); | ||
* // 2 | ||
* | ||
* levenshtein( | ||
* [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 }], | ||
* [{ id: 4 }, { id: 5 }, { id: 3 }, { id: 1 }, { id: 2 }], | ||
* // max dist | ||
* 2, | ||
* // predicate | ||
* (a, b) => a.id === b.id | ||
* ) | ||
* // Infinity | ||
* ``` | ||
* | ||
* @param a - | ||
* @param b - | ||
* @param maxDist - | ||
* @param equiv - | ||
*/ | ||
export const levenshtein = (a, b, maxDist = Infinity, equiv = eqStrict) => { | ||
if (a === b) { | ||
return 0; | ||
const levenshtein = (a, b, maxDist = Infinity, equiv = eqStrict) => { | ||
if (a === b) { | ||
return 0; | ||
} | ||
if (a.length > b.length) { | ||
const tmp = a; | ||
a = b; | ||
b = tmp; | ||
} | ||
let la = a.length; | ||
let lb = b.length; | ||
while (la > 0 && equiv(a[~-la], b[~-lb])) { | ||
la--; | ||
lb--; | ||
} | ||
let offset = 0; | ||
while (offset < la && equiv(a[offset], b[offset])) { | ||
offset++; | ||
} | ||
la -= offset; | ||
lb -= offset; | ||
if (la === 0 || lb < 3) { | ||
return lb; | ||
} | ||
let x = 0; | ||
let y; | ||
let minDist; | ||
let d0; | ||
let d1; | ||
let d2; | ||
let d3; | ||
let dd; | ||
let dy; | ||
let ay; | ||
let bx0; | ||
let bx1; | ||
let bx2; | ||
let bx3; | ||
const _min = (d02, d12, d22, bx, ay2) => { | ||
return d02 < d12 || d22 < d12 ? d02 > d22 ? d22 + 1 : d02 + 1 : equiv(ay2, bx) ? d12 : d12 + 1; | ||
}; | ||
const vector = []; | ||
for (y = 0; y < la; y++) { | ||
vector.push(y + 1, a[offset + y]); | ||
} | ||
const len = vector.length - 1; | ||
const lb3 = lb - 3; | ||
for (; x < lb3; ) { | ||
bx0 = b[offset + (d0 = x)]; | ||
bx1 = b[offset + (d1 = x + 1)]; | ||
bx2 = b[offset + (d2 = x + 2)]; | ||
bx3 = b[offset + (d3 = x + 3)]; | ||
dd = x += 4; | ||
minDist = Infinity; | ||
for (y = 0; y < len; y += 2) { | ||
dy = vector[y]; | ||
ay = vector[y + 1]; | ||
d0 = _min(dy, d0, d1, bx0, ay); | ||
d1 = _min(d0, d1, d2, bx1, ay); | ||
d2 = _min(d1, d2, d3, bx2, ay); | ||
dd = _min(d2, d3, dd, bx3, ay); | ||
dd < minDist && (minDist = dd); | ||
vector[y] = dd; | ||
d3 = d2; | ||
d2 = d1; | ||
d1 = d0; | ||
d0 = dy; | ||
} | ||
if (a.length > b.length) { | ||
const tmp = a; | ||
a = b; | ||
b = tmp; | ||
if (minDist > maxDist) | ||
return Infinity; | ||
} | ||
for (; x < lb; ) { | ||
bx0 = b[offset + (d0 = x)]; | ||
dd = ++x; | ||
minDist = Infinity; | ||
for (y = 0; y < len; y += 2) { | ||
dy = vector[y]; | ||
vector[y] = dd = _min(dy, d0, dd, bx0, vector[y + 1]); | ||
dd < minDist && (minDist = dd); | ||
d0 = dy; | ||
} | ||
let la = a.length; | ||
let lb = b.length; | ||
while (la > 0 && equiv(a[~-la], b[~-lb])) { | ||
la--; | ||
lb--; | ||
} | ||
let offset = 0; | ||
while (offset < la && equiv(a[offset], b[offset])) { | ||
offset++; | ||
} | ||
la -= offset; | ||
lb -= offset; | ||
if (la === 0 || lb < 3) { | ||
return lb; | ||
} | ||
let x = 0; | ||
let y; | ||
let minDist; | ||
let d0; | ||
let d1; | ||
let d2; | ||
let d3; | ||
let dd; | ||
let dy; | ||
let ay; | ||
let bx0; | ||
let bx1; | ||
let bx2; | ||
let bx3; | ||
const _min = (d0, d1, d2, bx, ay) => { | ||
return d0 < d1 || d2 < d1 | ||
? d0 > d2 | ||
? d2 + 1 | ||
: d0 + 1 | ||
: equiv(ay, bx) | ||
? d1 | ||
: d1 + 1; | ||
}; | ||
const vector = []; | ||
for (y = 0; y < la; y++) { | ||
vector.push(y + 1, a[offset + y]); | ||
} | ||
const len = vector.length - 1; | ||
const lb3 = lb - 3; | ||
for (; x < lb3;) { | ||
bx0 = b[offset + (d0 = x)]; | ||
bx1 = b[offset + (d1 = x + 1)]; | ||
bx2 = b[offset + (d2 = x + 2)]; | ||
bx3 = b[offset + (d3 = x + 3)]; | ||
dd = x += 4; | ||
minDist = Infinity; | ||
for (y = 0; y < len; y += 2) { | ||
dy = vector[y]; | ||
ay = vector[y + 1]; | ||
d0 = _min(dy, d0, d1, bx0, ay); | ||
d1 = _min(d0, d1, d2, bx1, ay); | ||
d2 = _min(d1, d2, d3, bx2, ay); | ||
dd = _min(d2, d3, dd, bx3, ay); | ||
dd < minDist && (minDist = dd); | ||
vector[y] = dd; | ||
d3 = d2; | ||
d2 = d1; | ||
d1 = d0; | ||
d0 = dy; | ||
} | ||
if (minDist > maxDist) | ||
return Infinity; | ||
} | ||
for (; x < lb;) { | ||
bx0 = b[offset + (d0 = x)]; | ||
dd = ++x; | ||
minDist = Infinity; | ||
for (y = 0; y < len; y += 2) { | ||
dy = vector[y]; | ||
vector[y] = dd = _min(dy, d0, dd, bx0, vector[y + 1]); | ||
dd < minDist && (minDist = dd); | ||
d0 = dy; | ||
} | ||
if (minDist > maxDist) | ||
return Infinity; | ||
} | ||
return dd; | ||
if (minDist > maxDist) | ||
return Infinity; | ||
} | ||
return dd; | ||
}; | ||
/** | ||
* Normalized version of {@link levenshtein}, i.e. the actual L-dist | ||
* divided by the length of the longest input (or `Infinity` if actual | ||
* distance > `maxDist`). | ||
* | ||
* @param a - | ||
* @param b - | ||
* @param maxDist - | ||
* @param equiv - | ||
*/ | ||
export const normalizedLevenshtein = (a, b, maxDist = Infinity, equiv = eqStrict) => { | ||
const n = Math.max(a.length, b.length); | ||
return n > 0 ? levenshtein(a, b, maxDist, equiv) / n : 0; | ||
const normalizedLevenshtein = (a, b, maxDist = Infinity, equiv = eqStrict) => { | ||
const n = Math.max(a.length, b.length); | ||
return n > 0 ? levenshtein(a, b, maxDist, equiv) / n : 0; | ||
}; | ||
export { | ||
levenshtein, | ||
normalizedLevenshtein | ||
}; |
{ | ||
"name": "@thi.ng/arrays", | ||
"version": "2.7.7", | ||
"version": "2.7.8", | ||
"description": "Array / Arraylike utilities", | ||
@@ -27,3 +27,5 @@ "type": "module", | ||
"scripts": { | ||
"build": "yarn clean && tsc --declaration", | ||
"build": "yarn build:esbuild && yarn build:decl", | ||
"build:decl": "tsc --declaration --emitDeclarationOnly", | ||
"build:esbuild": "esbuild --format=esm --platform=neutral --target=es2022 --tsconfig=tsconfig.json --outdir=. src/**/*.ts", | ||
"clean": "rimraf --glob '*.js' '*.d.ts' '*.map' doc", | ||
@@ -37,11 +39,12 @@ "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", | ||
"dependencies": { | ||
"@thi.ng/api": "^8.9.11", | ||
"@thi.ng/checks": "^3.4.11", | ||
"@thi.ng/compare": "^2.2.7", | ||
"@thi.ng/equiv": "^2.1.36", | ||
"@thi.ng/errors": "^2.4.5", | ||
"@thi.ng/random": "^3.6.17" | ||
"@thi.ng/api": "^8.9.12", | ||
"@thi.ng/checks": "^3.4.12", | ||
"@thi.ng/compare": "^2.2.8", | ||
"@thi.ng/equiv": "^2.1.37", | ||
"@thi.ng/errors": "^2.4.6", | ||
"@thi.ng/random": "^3.6.18" | ||
}, | ||
"devDependencies": { | ||
"@microsoft/api-extractor": "^7.38.3", | ||
"esbuild": "^0.19.8", | ||
"rimraf": "^5.0.5", | ||
@@ -168,3 +171,3 @@ "tools": "^0.0.1", | ||
}, | ||
"gitHead": "25f2ac8ff795a432a930119661b364d4d93b59a0\n" | ||
"gitHead": "5e7bafedfc3d53bc131469a28de31dd8e5b4a3ff\n" | ||
} |
18
peek.js
@@ -1,12 +0,6 @@ | ||
/** | ||
* Returns first element of given array or `undefined` if array is empty. | ||
* | ||
* @param buf - array | ||
*/ | ||
export const first = (buf) => buf[0]; | ||
/** | ||
* Returns last element of given array or `undefined` if array is empty. | ||
* | ||
* @param buf - array | ||
*/ | ||
export const peek = (buf) => buf[buf.length - 1]; | ||
const first = (buf) => buf[0]; | ||
const peek = (buf) => buf[buf.length - 1]; | ||
export { | ||
first, | ||
peek | ||
}; |
import { compare } from "@thi.ng/compare/compare"; | ||
import { swap } from "./swap.js"; | ||
export function quickSort(arr, _cmp = compare, _swap = swap, start = 0, end = arr.length - 1) { | ||
if (start < end) { | ||
const pivot = arr[start + ((end - start) >> 1)]; | ||
let s = start - 1; | ||
let e = end + 1; | ||
while (true) { | ||
do { | ||
s++; | ||
} while (_cmp(arr[s], pivot) < 0); | ||
do { | ||
e--; | ||
} while (_cmp(arr[e], pivot) > 0); | ||
if (s >= e) | ||
break; | ||
_swap(arr, s, e); | ||
} | ||
quickSort(arr, _cmp, _swap, start, e); | ||
quickSort(arr, _cmp, _swap, e + 1, end); | ||
function quickSort(arr, _cmp = compare, _swap = swap, start = 0, end = arr.length - 1) { | ||
if (start < end) { | ||
const pivot = arr[start + (end - start >> 1)]; | ||
let s = start - 1; | ||
let e = end + 1; | ||
while (true) { | ||
do { | ||
s++; | ||
} while (_cmp(arr[s], pivot) < 0); | ||
do { | ||
e--; | ||
} while (_cmp(arr[e], pivot) > 0); | ||
if (s >= e) | ||
break; | ||
_swap(arr, s, e); | ||
} | ||
return arr; | ||
quickSort(arr, _cmp, _swap, start, e); | ||
quickSort(arr, _cmp, _swap, e + 1, end); | ||
} | ||
return arr; | ||
} | ||
export { | ||
quickSort | ||
}; |
@@ -1,45 +0,29 @@ | ||
/** | ||
* Rotates array by `num` items. If `num < 0` items are rotated left (towards | ||
* the beginning of the array), otherwise to the right (end). The rotation | ||
* distance will be `num % buf.length`. The function is no-op if the resulting | ||
* distance is zero or `buf` is empty. | ||
* | ||
* @remarks | ||
* Not suitable for typed arrays. Use {@link rotateTyped} for those. | ||
* | ||
* @param buf | ||
* @param num | ||
*/ | ||
export const rotate = (buf, num) => { | ||
if (!(num = __distance(buf, num))) | ||
return buf; | ||
if (num < 0) { | ||
buf.push(...buf.splice(0, -num)); | ||
} | ||
else { | ||
buf.unshift(...buf.splice(-num)); | ||
} | ||
const rotate = (buf, num) => { | ||
if (!(num = __distance(buf, num))) | ||
return buf; | ||
if (num < 0) { | ||
buf.push(...buf.splice(0, -num)); | ||
} else { | ||
buf.unshift(...buf.splice(-num)); | ||
} | ||
return buf; | ||
}; | ||
/** | ||
* Same as {@link rotate}, but for optimized for typed arrays! | ||
* | ||
* @param buf | ||
* @param num | ||
*/ | ||
export const rotateTyped = (buf, num) => { | ||
if (!(num = __distance(buf, num))) | ||
return buf; | ||
if (num < 0) { | ||
const tmp = buf.slice(0, -num); | ||
buf.copyWithin(0, -num); | ||
buf.set(tmp, buf.length + num); | ||
} | ||
else if (num > 0) { | ||
const tmp = buf.slice(buf.length - num); | ||
buf.copyWithin(num, 0); | ||
buf.set(tmp, 0); | ||
} | ||
const rotateTyped = (buf, num) => { | ||
if (!(num = __distance(buf, num))) | ||
return buf; | ||
if (num < 0) { | ||
const tmp = buf.slice(0, -num); | ||
buf.copyWithin(0, -num); | ||
buf.set(tmp, buf.length + num); | ||
} else if (num > 0) { | ||
const tmp = buf.slice(buf.length - num); | ||
buf.copyWithin(num, 0); | ||
buf.set(tmp, 0); | ||
} | ||
return buf; | ||
}; | ||
const __distance = (buf, num) => buf.length ? (num | 0) % buf.length : 0; | ||
export { | ||
rotate, | ||
rotateTyped | ||
}; |
import { assert } from "@thi.ng/errors/assert"; | ||
import { SYSTEM } from "@thi.ng/random/system"; | ||
/** | ||
* Shuffles the items in the given index range of array `buf` using Fisher-yates | ||
* and optional `rnd` PRNG. | ||
* | ||
* @remarks | ||
* If neither `start` / `end` are given, the entire array will be shuffled. | ||
* Mutates original array. | ||
* | ||
* See [`IRandom`](https://docs.thi.ng/umbrella/random/interfaces/IRandom.html) | ||
* | ||
* @param buf - array | ||
* @param n - num items | ||
* @param rnd - PRNG | ||
*/ | ||
export const shuffleRange = (buf, start = 0, end = buf.length, rnd = SYSTEM) => { | ||
assert(start >= 0 && end >= start && end <= buf.length, `illegal range ${start}..${end}`); | ||
if (end - start > 1) { | ||
for (let i = end; i-- > start;) { | ||
const a = rnd.minmax(start, i + 1) | 0; | ||
const t = buf[a]; | ||
buf[a] = buf[i]; | ||
buf[i] = t; | ||
} | ||
const shuffleRange = (buf, start = 0, end = buf.length, rnd = SYSTEM) => { | ||
assert( | ||
start >= 0 && end >= start && end <= buf.length, | ||
`illegal range ${start}..${end}` | ||
); | ||
if (end - start > 1) { | ||
for (let i = end; i-- > start; ) { | ||
const a = rnd.minmax(start, i + 1) | 0; | ||
const t = buf[a]; | ||
buf[a] = buf[i]; | ||
buf[i] = t; | ||
} | ||
return buf; | ||
} | ||
return buf; | ||
}; | ||
/** | ||
* Applies {@link shuffleRange} to the given array. If `n` is given, | ||
* only the first `n` items are shuffled. Mutates original array. | ||
* | ||
* {@link shuffleRange} | ||
* | ||
* @param buf - array | ||
* @param n - num items | ||
* @param rnd - PRNG | ||
*/ | ||
export const shuffle = (buf, n = buf.length, rnd = SYSTEM) => shuffleRange(buf, 0, n, rnd); | ||
const shuffle = (buf, n = buf.length, rnd = SYSTEM) => shuffleRange(buf, 0, n, rnd); | ||
export { | ||
shuffle, | ||
shuffleRange | ||
}; |
@@ -6,34 +6,10 @@ import { isFunction } from "@thi.ng/checks/is-function"; | ||
import { multiSwap } from "./swap.js"; | ||
/** | ||
* Takes a `src` array and `key` array of function to provide the sort key of | ||
* each item. If a function, it will be first applied to pre-compute a new array | ||
* of all sort keys. Then uses {@link quickSort} to sort `src` array, based on | ||
* the ordering of cached keys and the optionally given comparator. Returns | ||
* `src`. | ||
* | ||
* @remarks | ||
* This function is primarily intended for use cases where an array needs to be | ||
* sorted based on the item order of another array, or where sort keys are | ||
* derived from non-trivial computations and need to be cached, rather than be | ||
* re-evaluated multiple times from within a comparator. | ||
* | ||
* @example | ||
* ```ts | ||
* // sort by length in descending order | ||
* sortByCachedKey(["a","bbbb","ccc","dd"], (x) => x.length, (a, b) => b - a); | ||
* // [ 'bbbb', 'ccc', 'dd', 'a' ] | ||
* | ||
* sortByCachedKey(["a", "b", "c", "d"], [3, 2, 1, 0]) | ||
* // [ 'd', 'c', 'b', 'a' ] | ||
* ``` | ||
* | ||
* @param src - | ||
* @param key - | ||
* @param cmp - | ||
*/ | ||
export const sortByCachedKey = (src, key, cmp = compare) => { | ||
const keys = isFunction(key) ? src.map(key) : key; | ||
assert(keys.length === src.length, `keys.length != src.length`); | ||
quickSort(keys, cmp, multiSwap(src)); | ||
return src; | ||
const sortByCachedKey = (src, key, cmp = compare) => { | ||
const keys = isFunction(key) ? src.map(key) : key; | ||
assert(keys.length === src.length, `keys.length != src.length`); | ||
quickSort(keys, cmp, multiSwap(src)); | ||
return src; | ||
}; | ||
export { | ||
sortByCachedKey | ||
}; |
import { equiv as _eq } from "@thi.ng/equiv"; | ||
/** | ||
* Returns true if the first items of `buf` are the same items as in `needle`. | ||
* | ||
* @remarks | ||
* This means `buf` should have at least the same length as `needle` for this to | ||
* be true. | ||
* | ||
* By default, uses | ||
* [`equiv()`](https://docs.thi.ng/umbrella/equiv/functions/equiv.html) for | ||
* equality checking. | ||
* | ||
* {@link endsWith} | ||
* | ||
* @param buf - array | ||
* @param needle - search value | ||
* @param equiv - equivalence predicate | ||
*/ | ||
export const startsWith = (buf, needle, equiv = _eq) => { | ||
let i = buf.length; | ||
let j = needle.length; | ||
if (i < j) | ||
return false; | ||
while (-j >= 0 && equiv(buf[j], needle[j])) { } | ||
return j < 0; | ||
const startsWith = (buf, needle, equiv = _eq) => { | ||
let i = buf.length; | ||
let j = needle.length; | ||
if (i < j) | ||
return false; | ||
while (-j >= 0 && equiv(buf[j], needle[j])) { | ||
} | ||
return j < 0; | ||
}; | ||
export { | ||
startsWith | ||
}; |
110
swap.js
@@ -1,73 +0,41 @@ | ||
/** | ||
* Swaps values at index `x`/`y` in given array. | ||
* | ||
* @param arr - array | ||
* @param x - first index | ||
* @param y - other index | ||
*/ | ||
export const swap = (arr, x, y) => { | ||
const t = arr[x]; | ||
arr[x] = arr[y]; | ||
arr[y] = t; | ||
const swap = (arr, x, y) => { | ||
const t = arr[x]; | ||
arr[x] = arr[y]; | ||
arr[y] = t; | ||
}; | ||
/** | ||
* Higher-order version of {@link swap} for swapping elements in multiple arrays | ||
* at once and hence useful for sorting multiple arrays based on a single | ||
* criteria. | ||
* | ||
* @remarks | ||
* The returned function takes the same args as `swap`, and when called swaps 2 | ||
* elements in the array given to that function AND in the arrays given to | ||
* {@link multiSwap} itself. Provides fast routes for up to 3 extra arrays, then | ||
* falls back to a loop-based approach. | ||
* | ||
* {@link quickSort} | ||
* | ||
* @example | ||
* ```ts | ||
* a = [2, 1]; | ||
* b = [20, 10]; | ||
* c = [40, 30]; | ||
* | ||
* ms = multiSwap(b, c); | ||
* ms(a, 0, 1); | ||
* | ||
* // a: [1, 2] | ||
* // b: [10, 20] | ||
* // c: [30, 40] | ||
* ``` | ||
* | ||
* @param xs - arrays to swap in later | ||
*/ | ||
export const multiSwap = (...xs) => { | ||
const [b, c, d] = xs; | ||
const n = xs.length; | ||
switch (n) { | ||
case 0: | ||
return swap; | ||
case 1: | ||
return (a, x, y) => { | ||
swap(a, x, y); | ||
swap(b, x, y); | ||
}; | ||
case 2: | ||
return (a, x, y) => { | ||
swap(a, x, y); | ||
swap(b, x, y); | ||
swap(c, x, y); | ||
}; | ||
case 3: | ||
return (a, x, y) => { | ||
swap(a, x, y); | ||
swap(b, x, y); | ||
swap(c, x, y); | ||
swap(d, x, y); | ||
}; | ||
default: | ||
return (a, x, y) => { | ||
swap(a, x, y); | ||
for (let i = n; i-- > 0;) | ||
swap(xs[i], x, y); | ||
}; | ||
} | ||
const multiSwap = (...xs) => { | ||
const [b, c, d] = xs; | ||
const n = xs.length; | ||
switch (n) { | ||
case 0: | ||
return swap; | ||
case 1: | ||
return (a, x, y) => { | ||
swap(a, x, y); | ||
swap(b, x, y); | ||
}; | ||
case 2: | ||
return (a, x, y) => { | ||
swap(a, x, y); | ||
swap(b, x, y); | ||
swap(c, x, y); | ||
}; | ||
case 3: | ||
return (a, x, y) => { | ||
swap(a, x, y); | ||
swap(b, x, y); | ||
swap(c, x, y); | ||
swap(d, x, y); | ||
}; | ||
default: | ||
return (a, x, y) => { | ||
swap(a, x, y); | ||
for (let i = n; i-- > 0; ) | ||
swap(xs[i], x, y); | ||
}; | ||
} | ||
}; | ||
export { | ||
multiSwap, | ||
swap | ||
}; |
@@ -1,56 +0,34 @@ | ||
/** | ||
* Returns optimized function to immutably select, repeat, reshape and / | ||
* or reorder array / object values in the specified index order. | ||
* | ||
* @remarks | ||
* Fast paths for up to 8 indices are provided, before a loop based | ||
* approach is used. | ||
* | ||
* @example | ||
* ```ts | ||
* swizzle([0, 0, 0])([1, 2, 3, 4]) // [ 1, 1, 1 ] | ||
* swizzle([1, 1, 3, 3])([1, 2, 3, 4]) // [ 2, 2, 4, 4 ] | ||
* swizzle([2, 0])([1, 2, 3]) // [ 3, 1 ] | ||
* ``` | ||
* | ||
* @example | ||
* Objects can be used as input to the generated function, but the | ||
* result will always be in array form. | ||
* ```ts | ||
* swizzle(["a", "c", "b"])({a: 1, b: 2, c: 3}) // [ 1, 3, 2 ] | ||
* ``` | ||
* | ||
* @param order - indices | ||
*/ | ||
export const swizzle = (order) => { | ||
const [a, b, c, d, e, f, g, h] = order; | ||
switch (order.length) { | ||
case 0: | ||
return () => []; | ||
case 1: | ||
return (x) => [x[a]]; | ||
case 2: | ||
return (x) => [x[a], x[b]]; | ||
case 3: | ||
return (x) => [x[a], x[b], x[c]]; | ||
case 4: | ||
return (x) => [x[a], x[b], x[c], x[d]]; | ||
case 5: | ||
return (x) => [x[a], x[b], x[c], x[d], x[e]]; | ||
case 6: | ||
return (x) => [x[a], x[b], x[c], x[d], x[e], x[f]]; | ||
case 7: | ||
return (x) => [x[a], x[b], x[c], x[d], x[e], x[f], x[g]]; | ||
case 8: | ||
return (x) => [x[a], x[b], x[c], x[d], x[e], x[f], x[g], x[h]]; | ||
default: | ||
return (x) => { | ||
const res = []; | ||
for (let i = order.length; i-- > 0;) { | ||
res[i] = x[order[i]]; | ||
} | ||
return res; | ||
}; | ||
} | ||
const swizzle = (order) => { | ||
const [a, b, c, d, e, f, g, h] = order; | ||
switch (order.length) { | ||
case 0: | ||
return () => []; | ||
case 1: | ||
return (x) => [x[a]]; | ||
case 2: | ||
return (x) => [x[a], x[b]]; | ||
case 3: | ||
return (x) => [x[a], x[b], x[c]]; | ||
case 4: | ||
return (x) => [x[a], x[b], x[c], x[d]]; | ||
case 5: | ||
return (x) => [x[a], x[b], x[c], x[d], x[e]]; | ||
case 6: | ||
return (x) => [x[a], x[b], x[c], x[d], x[e], x[f]]; | ||
case 7: | ||
return (x) => [x[a], x[b], x[c], x[d], x[e], x[f], x[g]]; | ||
case 8: | ||
return (x) => [x[a], x[b], x[c], x[d], x[e], x[f], x[g], x[h]]; | ||
default: | ||
return (x) => { | ||
const res = []; | ||
for (let i = order.length; i-- > 0; ) { | ||
res[i] = x[order[i]]; | ||
} | ||
return res; | ||
}; | ||
} | ||
}; | ||
export { | ||
swizzle | ||
}; |
@@ -1,50 +0,18 @@ | ||
/** | ||
* Higher order function. Takes an object of threshold values and their target | ||
* values, as well as a default value. Returns a new function which then matches | ||
* a given value against all given thresholds and returns a matching target | ||
* value, of (if none matched), the given default. | ||
* | ||
* @remarks | ||
* The thresholds will be sorted & matched in ascending order using `<=` | ||
* comparison. | ||
* | ||
* @example | ||
* ```ts | ||
* const numColumns = selectThresholdMin({ 480: 1, 640: 2, 960: 3 }, 4); | ||
* | ||
* numColumns(320) // 1 | ||
* | ||
* numColumns(481) // 2 | ||
* | ||
* numColumns(768) // 3 | ||
* | ||
* numColumns(1024) // 4 | ||
* ``` | ||
* | ||
* @param thresholds | ||
* @param defaultVal | ||
*/ | ||
export const selectThresholdMin = (thresholds, defaultVal) => { | ||
const $thresholds = Object.entries(thresholds) | ||
.map(([k, v]) => [+k, v]) | ||
.sort((a, b) => a[0] - b[0]); | ||
return (x) => { | ||
const res = $thresholds.find((t) => x <= t[0]); | ||
return res ? res[1] : defaultVal; | ||
}; | ||
const selectThresholdMin = (thresholds, defaultVal) => { | ||
const $thresholds = Object.entries(thresholds).map(([k, v]) => [+k, v]).sort((a, b) => a[0] - b[0]); | ||
return (x) => { | ||
const res = $thresholds.find((t) => x <= t[0]); | ||
return res ? res[1] : defaultVal; | ||
}; | ||
}; | ||
/** | ||
* Similar to {@link selectThresholdMin}, but uses `>=` ordering. | ||
* | ||
* @param thresholds | ||
* @param defaultVal | ||
*/ | ||
export const selectThresholdMax = (thresholds, defaultVal) => { | ||
const $thresholds = Object.entries(thresholds) | ||
.map(([k, v]) => [+k, v]) | ||
.sort((a, b) => b[0] - a[0]); | ||
return (x) => { | ||
const res = $thresholds.find((t) => x >= t[0]); | ||
return res ? res[1] : defaultVal; | ||
}; | ||
const selectThresholdMax = (thresholds, defaultVal) => { | ||
const $thresholds = Object.entries(thresholds).map(([k, v]) => [+k, v]).sort((a, b) => b[0] - a[0]); | ||
return (x) => { | ||
const res = $thresholds.find((t) => x >= t[0]); | ||
return res ? res[1] : defaultVal; | ||
}; | ||
}; | ||
export { | ||
selectThresholdMax, | ||
selectThresholdMin | ||
}; |
import { illegalState } from "@thi.ng/errors"; | ||
/** | ||
* Takes an object describing a DAG of nodes of type T with keys being node IDs. | ||
* Also takes a function which will be applied to each node and either returns | ||
* an array of node IDs the given node depends on or null if the node has no | ||
* dependencies. Traverses all nodes in the graph (object) and returns an array | ||
* of node IDs in topological dependency order. | ||
* | ||
* @remarks | ||
* An error will be thrown if the graph contains cycles. In this case, the full | ||
* cycle will be part of the error message (see second example below). | ||
* | ||
* @example | ||
* ```ts | ||
* const graph = { | ||
* a: { deps: ["c", "b"] }, | ||
* b: {}, | ||
* c: { deps: ["d"] }, | ||
* d: { deps: ["b"] } | ||
* }; | ||
* topoSort(graph, (node) => node.deps); | ||
* // [ "b", "d", "c", "a" ] | ||
* | ||
* // An error will be thrown if the graph contains cycles... | ||
* graph.d.deps.push("a"); | ||
* | ||
* topoSort(graph, (node) => node.deps); | ||
* // Uncaught Error: illegal state: dependency cycle: a -> c -> d -> a | ||
* ``` | ||
* | ||
* @param nodes | ||
* @param deps | ||
* @returns | ||
*/ | ||
export const topoSort = (nodes, deps) => { | ||
const cycles = {}; | ||
const topology = []; | ||
const sort = (id, path) => { | ||
if (cycles[id]) | ||
illegalState(`dependency cycle: ${path.join(" -> ")}`); | ||
cycles[id] = true; | ||
const nodeDeps = deps(nodes[id]); | ||
if (nodeDeps) { | ||
for (let d of nodeDeps) | ||
sort(d, [...path, d]); | ||
} | ||
cycles[id] = false; | ||
if (!topology.includes(id)) | ||
topology.push(id); | ||
}; | ||
for (let id in nodes) | ||
sort(id, [id]); | ||
return topology; | ||
const topoSort = (nodes, deps) => { | ||
const cycles = {}; | ||
const topology = []; | ||
const sort = (id, path) => { | ||
if (cycles[id]) | ||
illegalState(`dependency cycle: ${path.join(" -> ")}`); | ||
cycles[id] = true; | ||
const nodeDeps = deps(nodes[id]); | ||
if (nodeDeps) { | ||
for (let d of nodeDeps) | ||
sort(d, [...path, d]); | ||
} | ||
cycles[id] = false; | ||
if (!topology.includes(id)) | ||
topology.push(id); | ||
}; | ||
for (let id in nodes) | ||
sort(id, [id]); | ||
return topology; | ||
}; | ||
export { | ||
topoSort | ||
}; |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
76932
6
1523
Updated@thi.ng/api@^8.9.12
Updated@thi.ng/checks@^3.4.12
Updated@thi.ng/compare@^2.2.8
Updated@thi.ng/equiv@^2.1.37
Updated@thi.ng/errors@^2.4.6
Updated@thi.ng/random@^3.6.18