From 599113d1a028f31035858c1dced53956a524f374 Mon Sep 17 00:00:00 2001 From: David Contreras Date: Tue, 21 Jan 2025 19:57:40 -0600 Subject: [PATCH 1/7] refactor: simplify flatten function using Array.prototype.flat --- src/utils/array.js | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/utils/array.js b/src/utils/array.js index 4017983361..3ef57eb73a 100644 --- a/src/utils/array.js +++ b/src/utils/array.js @@ -472,17 +472,7 @@ export function flatten (array) { // if not an array, return as is return array } - const flat = [] - - array.forEach(function callback (value) { - if (Array.isArray(value)) { - value.forEach(callback) // traverse through sub-arrays recursively - } else { - flat.push(value) - } - }) - - return flat + return array.flat(Infinity) } /** From 86957648d88957fe8221a56cf484c890be326bbe Mon Sep 17 00:00:00 2001 From: David Contreras Date: Fri, 24 Jan 2025 11:23:57 -0600 Subject: [PATCH 2/7] Added fallback --- src/utils/array.js | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/utils/array.js b/src/utils/array.js index 3ef57eb73a..92ded45183 100644 --- a/src/utils/array.js +++ b/src/utils/array.js @@ -472,7 +472,34 @@ export function flatten (array) { // if not an array, return as is return array } - return array.flat(Infinity) + if (typeof Array.prototype.flat === 'function') { + return array.flat(Infinity) + } else { + // TODO: once Array.prototype.flat is supported in all browsers, remove this and the _flatten function + return _flatten(array) + } + + function _flatten (array) { + const flat = [] + + function flattenHelper (value) { + if (Array.isArray(value)) { + const len = value.length + for (let i = 0; i < len; i++) { + flattenHelper(value[i]) // traverse through sub-arrays recursively + } + } else { + flat.push(value) + } + } + + const len = array.length + for (let i = 0; i < len; i++) { + flattenHelper(array[i]) + } + + return flat + } } /** From 86fc17d5b66d1a742a117a8c63fc503a23ae5794 Mon Sep 17 00:00:00 2001 From: David Contreras Date: Sat, 25 Jan 2025 19:28:25 -0600 Subject: [PATCH 3/7] added option to flatten arrays with homogeneous size --- src/function/matrix/flatten.js | 10 ++++--- src/utils/array.js | 48 ++++++++++++++++++++++++---------- 2 files changed, 41 insertions(+), 17 deletions(-) diff --git a/src/function/matrix/flatten.js b/src/function/matrix/flatten.js index 24b9b2f6fc..dd077db97f 100644 --- a/src/function/matrix/flatten.js +++ b/src/function/matrix/flatten.js @@ -1,4 +1,4 @@ -import { flatten as flattenArray } from '../../utils/array.js' +import { flatten as flattenArray, arraySize, validate } from '../../utils/array.js' import { factory } from '../../utils/factory.js' const name = 'flatten' @@ -26,13 +26,17 @@ export const createFlatten = /* #__PURE__ */ factory(name, dependencies, ({ type */ return typed(name, { Array: function (x) { - return flattenArray(x) + try { + return flattenArray(x, validate(x, arraySize(x))) + } catch { + return flattenArray(x) + } }, Matrix: function (x) { // Return the same matrix type as x (Dense or Sparse Matrix) // Return the same data type as x - return x.create(flattenArray(x.toArray()), x.datatype()) + return x.create(flattenArray(x.toArray(), true), x.datatype()) } }) }) diff --git a/src/utils/array.js b/src/utils/array.js index 92ded45183..0630f6bb32 100644 --- a/src/utils/array.js +++ b/src/utils/array.js @@ -465,40 +465,60 @@ function _unsqueeze (array, dims, dim) { * Flatten a multi dimensional array, put all elements in a one dimensional * array * @param {Array} array A multi dimensional array + * @param {boolean} [isHomogeneous=false] Indicates if the size is homogeneous (like a valid matrix) * @return {Array} The flattened array (1 dimensional) */ -export function flatten (array) { +export function flatten (array, isHomogeneous = false) { if (!Array.isArray(array)) { // if not an array, return as is return array } - if (typeof Array.prototype.flat === 'function') { - return array.flat(Infinity) + if (isHomogeneous) { + return _flattenHomogeneous(array) } else { - // TODO: once Array.prototype.flat is supported in all browsers, remove this and the _flatten function - return _flatten(array) + if (typeof Array.prototype.flat === 'function') { + return array.flat(Infinity) + } else { + return _flattenFallback(array) + } } - function _flatten (array) { + function _flattenFallback (arr) { + // remove this when Array.prototype.flat is broadly supported const flat = [] - - function flattenHelper (value) { + _recurse(arr) + return flat + function _recurse (value) { if (Array.isArray(value)) { const len = value.length for (let i = 0; i < len; i++) { - flattenHelper(value[i]) // traverse through sub-arrays recursively + _recurse(value[i]) // traverse through sub-arrays recursively } } else { flat.push(value) } } + } - const len = array.length - for (let i = 0; i < len; i++) { - flattenHelper(array[i]) - } - + function _flattenHomogeneous (arr) { + const flat = [] + _recurse(arr) return flat + + function _recurse (value) { + if (Array.isArray(value)) { + const len = value.length + if (Array.isArray(value[0])) { + for (let i = 0; i < len; i++) { + _recurse(value[i]) // traverse through sub-arrays recursively + } + } else { + for (let i = 0; i < len; i++) { + flat.push(value[i]) // traverse through sub-arrays without recursion + } + } + } + } } } From 94b7f346afb49643e9dc10c4b904f866743d7053 Mon Sep 17 00:00:00 2001 From: David Contreras Date: Sat, 25 Jan 2025 20:14:44 -0600 Subject: [PATCH 4/7] Typos in array.js and array.test.js --- src/utils/array.js | 64 ++++++++++++++--------------- test/unit-tests/utils/array.test.js | 4 +- 2 files changed, 34 insertions(+), 34 deletions(-) diff --git a/src/utils/array.js b/src/utils/array.js index 0630f6bb32..2cf4c6ec85 100644 --- a/src/utils/array.js +++ b/src/utils/array.js @@ -10,7 +10,7 @@ import { deepStrictEqual } from './object.js' * This function checks the size of the first entry, it does not validate * whether all dimensions match. (use function `validate` for that) * @param {Array} x - * @Return {Number[]} size + * @return {Number[]} size */ export function arraySize (x) { const s = [] @@ -28,7 +28,7 @@ export function arraySize (x) { * has a size corresponding to the provided size array. * @param {Array} array Array to be validated * @param {number[]} size Array with the size of each dimension - * @param {number} dim Current dimension + * @param {number} dim Current dimension * @throws DimensionError * @private */ @@ -51,7 +51,7 @@ function _validate (array, size, dim) { _validate(array[i], size, dimNext) } } else { - // last dimension. none of the childs may be an array + // last dimension. none of the children may be an array for (i = 0; i < len; i++) { if (Array.isArray(array[i])) { throw new DimensionError(size.length + 1, size.length, '>') @@ -82,7 +82,7 @@ export function validate (array, size) { /** * Validate whether the source of the index matches the size of the Array - * @param {Array | Matrix} array Array to be validated + * @param {Array | Matrix} value Array to be validated * @param {Index} index Index with the source information to validate * @throws DimensionError */ @@ -113,8 +113,8 @@ export function validateIndex (index, length) { } /** - * Test if and index has empty values - * @param {number} index Zero-based index + * Test if an index has empty values + * @param {Index} index Zero-based index */ export function isEmptyIndex (index) { for (let i = 0; i < index._dimensions.length; ++i) { @@ -140,7 +140,7 @@ export function isEmptyIndex (index) { * Resize a multi dimensional array. The resized array is returned. * @param {Array | number} array Array to be resized * @param {number[]} size Array with the size of each dimension - * @param {*} [defaultValue=0] Value to be filled in in new entries, + * @param {*} [defaultValue=0] Value to be filled in new entries, * zero by default. Specify for example `null`, * to clearly see entries that are not explicitly * set. @@ -180,7 +180,7 @@ export function resize (array, size, defaultValue) { * @param {Array} array Array to be resized * @param {number[]} size Array with the size of each dimension * @param {number} dim Current dimension - * @param {*} [defaultValue] Value to be filled in in new entries, + * @param {*} [defaultValue] Value to be filled in new entries, * undefined by default. * @private */ @@ -283,7 +283,7 @@ export function reshape (array, sizes) { /** * Replaces the wildcard -1 in the sizes array. - * @param {number[]} sizes List of sizes for each dimension. At most on wildcard. + * @param {number[]} sizes List of sizes for each dimension. At most one wildcard. * @param {number} currentLength Number of elements in the array. * @throws {Error} If more than one wildcard or unable to replace it. * @returns {number[]} The sizes array with wildcard replaced. @@ -333,7 +333,7 @@ function _reshape (array, sizes) { // testing if there are enough elements for the requested shape let tmpArray = array let tmpArray2 - // for each dimensions starting by the last one and ignoring the first one + // for each dimension starting by the last one and ignoring the first one for (let sizeIndex = sizes.length - 1; sizeIndex > 0; sizeIndex--) { const size = sizes[sizeIndex] tmpArray2 = [] @@ -408,7 +408,7 @@ function _squeeze (array, dims, dim) { /** * Unsqueeze a multi dimensional array: add dimensions when missing * - * Paramter `size` will be mutated to match the new, unqueezed matrix size. + * Parameter `size` will be mutated to match the new, unsqueezed matrix size. * * @param {Array} array * @param {number} dims Desired number of dimensions of the array @@ -442,7 +442,7 @@ export function unsqueeze (array, dims, outer, size) { * @param {Array} array * @param {number} dims Required number of dimensions * @param {number} dim Current dimension - * @returns {Array | *} Returns the squeezed array + * @returns {Array | *} Returns the unsqueezed array * @private */ function _unsqueeze (array, dims, dim) { @@ -554,7 +554,7 @@ export function filter (array, callback) { } /** - * Filter values in a callback given a regular expression + * Filter values in an array given a regular expression * @param {Array} array * @param {RegExp} regexp * @return {Array} Returns the filtered array @@ -671,7 +671,7 @@ export function getArrayDataType (array, typeOf) { /** * Return the last item from an array - * @param {array} + * @param {Array} array * @returns {*} */ export function last (array) { @@ -680,8 +680,8 @@ export function last (array) { /** * Get all but the last element of array. - * @param {array} - * @returns {*} + * @param {Array} array + * @returns {Array} */ export function initial (array) { return array.slice(0, array.length - 1) @@ -689,7 +689,7 @@ export function initial (array) { /** * Recursively concatenate two matrices. - * The contents of the matrices is not cloned. + * The contents of the matrices are not cloned. * @param {Array} a Multi dimensional array * @param {Array} b Multi dimensional array * @param {number} concatDim The dimension on which to concatenate (zero-based) @@ -719,8 +719,8 @@ function concatRecursive (a, b, concatDim, dim) { * Concatenates many arrays in the specified direction * @param {...Array} arrays All the arrays to concatenate * @param {number} concatDim The dimension on which to concatenate (zero-based) - * @returns -*/ + * @returns {Array} + */ export function concat () { const arrays = Array.prototype.slice.call(arguments, 0, -1) const concatDim = Array.prototype.slice.call(arguments, -1) @@ -736,9 +736,9 @@ export function concat () { } /** - * Receives two or more sizes and get's the broadcasted size for both. + * Receives two or more sizes and gets the broadcasted size for both. * @param {...number[]} sizes Sizes to broadcast together - * @returns + * @returns {number[]} The broadcasted size */ export function broadcastSizes (...sizes) { const dimensions = sizes.map((s) => s.length) @@ -773,7 +773,7 @@ export function checkBroadcastingRules (size, toSize) { const n = N - dim + j if ((size[j] < toSize[n] && size[j] > 1) || (size[j] > toSize[n])) { throw new Error( - `shape missmatch: missmatch is found in arg with shape (${size}) not possible to broadcast dimension ${dim} with size ${size[j]} to size ${toSize[n]}` + `shape mismatch: mismatch is found in arg with shape (${size}) not possible to broadcast dimension ${dim} with size ${size[j]} to size ${toSize[n]}` ) } } @@ -781,9 +781,9 @@ export function checkBroadcastingRules (size, toSize) { /** * Broadcasts a single array to a certain size - * @param {array} array Array to be broadcasted + * @param {Array} array Array to be broadcasted * @param {number[]} toSize Size to broadcast the array - * @returns The broadcasted array + * @returns {Array} The broadcasted array */ export function broadcastTo (array, toSize) { let Asize = arraySize(array) @@ -815,11 +815,11 @@ export function broadcastTo (array, toSize) { /** * Broadcasts arrays and returns the broadcasted arrays in an array * @param {...Array | any} arrays - * @returns + * @returns {Array[]} The broadcasted arrays */ export function broadcastArrays (...arrays) { if (arrays.length === 0) { - throw new Error('Insuficient number of argumnets in function broadcastArrays') + throw new Error('Insufficient number of arguments in function broadcastArrays') } if (arrays.length === 1) { return arrays[0] @@ -832,11 +832,11 @@ export function broadcastArrays (...arrays) { } /** - * stretches a matrix up to a certain size in a certain dimension + * Stretches a matrix up to a certain size in a certain dimension * @param {Array} arrayToStretch - * @param {number[]} sizeToStretch + * @param {number} sizeToStretch * @param {number} dimToStretch - * @returns + * @returns {Array} The stretched array */ export function stretch (arrayToStretch, sizeToStretch, dimToStretch) { return concat(...Array(sizeToStretch).fill(arrayToStretch), dimToStretch) @@ -846,13 +846,13 @@ export function stretch (arrayToStretch, sizeToStretch, dimToStretch) { * Retrieves a single element from an array given an index. * * @param {Array} array - The array from which to retrieve the value. -* @param {Array} idx - An array of indices specifying the position of the desired element in each dimension. +* @param {Array} index - An array of indices specifying the position of the desired element in each dimension. * @returns {*} - The value at the specified position in the array. * * @example * const arr = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]]; * const index = [1, 0, 1]; -* console.log(getValue(arr, index)); // 6 +* console.log(get(arr, index)); // 6 */ export function get (array, index) { if (!Array.isArray(array)) { throw new Error('Array expected') } @@ -886,7 +886,7 @@ export function recurse (value, index, array, callback) { /** * Deep clones a multidimensional array * @param {Array} array - * @returns cloned array + * @returns {Array} cloned array */ export function clone (array) { return Object.assign([], array) diff --git a/test/unit-tests/utils/array.test.js b/test/unit-tests/utils/array.test.js index d87ee32cd3..f779379356 100644 --- a/test/unit-tests/utils/array.test.js +++ b/test/unit-tests/utils/array.test.js @@ -195,7 +195,7 @@ describe('util.array', function () { [0, 0], [0, 0] ]) - // TODO: would be nicer if this returns uninit everwhere and not undefined on some places + // TODO: would be nicer if this returns uninit everywhere and not undefined in some places }) it('should resize a 2 dimensional array to 1 dimensional', function () { @@ -604,7 +604,7 @@ describe('util.array', function () { }) it('should throw an error when the broadcasting rules are not followed', function () { - assert.throws(function () { broadcastSizes([2, 2], [3, 2]) }, /Error: shape missmatch: missmatch is found in arg with shape.*/) + assert.throws(function () { broadcastSizes([2, 2], [3, 2]) }, /Error: shape mismatch: mismatch is found in arg with shape.*/) }) }) From 34e306e92d350cf93c4b1824f9cf9e3feff9cc77 Mon Sep 17 00:00:00 2001 From: David Contreras Date: Sat, 25 Jan 2025 20:34:57 -0600 Subject: [PATCH 5/7] fix types in jsdocs --- src/utils/array.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/array.js b/src/utils/array.js index 2cf4c6ec85..4994b76535 100644 --- a/src/utils/array.js +++ b/src/utils/array.js @@ -10,7 +10,7 @@ import { deepStrictEqual } from './object.js' * This function checks the size of the first entry, it does not validate * whether all dimensions match. (use function `validate` for that) * @param {Array} x - * @return {Number[]} size + * @return {number[]} size */ export function arraySize (x) { const s = [] @@ -834,7 +834,7 @@ export function broadcastArrays (...arrays) { /** * Stretches a matrix up to a certain size in a certain dimension * @param {Array} arrayToStretch - * @param {number} sizeToStretch + * @param {number[]} sizeToStretch * @param {number} dimToStretch * @returns {Array} The stretched array */ From 5534e3c2e936fae8affcfe71618703056533c3ac Mon Sep 17 00:00:00 2001 From: David Contreras Date: Mon, 27 Jan 2025 19:41:11 -0600 Subject: [PATCH 6/7] Renamed variable --- src/function/matrix/flatten.js | 8 ++------ src/utils/array.js | 7 ++++--- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/function/matrix/flatten.js b/src/function/matrix/flatten.js index dd077db97f..684e6d9f67 100644 --- a/src/function/matrix/flatten.js +++ b/src/function/matrix/flatten.js @@ -1,4 +1,4 @@ -import { flatten as flattenArray, arraySize, validate } from '../../utils/array.js' +import { flatten as flattenArray } from '../../utils/array.js' import { factory } from '../../utils/factory.js' const name = 'flatten' @@ -26,11 +26,7 @@ export const createFlatten = /* #__PURE__ */ factory(name, dependencies, ({ type */ return typed(name, { Array: function (x) { - try { - return flattenArray(x, validate(x, arraySize(x))) - } catch { - return flattenArray(x) - } + return flattenArray(x, false) }, Matrix: function (x) { diff --git a/src/utils/array.js b/src/utils/array.js index 4994b76535..f7f85e143e 100644 --- a/src/utils/array.js +++ b/src/utils/array.js @@ -465,15 +465,15 @@ function _unsqueeze (array, dims, dim) { * Flatten a multi dimensional array, put all elements in a one dimensional * array * @param {Array} array A multi dimensional array - * @param {boolean} [isHomogeneous=false] Indicates if the size is homogeneous (like a valid matrix) + * @param {boolean} [hasHomogeneousSize=false] Indicates if the size is homogeneous (like a valid matrix) * @return {Array} The flattened array (1 dimensional) */ -export function flatten (array, isHomogeneous = false) { +export function flatten (array, hasHomogeneousSize = false) { if (!Array.isArray(array)) { // if not an array, return as is return array } - if (isHomogeneous) { + if (hasHomogeneousSize) { return _flattenHomogeneous(array) } else { if (typeof Array.prototype.flat === 'function') { @@ -488,6 +488,7 @@ export function flatten (array, isHomogeneous = false) { const flat = [] _recurse(arr) return flat + function _recurse (value) { if (Array.isArray(value)) { const len = value.length From 4591d9d5bc247846ce3c31d43442a73db86fb01d Mon Sep 17 00:00:00 2001 From: David Contreras Date: Thu, 30 Jan 2025 22:20:35 -0600 Subject: [PATCH 7/7] Reverted to original form --- src/function/matrix/flatten.js | 4 +-- src/utils/array.js | 56 ++++++---------------------------- 2 files changed, 11 insertions(+), 49 deletions(-) diff --git a/src/function/matrix/flatten.js b/src/function/matrix/flatten.js index 684e6d9f67..fb4c542c26 100644 --- a/src/function/matrix/flatten.js +++ b/src/function/matrix/flatten.js @@ -26,13 +26,13 @@ export const createFlatten = /* #__PURE__ */ factory(name, dependencies, ({ type */ return typed(name, { Array: function (x) { - return flattenArray(x, false) + return flattenArray(x) }, Matrix: function (x) { // Return the same matrix type as x (Dense or Sparse Matrix) // Return the same data type as x - return x.create(flattenArray(x.toArray(), true), x.datatype()) + return x.create(flattenArray(x.valueOf()), x.datatype()) } }) }) diff --git a/src/utils/array.js b/src/utils/array.js index f7f85e143e..f27a913ed6 100644 --- a/src/utils/array.js +++ b/src/utils/array.js @@ -465,62 +465,24 @@ function _unsqueeze (array, dims, dim) { * Flatten a multi dimensional array, put all elements in a one dimensional * array * @param {Array} array A multi dimensional array - * @param {boolean} [hasHomogeneousSize=false] Indicates if the size is homogeneous (like a valid matrix) * @return {Array} The flattened array (1 dimensional) */ -export function flatten (array, hasHomogeneousSize = false) { +export function flatten (array) { if (!Array.isArray(array)) { // if not an array, return as is return array } - if (hasHomogeneousSize) { - return _flattenHomogeneous(array) - } else { - if (typeof Array.prototype.flat === 'function') { - return array.flat(Infinity) - } else { - return _flattenFallback(array) - } - } + const flat = [] - function _flattenFallback (arr) { - // remove this when Array.prototype.flat is broadly supported - const flat = [] - _recurse(arr) - return flat - - function _recurse (value) { - if (Array.isArray(value)) { - const len = value.length - for (let i = 0; i < len; i++) { - _recurse(value[i]) // traverse through sub-arrays recursively - } - } else { - flat.push(value) - } + array.forEach(function callback (value) { + if (Array.isArray(value)) { + value.forEach(callback) // traverse through sub-arrays recursively + } else { + flat.push(value) } - } + }) - function _flattenHomogeneous (arr) { - const flat = [] - _recurse(arr) - return flat - - function _recurse (value) { - if (Array.isArray(value)) { - const len = value.length - if (Array.isArray(value[0])) { - for (let i = 0; i < len; i++) { - _recurse(value[i]) // traverse through sub-arrays recursively - } - } else { - for (let i = 0; i < len; i++) { - flat.push(value[i]) // traverse through sub-arrays without recursion - } - } - } - } - } + return flat } /**