@oazmi/kitchensink
Version:
a collection of personal utility functions
320 lines (319 loc) • 11.5 kB
JavaScript
/** utility functions for 2d arrays.
*
* a 2d array of type `T` is defined as `T[R][C]`, where `R` is the major-axis (axis=0), and `C` is the minor-axis (axis=1).
* internally, we call the major-axis the row-axis, and the minor-axis the column-axis (or col-axis).
*
* @module
*/
import "./_dnt.polyfills.js";
import { max, modulo } from "./numericmethods.js";
import { isFunction } from "./struct.js";
/** get the shape of a 2d array as a 2-tuple describing the major-axis's length, and the minor-axis's length.
*
* @example
* ```ts
* import { assertEquals } from "jsr:@std/assert"
*
* const arr2d: Array2DRowMajor<number> = [
* [1 , 2 , 3 , 4 , 5 ],
* [6 , 7 , 8 , 9 , 10],
* [11, 12, 13, 14, 15],
* ]
* const [rows, cols] = shapeOfArray2D(arr2d)
* assertEquals(rows, 3)
* assertEquals(cols, 5)
* ```
*/
export const shapeOfArray2D = (arr2d) => {
const major_len = arr2d.length, minor_len = arr2d[0]?.length ?? 0;
return [major_len, minor_len];
};
// TODO: deprecate this later on, as I didn't like the inconsistency in function naming, and the CamelCasing, instead of pascalCasing
/** @deprecated this got renamed to {@link shapeOfArray2D | `shapeOfArray2D`} for naming consistency. */
export const Array2DShape = shapeOfArray2D;
/** create a new row-major 2d array, with provided value or fill function. */
export const newArray2D = (rows, cols, fill_fn) => {
const col_map_fn = isFunction(fill_fn) ?
() => Array(cols).fill(undefined).map(fill_fn) :
() => Array(cols).fill(fill_fn);
return Array(rows).fill(undefined).map(col_map_fn);
};
/** transpose a 2D array (row-major to column-major, or vice versa)
*
* @param arr2d the 2D array to be transposed
* @returns the transposed 2D array
*
* @example
* ```ts
* import { assertEquals } from "jsr:@std/assert"
*
* const arr2d: Array2DRowMajor<number> = [
* [1 , 2 , 3 , 4 , 5 ],
* [6 , 7 , 8 , 9 , 10],
* [11, 12, 13, 14, 15],
* ]
* const arr2d_transposed: Array2DColMajor<number> = transposeArray2D(arr2d)
* assertEquals(arr2d_transposed, [
* [1 , 6 , 11],
* [2 , 7 , 12],
* [3 , 8 , 13],
* [4 , 9 , 14],
* [5 , 10, 15],
* ])
* ```
*/
export const transposeArray2D = (arr2d) => {
const [rows, cols] = shapeOfArray2D(arr2d), arr_transposed = [];
for (let c = 0; c < cols; c++) {
arr_transposed[c] = [];
}
for (let r = 0; r < rows; r++) {
for (let c = 0; c < cols; c++) {
arr_transposed[c][r] = arr2d[r][c];
}
}
return arr_transposed;
};
/** splice rows of a row-major 2D array and optionally insert new rows at the specified `start` index.
*
* @param arr2d the row-major 2D array to be spliced.
* @param start the row-index at which to start changing the array.
* @param delete_count the number of rows to remove. if `undefined`, all rows from `start` to the end of the array will be removed.
* @param insert_items optionally insert row-major based 2D array items the index of `start`.
* @returns a new row-major 2D array containing the deleted rows.
*
* @example
* delete `1` row from `arr2d` (starting at row-index `1`), and insert `2` new rows in its place.
*
* ```ts
* import { assertEquals } from "jsr:@std/assert"
*
* const arr2d: Array2DRowMajor<number> = [
* [1 , 2 , 3 , 4 , 5 ],
* [6 , 7 , 8 , 9 , 10],
* [11, 12, 13, 14, 15],
* ]
* const deleted_rows = spliceArray2DMajor(arr2d, 1, 1,
* [21, 22, 23, 24, 25],
* [31, 32, 33, 34, 35]
* )
* assertEquals(arr2d, [
* [1 , 2 , 3 , 4 , 5 ],
* [21, 22, 23, 24, 25],
* [31, 32, 33, 34, 35],
* [11, 12, 13, 14, 15],
* ])
* assertEquals(deleted_rows, [
* [6 , 7 , 8 , 9 , 10],
* ])
* ```
*/
export const spliceArray2DMajor = (arr2d, start, delete_count, ...insert_items) => {
const [rows, cols] = shapeOfArray2D(arr2d);
delete_count ??= max(rows - start, 0);
return arr2d.splice(start, delete_count, ...insert_items);
};
/** splice columns of a row-major 2D array and optionally insert new columns at the specified `start` index.
*
* @param arr2d the row-major 2D array to be spliced.
* @param start the column-index at which to start changing the array.
* @param delete_count the number of columns to remove. if `undefined`, all columns from `start` to the end of the array will be removed.
* @param insert_items optionally insert column-major based 2D array items the index of `start`.
* @returns a new column-major 2D array containing the deleted columns.
*
* @example
* delete `2` columns from `arr2d` (starting at column-index `1`), and insert `5` new columns in its place.
*
* ```ts
* import { assertEquals } from "jsr:@std/assert"
*
* const arr2d: Array2DRowMajor<number> = [
* [1 , 2 , 3 , 4 , 5 ],
* [6 , 7 , 8 , 9 , 10],
* [11, 12, 13, 14, 15],
* ]
* const deleted_cols = spliceArray2DMinor(arr2d, 1, 2,
* [21, 31, 41],
* [22, 32, 42],
* [23, 33, 43],
* [24, 34, 44],
* [25, 35, 45]
* )
* assertEquals(arr2d, [
* [1 , 21, 22, 23, 24, 25, 4 , 5 ],
* [6 , 31, 32, 33, 34, 35, 9 , 10],
* [11, 41, 42, 43, 44, 45, 14, 15],
* ])
* assertEquals(deleted_cols, [
* [2 , 7 , 12],
* [3 , 8 , 13],
* ])
* ```
*/
export const spliceArray2DMinor = (arr2d, start, delete_count, ...insert_items) => {
const [rows, cols] = shapeOfArray2D(arr2d), insert_items_rowwise = insert_items.length > 0 ?
transposeArray2D(insert_items) :
Array(rows).fill([]);
delete_count ??= max(cols - start, 0);
return transposeArray2D(arr2d.map((row_items, row) => row_items.splice(start, delete_count, ...insert_items_rowwise[row])));
};
/** mutate and rotate the major-axis of a 2D array by the specified amount to the right.
*
* given a row-major 2D array `arr2d`, 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 arr2d the 2D 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 arr2d: Array2DRowMajor<number> = [
* [1 , 2 , 3 ],
* [4 , 5 , 6 ],
* [7 , 8 , 9 ],
* [10, 11, 12],
* [13, 14, 15],
* ]
* rotateArray2DMajor(arr2d, 2)
* assertEquals(arr2d, [
* [10, 11, 12],
* [13, 14, 15],
* [1 , 2 , 3 ],
* [4 , 5 , 6 ],
* [7 , 8 , 9 ],
* ])
* ```
*/
export const rotateArray2DMajor = (arr2d, amount) => {
const [rows, cols] = shapeOfArray2D(arr2d);
// compute the effective right-rotation amount so that it handles negative values and full rotations
amount = modulo(amount, rows === 0 ? 1 : rows);
// there is nothing to rotate if the effective amount is zero
if (amount === 0) {
return arr2d;
}
const right_removed_rows = spliceArray2DMajor(arr2d, rows - amount, amount);
spliceArray2DMajor(arr2d, 0, 0, ...right_removed_rows);
return arr2d;
};
/** mutate and rotate the minor-axis of a 2D array by the specified amount to the right.
*
* given a row-major (and column-minor) 2D array `arr2d`, this function would rotate its columns by the specified `amount`.
* a positive `amount` would rotate the columns to the right, and a negative `amount` would rotate it to the left.
*
* @param arr2d the 2D array to be rotated.
* @param amount The number of indexes to rotate the minor-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 arr2d: Array2DRowMajor<number> = [
* [1 , 2 , 3 , 4 , 5 , 6 ],
* [7 , 8 , 9 , 10, 11, 12],
* [13, 14, 15, 16, 17, 18],
* ]
* rotateArray2DMinor(arr2d, 2)
* assertEquals(arr2d, [
* [5 , 6 , 1 , 2 , 3 , 4 ,],
* [11, 12, 7 , 8 , 9 , 10,],
* [17, 18, 13, 14, 15, 16,],
* ])
* ```
*/
export const rotateArray2DMinor = (arr2d, amount) => {
const [rows, cols] = shapeOfArray2D(arr2d);
// compute the effective right-rotation amount so that it handles negative values and full rotations
amount = modulo(amount, cols === 0 ? 1 : cols);
// there is nothing to rotate if the effective amount is zero
if (amount === 0) {
return arr2d;
}
const right_removed_cols = spliceArray2DMinor(arr2d, cols - amount, amount);
spliceArray2DMinor(arr2d, 0, 0, ...right_removed_cols);
return arr2d;
};
/** create a mesh grid from major and minor values.
*
* given two arrays `major_values` and `minor_values`,
* this function generates a pair of 2D arrays representing the major-grid and minor-grid.
* the major-grid contains rows of `major_values`, and the minor-grid contains columns of `minor_values`.
*
* @param major_values the values to be used as rows in the major-grid
* @param minor_values the values to be used as columns in the minor-grid
* @returns a 2-tuple containing the major-grid and minor-grid as 2D arrays
*
* @example
* ```ts
* import { assertEquals } from "jsr:@std/assert"
*
* const
* y_values = [1, 2, 3],
* x_values = [4, 5],
* [yy_grid, xx_grid] = meshGrid(y_values, x_values)
* assertEquals(yy_grid, [
* [1, 1],
* [2, 2],
* [3, 3],
* ])
* assertEquals(xx_grid, [
* [4, 5],
* [4, 5],
* [4, 5],
* ])
* ```
*/
export const meshGrid = (major_values, minor_values) => {
const
// axis0_len = major_values.length,
axis1_len = minor_values.length, major_grid = major_values.map((major_val) => Array(axis1_len).fill(major_val)), minor_grid = major_values.map(() => minor_values.slice());
return [major_grid, minor_grid];
};
/** map two arrays to a "field" of 2D array through a mapping function.
*
* given a mapping function `map_fn`, and two arrays `x_values` and `y_values`,
* this function generates a 2D array where each element is the result of applying
* `map_fn` to the corresponding elements from `x_values` and `y_values`.
*
* @param map_fn the mapping function that takes an `x` value from `x_values`
* and a `y` value from `y_values`, and returns the mapped z_value.
* @param x_values the values to be used as the major axis (rows) of the resulting 2D array.
* @param y_values the values to be used as the minor axis (columns) of the resulting 2D array.
* @returns a 2D array with mapped values from `x_values` and `y_values`
*
* @example
* `z` is a function of `x` and `y` defined by: `z(x, y) = x + y`. <br>
* to create a 2d grid of `z_values` using `x_values = [1, 2, 3]` and `y_values = [4, 5]`, we do the following:
*
* ```ts
* import { assertEquals } from "jsr:@std/assert"
*
* const
* add = (x: number, y: number) => (x + y),
* x_values = [1, 2, 3],
* y_values = [4, 5],
* z_values = meshMap(add, x_values, y_values)
* assertEquals(z_values, [
* [5, 6],
* [6, 7],
* [7, 8],
* ])
* ```
*/
export const meshMap = (map_fn, x_values, y_values) => {
const axis0_len = x_values.length, axis1_len = y_values.length, z_values = Array(axis0_len).fill(undefined);
for (let x_idx = 0; x_idx < axis0_len; x_idx++) {
const row = Array(axis1_len).fill(0), x = x_values[x_idx];
for (let y_idx = 0; y_idx < axis1_len; y_idx++) {
row[y_idx] = map_fn(x, y_values[y_idx]);
}
z_values[x_idx] = row;
}
return z_values;
};