@oazmi/kitchensink
Version:
a collection of personal utility functions
487 lines • 24.4 kB
TypeScript
/** utility functions for 1d arrays.
*
* @module
*/
/** resolve the positive (normalized) starting and ending indexes of a range.
*
* for both `start` and `end`, a negative index can be used to indicate an index from the end of the range, if a `length` is given.
* for example, `-2` refers to the second to last index (ie `length - 2`).
*
* > [!note]
* > you **must** provide the length of your array if you wish to use negative indexes.
* > furthermore, you will **only** receive the length property if you had initially provided the length of the array.
*
* @param start starting index. defaults to `0`
* @param end ending index. defaults to `undefined` if `length` is not provided. else `end = length` (before offsetting)
* @param length length of the array in question. required if you want a numeric value of `end` that is `undefined`. defaults to `undefined`
* @param offset in the very end of evauation, add an addition offset to `start` and `end` indexes
* @returns a 3-tuple array of resolved [`start` index, `end` index, and `length` of range (ie `end - start`)]
*
* @example
* ```ts
* import { assertEquals } from "jsr:@std/assert"
*
* const
* my_array = [0, 1, 2, 3, 4, 5, 6],
* [start, end, new_length] = resolveRange(1, -2, my_array.length)
*
* assertEquals([start, end, new_length], [1, 5, 4])
* assertEquals(my_array.slice(start, end), [1, 2, 3, 4])
*
* // expected resolved ranges when a length (3rd argument) is given.
* assertEquals(resolveRange(2, undefined, 10), [2, 10, 8])
* assertEquals(resolveRange(2, 10, 10), [2, 10, 8])
* assertEquals(resolveRange(2, -1, 10), [2, 9, 7])
* assertEquals(resolveRange(2, -2, 10), [2, 8, 6])
* assertEquals(resolveRange(undefined, -2, 10), [0, 8, 8])
* assertEquals(resolveRange(-3, undefined, 10), [7, 10, 3])
* assertEquals(resolveRange(-3, -1, 10), [7, 9, 2])
*
* // if no length argument is provided, then no expectation for output length will be given.
* assertEquals(resolveRange(2, 10), [2, 10, undefined])
* assertEquals(resolveRange(2), [2, undefined, undefined])
*
* // if no length argument is provided, negative indexes will not be resolved.
* assertEquals(resolveRange(-2, 10), [-2, 10, undefined])
* assertEquals(resolveRange(2, -2), [2, -2, undefined])
* assertEquals(resolveRange(-2, -2), [-2, -2, undefined])
*
* // you can additionally offset you final resolved output `start` and `end` indexes using the optional 4th `offset` argument.
* assertEquals(resolveRange(2, undefined, 10, 100), [102, 110, 8])
* assertEquals(resolveRange(2, 10, 10, 100), [102, 110, 8])
* assertEquals(resolveRange(2, -1, 10, 100), [102, 109, 7])
* assertEquals(resolveRange(2, -2, 10, 100), [102, 108, 6])
* assertEquals(resolveRange(undefined, -2, 10, 100), [100, 108, 8])
* assertEquals(resolveRange(-3, undefined, 10, 100), [107, 110, 3])
* assertEquals(resolveRange(-3, -1, 10, 100), [107, 109, 2])
*
* // expected resolved output when a length is not provided, but an offset is provided.
* assertEquals(resolveRange(2, 10, undefined, 100), [102, 110, undefined])
* assertEquals(resolveRange(2, undefined, undefined, 100), [102, undefined, undefined])
* // notice the `98`s below when negative indexes are used.
* // these might not be what one would expect, so always make sure to provide a length if a potential negative index might be used.
* assertEquals(resolveRange(-2, 10, undefined, 100), [98, 110, undefined])
* assertEquals(resolveRange(2, -2, undefined, 100), [102, 98, undefined])
* assertEquals(resolveRange(-2, -2, undefined, 100), [98, 98, undefined])
* ```
*/
export declare function resolveRange(start: number | undefined, end: number | undefined, length: number, offset?: number): [start: number, end: number, length: number];
export declare function resolveRange(start?: number | undefined, end?: number | undefined, length?: undefined, offset?: number): [start: number, end: number | undefined, length: undefined];
/** mutate and rotate the given array by the specified amount to the right.
*
* given an array `arr`, this function would rotate its rows by the specified `amount`.
* a positive `amount` would rotate the rows to the right, and a negative `amount` would rotate it to the left.
*
* @param arr the array to be rotated.
* @param amount The number of indexes to rotate the major-axis to the right.
* positive values rotate right, while negative values rotate left.
* @returns The original array is returned back after the rotation.
*
* @example
* ```ts
* import { assertEquals } from "jsr:@std/assert"
*
* const arr: Array<number> = [1, 2, 3, 4, 5]
*
* rotateArray(arr, 2)
* assertEquals(arr, [4, 5, 1, 2, 3])
*
* rotateArray(arr, -3)
* assertEquals(arr, [2, 3, 4, 5, 1])
* ```
*/
export declare const rotateArray: <T>(arr: Array<T>, amount: number) => Array<T>;
/** shuffle a 1D array via mutation. the ordering of elements will be randomized by the end.
*
* ```ts
* import { assertEquals, assertNotEquals } from "jsr:@std/assert"
*
* const
* range_100 = Array(100).fill(0).map((_, i) => (i)), // sequntially numbered array
* my_arr = range_100.slice()
* shuffleArray(my_arr) // shuffling our array via mutation
*
* // the shuffled array is very unlikely to equal to the original unshuffled form
* assertNotEquals(my_arr, range_100)
* // sort the shuffled array to assert the preservation of the contained items
* assertEquals(my_arr.toSorted((a, b) => (a - b)), range_100)
* ```
*/
export declare const shuffleArray: <T>(arr: Array<T>) => Array<T>;
/** a generator that shuffles your 1D array via mutation, then yields randomly selected non-repeating elements out of it, one by one,
* until all elements have been yielded, at which a new cycle begins, and the items in the array are re-shuffled again.
* i.e. after every new cycle, the ordering of the randomly yielded elements will differ from the ordering of the previous cycle.
*
* moreover, you can call the iterator with an optional number argument that specifies if you wish to skip ahead or go back a certain number of elements.
* - `1`: go to next element (default behavior)
* - `0`: receive the same element as before
* - `-1`: go to previous next element
* - `+ve number`: skip to next `number` of elements
* - `-ve number`: go back `number` of elements
*
* note that once a cycle is complete, going back won't restore the correct element from the previous cycle, because the info about the previous cycle gets lost.
*
* ```ts
* import { assert, assertEquals, assertNotEquals } from "jsr:@std/assert"
*
* const
* my_playlist = ["song1", "song2", "song3", "song4"],
* my_queue = my_playlist.slice(),
* track_iter = shuffledDeque(my_queue) // shuffles our play queue via mutation, and then indefinitely provides unique items each cycle
*
* const
* track1 = track_iter.next().value,
* track2 = track_iter.next(1).value,
* track3 = track_iter.next().value
*
* assertEquals(track1, track_iter.next(-2).value)
* assertEquals(track2, track_iter.next(1).value)
* assertEquals(track3, track_iter.next().value)
*
* const track4 = track_iter.next().value // final track of the current queue
* const track5 = track_iter.next().value // the queue has been reset, and re-shuffled
*
* assert([track1, track2, track3].includes(track4) === false)
* assert([track1, track2, track3, track4].includes(track5) === true)
* ```
*/
export declare const shuffledDeque: <T>(arr: Array<T>) => Generator<T, void, number | undefined>;
/** type definition for a generic stack data structure, that is aware of its size. */
export interface GenericStack<T> {
readonly length: number;
push: (...items: T[]) => any;
pop: () => T | undefined;
}
/** a function to splice any stack (see the {@link GenericStack} interface).
*
* splicing alone lets you effectively implement all sorts of array mutation methods, such as
* `push`, `pop`, `unshift`, `shift`, `insert`, `rotate`, and many more.
*
* > [!note]
* > the `length` property of your `stack` is not mutated/assigned by this function.
* > you will have to do that manually yourself if your `stack` does not modify the `length` property upon the `push` and `pop` operations.
*
* @param stack the generic stack object to splice.
* @param start the starting index to begin splicing from.
* you must provide only positive starting index values.
* defaults to `0`.
* @param deleteCount the number of elements to remove from the `start` index (inclusive).
* if it is set to `undefined`, then all elements until the end of the generic stack array will be removed.
* defaults to `undefined`.
* @param items insert items at the `start` index, so that the first inserted item will occupy the `start` index _after_ the splicing.
* @returns an array of deleted items in the generic stack will be returned.
*
* @example
* ```ts
* import { assertEquals } from "jsr:@std/assert"
*
* // aliasing our functions for brevity
* const
* fn = spliceGenericStack,
* eq = assertEquals
*
* const my_stack = [0, 1, 2, 3, 4, 5, 6, 7]
*
* eq(fn(my_stack, 4), [4, 5, 6, 7])
* eq(my_stack, [0, 1, 2, 3])
*
* eq(fn(my_stack, 0, 0, -3, -2, -1), [])
* eq(my_stack, [-3, -2, -1, 0, 1, 2, 3])
*
* eq(fn(my_stack, 4, 2, 0.1, 0.2, 0.3), [1, 2])
* eq(my_stack, [-3, -2, -1, 0, 0.1, 0.2, 0.3, 3])
*
* eq(fn(my_stack), [-3, -2, -1, 0, 0.1, 0.2, 0.3, 3])
* eq(my_stack, [])
* ```
*/
export declare const spliceGenericStack: <T>(stack: GenericStack<T>, start?: number, deleteCount?: number | undefined, ...items: T[]) => T[];
/** generate a numeric array with sequentially increasing value, within a specific range interval.
* similar to python's `range` function.
*
* however, unlike python's `range`, you **must** always supply the starting index **and** the ending index,
* even if the start index is supposed to be `0`, you cannot substitute the first argument with the ending index.
* only the {@link step} argument is optional. moreover, the {@link step} argument must always be a positive number.
*
* > [!note]
* > there is also an iterator generator variant of this function that is also capable of indefinite sequences.
* > check out {@link rangeIterator} for details.
*
* @param start the initial number to begin the output range sequence from.
* @param end the final exclusive number to end the output range sequence at. its value will **not** be in the output array.
* @param step a **positive** number, dictating how large each step from the `start` to the `end` should be.
* for safety, so that a user doesn't run into an infinite loop by providing a negative step value,
* we always take the absolute value of this parameter.
* defaults to `1`.
* @param decimal_precision an integer that specifies the number of decimal places to which the output
* numbers should be rounded to, in order to nullify floating point arithmetic inaccuracy.
* for instance, in javascript `0.1 + 0.2 = 0.30000000000000004` instead of `0.3`.
* now, you'd certainly not want to see this kind of number in our output, which is why we round it so that it becomes `0.3`.
* defaults to `6` (6 decimal places; i.e. rounds to the closest micro-number (10**(-6))).
* @returns a numeric array with sequentially increasing value from the `start` to the `end` interval, with steps of size `step`.
*
* @example
* ```ts
* import { assertEquals } from "jsr:@std/assert"
*
* // aliasing our functions for brevity
* const
* fn = rangeArray,
* eq = assertEquals
*
* eq(fn(0, 5), [0, 1, 2, 3, 4])
* eq(fn(-2, 3), [-2, -1, 0, 1, 2])
* eq(fn(2, 7), [2, 3, 4, 5, 6])
* eq(fn(2, 7.1), [2, 3, 4, 5, 6, 7])
* eq(fn(0, 1, 0.2), [0, 0.2, 0.4, 0.6, 0.8])
* eq(fn(0, 100, 20), [0, 20, 40, 60, 80])
* eq(fn(2, -3), [2, 1, 0, -1, -2])
* eq(fn(2, -7, 2), [2, 0, -2, -4, -6])
* eq(fn(2, -7, -2), [2, 0, -2, -4, -6]) // as a protective measure, only the `abs(step)` value is ever taken.
* eq(fn(2, 7, -1), [2, 3, 4, 5, 6]) // as a protective measure, only the `abs(step)` value is ever taken.
* ```
*/
export declare const rangeArray: (start: number, end: number, step?: number, decimal_precision?: number) => Array<number>;
/** this function is the iterator version of {@link rangeArray}, mimicking python's `range` function.
*
* you can iterate indefinitely with this function if you set the {@link end} parameter to `undefined`,
* and then define the direction of the step increments with the {@link step} parameter.
* (a negative `step` will result in a decreasing sequence of numbers).
*
* @param start the initial number to begin the output range sequence from. defaults to `0`.
* @param end the final exclusive number to end the output range sequence at. its value will **not** be in the last output number.
* if left `undefined`, then it will be assumed to be `Number.POSITIVE_INFINITY` if `step` is a positive number (default),
* or it will become `Number.NEGATIVE_INFINITY` if `step` is a negative number.
* defaults to `undefined`.
* @param step a number, dictating how large each step from the `start` to the `end` should be. defaults to `1`.
* @param decimal_precision an integer that specifies the number of decimal places to which the output
* numbers should be rounded to, in order to nullify floating point arithmetic inaccuracy.
* defaults to `6` (6 decimal places; i.e. rounds to the closest micro-number (10**(-6))).
* @yields a number in the sequence of the given range.
* @returns the total number of elements that were outputted.
*
* @example
* ```ts
* import { assertEquals } from "jsr:@std/assert"
*
* // aliasing our functions for brevity
* const
* fn = rangeIterator,
* eq = assertEquals
*
* eq([...fn(0, 5)], [0, 1, 2, 3, 4])
* eq([...fn(-2, 3)], [-2, -1, 0, 1, 2])
* eq([...fn(2, 7)], [2, 3, 4, 5, 6])
* eq([...fn(2, 7.1)], [2, 3, 4, 5, 6, 7])
* eq([...fn(0, 1, 0.2)], [0, 0.2, 0.4, 0.6, 0.8])
* eq([...fn(1, -1, 0.4)], [1, 0.6, 0.2, -0.2, -0.6])
* eq([...fn(1, -1, -0.4)], [1, 0.6, 0.2, -0.2, -0.6])
*
* // indefinite sequence in the positive direction
* const
* loop_limit = 10,
* accumulation_arr: number[] = []
* for (const v of fn(0)) {
* if (v >= loop_limit) { break }
* accumulation_arr.push(v)
* }
* eq(accumulation_arr, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
* accumulation_arr.splice(0) // clearing our array for the next test
*
* // indefinite sequence in the negative direction
* for (const v of fn(0, undefined, -1)) {
* if (v <= -loop_limit) { break }
* accumulation_arr.push(v)
* }
* eq(accumulation_arr, [0, -1, -2, -3, -4, -5, -6, -7, -8, -9])
* ```
*/
export declare const rangeIterator: (start?: number, end?: number | undefined, step?: number, decimal_precision?: number) => IterableIterator<number, number>;
/** zip together a list of input arrays as tuples, similar to python's `zip` function.
*
* > [!note]
* > if one of the input arrays is shorter in length than all the other input arrays,
* > then this zip function will only generate tuples up until the shortest array is expended,
* > similar to how python's `zip` function behaves.
* > in a sense, this feature is what sets it apart from the 2d array transpose function {@link transposeArray2D},
* > which decides its output length based on the first array's length.
*
* > [!tip]
* > applying the zip function twice will give you back the original arrays (assuming they all had the same length).
* > so in a sense, to unzip the output of `zipArrays`, you simply apply `zipArrays` to again (after performing an array spread operation).
*
* > [!important]
* > this function only accepts array inputs to zip, and **not** iterators.
* > to zip a sequence of iterators, use the {@link zipIterators} function (which has a slightly slower performance).
*
* @example
* ```ts
* import { assertEquals } from "jsr:@std/assert"
*
* type MyObj = { key: string }
* type MyTuple = [number, boolean, MyObj]
*
* const
* my_num_arr: number[] = [100, 101, 102, 103, 104],
* my_bool_arr: boolean[] = [true, false, false, true, false],
* my_obj_arr: MyObj[] = [{ key: "a" }, { key: "b" }, { key: "c" }, { key: "d" }]
* // notice that `my_obj_arr` is shorter than the other two arrays. (i.e. has a length of `4`, while others are `5`)
* // this would mean that zipping them together would only generate a 3-tuple array of `4` elements.
*
* const my_tuples_arr: MyTuple[] = zipArrays<[number, boolean, MyObj]>(my_num_arr, my_bool_arr, my_obj_arr)
* assertEquals(my_tuples_arr, [
* [100, true, { key: "a" }],
* [101, false, { key: "b" }],
* [102, false, { key: "c" }],
* [103, true, { key: "d" }],
* ])
*
* // to unzip the array of tuples, and receive back the original (trimmed) arrays, simply apply `zipArrays` again.
* const my_arrs = [
* [ 1, 2, 3, 4],
* [true, false, true, true],
* [ "w", "x", "y", "z"],
* ]
* assertEquals(zipArrays(...zipArrays(...my_arrs)), my_arrs)
*
* // zipping no input arrays should not iterate infinitely.
* assertEquals(zipArrays(), [])
* ```
*/
export declare const zipArrays: <T extends Array<any>>(...arrays: Array<any[]>) => Array<T>;
/** zip together a list of input iterators or iterable objects as tuples, similar to python's `zip` function.
*
* > [!note]
* > this zip function stops yielding as soon as one of its input iterators is "done" iterating (i.e. out of elements).
*
* if all of your input `iterators` are arrays, then use the {@link zipArrays} function, which is more performant (and smaller in footprint).
*
* @param iterators the list of iterators/iterable objects which should be zipped.
* @yields a tuple of each entry from the given list of `iterators`, until one of the iterators is "done" iterating (i.e. out of elements).
* @returns the number of items that were yielded/iterated (i.e. length of iterator).
*
* @example
* ```ts
* import { assertEquals } from "jsr:@std/assert"
*
* type MyObj = { key: string }
* type MyTuple = [number, boolean, MyObj]
*
* const
* my_num_iter: Iterable<number> = rangeIterator(100), // infnite iterable, with values `[100, 101, 102, ...]`
* my_bool_iter: Iterator<boolean> = [true, false, false, true, false][Symbol.iterator](),
* my_obj_iter: Iterable<MyObj> = [{ key: "a" }, { key: "b" }, { key: "c" }, { key: "d" }]
* // notice that `my_obj_iter` is shorter than the other two arrays. (i.e. has a length of `4`)
* // this would mean that zipping them together would only generate a 3-tuple array of `4` elements.
*
* const my_tuples_iter: Iterator<MyTuple> = zipIterators<[number, boolean, MyObj]>(my_num_iter, my_bool_iter, my_obj_iter)
* assertEquals(my_tuples_iter.next(), { value: [100, true, { key: "a" }], done: false })
* assertEquals(my_tuples_iter.next(), { value: [101, false, { key: "b" }], done: false })
* assertEquals(my_tuples_iter.next(), { value: [102, false, { key: "c" }], done: false })
* assertEquals(my_tuples_iter.next(), { value: [103, true, { key: "d" }], done: false })
* assertEquals(my_tuples_iter.next(), { value: 4, done: true }) // the return value of the iterator dictates its length.
*
*
* // since the actual output of `zipIterators` is an `IterableIterator`,
* // so we may even use it in a for-of loop, or do an array spreading with the output.
* const my_tuples_iter2 = zipIterators<[number, boolean]>(my_num_iter, [false, true, false, false, true])
* my_tuples_iter2 satisfies Iterable<[number, boolean]>
*
* // IMPORTANT: notice that the first tuple is not `[104, false]`, but instead `[105, false]`.
* // this is because our first zip iterator (`my_tuples_iter`) utilized the `my_num_iter` iterable one additional time
* // before realizing that one of the input iterables (the `my_bool_iter`) had gone out of elements to provide.
* // thus, the ordering of the iterators do matter, and it is possible to have one iterated value to disappear into the void.
* assertEquals([...my_tuples_iter2], [
* [105, false],
* [106, true ],
* [107, false],
* [108, false],
* [109, true ],
* ])
*
* // zipping with zero sized input iterators should not yield anything.
* assertEquals([...zipIterators([], [], [])], [])
*
* // zipping with no input iterators at all should not iterate infinitely.
* assertEquals([...zipIterators()], [])
* ```
*/
export declare const zipIterators: <T extends Array<any>>(...iterators: Array<Iterator<any> | Iterable<any>>) => IterableIterator<T, number>;
/** create a mapping function that operates on a list of iterable/iterator inputs, that are zipped together as tuples,
* and then passed on to the {@link map_fn} for transformation, one by one.
*
* > [!note]
* > if one of the input arrays or iterators is shorter in length than all the rest,
* > then the mapping function will only operate up till the shortest array/iterator.
* > similar to how python's `zip` function generates tuples up till the end of the shortest input array.
*
* @param map_fn a function that maps each tuple `T` (from the collection of input iterators) to some type `V`.
* @returns a generator function that will accept a list of iterators as its input,
* and that yields back the result of each zipped tuple being mapped via `map_fn`.
* the return value of the generator (after it concludes) is the length of the number of items that it had yielded.
*
* @example
* ```ts
* import { assertEquals } from "jsr:@std/assert"
*
* type MyObj = { key: string }
* type MyTuple = [number, boolean, MyObj]
*
* const myTupleMapper = zipIteratorsMapperFactory((tuple: MyTuple, index: number): string => {
* const [my_number, my_boolean, my_object] = tuple
* return `${index}-${my_object.key}/${my_number}/${my_boolean}`
* })
*
* myTupleMapper satisfies ((number_arr: number[], boolean_arr: boolean[], object_arr: MyObj[]) => IterableIterator<string>)
*
* const
* my_num_iter = rangeIterator(100), // infnite iterable, with values `[100, 101, 102, ...]`
* my_bool_arr = [true, false, false, true, false],
* my_obj_arr = [{ key: "a" }, { key: "b" }, { key: "c" }, { key: "d" }]
* // notice that `my_obj_arr` is shorter than the other two arrays. (i.e has a length of `4`).
* // this would mean that `myTupleMapper` would only operate on the first `4` elements of all the 3 arrays.
*
* const outputs_iter: Iterable<string> = myTupleMapper(my_num_iter, my_bool_arr, my_obj_arr)
* assertEquals([...outputs_iter], [
* "0-a/100/true",
* "1-b/101/false",
* "2-c/102/false",
* "3-d/103/true",
* ])
*
* // zipping with zero sized input iterators should not yield anything.
* assertEquals([...myTupleMapper([], [], [])], [])
*
* // for safety, map-zipping with no input iterators should not yield anything.
* assertEquals([...myTupleMapper()], [])
* ```
*/
export declare const zipIteratorsMapperFactory: <T extends Array<any>, V>(map_fn: ((tuple: T, index: number) => V)) => ((...iterators: Array<Iterator<any> | Iterable<any>>) => IterableIterator<V, number>);
/** a generator function that slices your input `array` to smaller chunks of your desired `chunk_size`.
*
* note that the final chunk that gets yielded may be smaller than your `chunk_size` if it does not divide `array.length` precisely.
*
* @param chunk_size a **positive** integer dictating the length of each chunk that gets yielded.
* @param array your input array that needs to be yielded in chunks.
* @yields a chunk of length `chunk_size` from your input `array`.
*
* @example
* ```ts
* import { assertEquals } from "jsr:@std/assert"
*
* const my_arr = rangeArray(0, 30) // equals to `[0, 1, 2, ..., 28, 29]`
*
* // below, we split `my_arr` into smaller array chunks of size `8`, except for the last chunk, which is smaller.
* assertEquals([...chunkGenerator(8, my_arr)], [
* [ 0, 1, 2, 3, 4, 5, 6, 7 ],
* [ 8, 9, 10, 11, 12, 13, 14, 15],
* [16, 17, 18, 19, 20, 21, 22, 23],
* [24, 25, 26, 27, 28, 29],
* ])
*
* // chunking zero length array will not yield anything
* assertEquals([...chunkGenerator(8, [])], [])
* ```
*/
export declare const chunkGenerator: <T>(chunk_size: number, array: T[]) => Generator<T[], void>;
//# sourceMappingURL=array1d.d.ts.map